diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/libwebrtc/modules/desktop_capture | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture')
251 files changed, 34826 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/BUILD.gn b/third_party/libwebrtc/modules/desktop_capture/BUILD.gn new file mode 100644 index 0000000000..8010db0fba --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/BUILD.gn @@ -0,0 +1,752 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("//build/config/linux/gtk/gtk.gni") +import("//build/config/linux/pkg_config.gni") +import("//build/config/ui.gni") +import("//tools/generate_stubs/rules.gni") +import("../../webrtc.gni") + +use_desktop_capture_differ_sse2 = target_cpu == "x86" || target_cpu == "x64" + +config("x11_config") { + if (rtc_use_x11_extensions) { + defines = [ "WEBRTC_USE_X11" ] + } +} + +rtc_library("primitives") { + visibility = [ "*" ] + sources = [ + "desktop_capture_types.h", + "desktop_frame.cc", + "desktop_frame.h", + "desktop_geometry.cc", + "desktop_geometry.h", + "desktop_region.cc", + "desktop_region.h", + "shared_desktop_frame.cc", + "shared_desktop_frame.h", + "shared_memory.cc", + "shared_memory.h", + ] + + deps = [ + "../../api:scoped_refptr", + "../../rtc_base:checks", + "../../rtc_base:refcount", + "../../rtc_base/system:rtc_export", + "//third_party/libyuv", + ] + if (build_with_mozilla) { + deps -= [ "//third_party/libyuv" ] + include_dirs = [ + "/media/libyuv", + "/media/libyuv/libyuv/include", + ] + } + + if (!build_with_mozilla) { + deps += [ "../../rtc_base" ] # TODO(kjellander): Cleanup in + # bugs.webrtc.org/3806. + } +} + +if (rtc_include_tests) { + rtc_library("desktop_capture_modules_tests") { + testonly = true + + defines = [] + sources = [] + deps = [ + ":desktop_capture", + "../../api:function_view", + "../../api:scoped_refptr", + "../../rtc_base:checks", + "../../rtc_base:logging", + "../../rtc_base:platform_thread", + "../../rtc_base:random", + "../../rtc_base:timeutils", + ] + if (rtc_desktop_capture_supported) { + deps += [ + ":desktop_capture_mock", + ":primitives", + ":screen_drawer", + "../../rtc_base", + "../../rtc_base/third_party/base64", + "../../system_wrappers", + "../../test:test_support", + "../../test:video_test_support", + ] + sources += [ + "screen_capturer_integration_test.cc", + "screen_drawer_unittest.cc", + "window_finder_unittest.cc", + ] + + if ((is_linux || is_chromeos) && rtc_use_pipewire) { + configs += [ ":gio" ] + } + + public_configs = [ ":x11_config" ] + + if (is_win) { + deps += [ "../../rtc_base/win:windows_version" ] + } + } + } + + rtc_library("desktop_capture_unittests") { + testonly = true + + defines = [] + sources = [ + "blank_detector_desktop_capturer_wrapper_unittest.cc", + "cropped_desktop_frame_unittest.cc", + "desktop_and_cursor_composer_unittest.cc", + "desktop_capturer_differ_wrapper_unittest.cc", + "desktop_frame_rotation_unittest.cc", + "desktop_frame_unittest.cc", + "desktop_geometry_unittest.cc", + "desktop_region_unittest.cc", + "differ_block_unittest.cc", + "fallback_desktop_capturer_wrapper_unittest.cc", + "mouse_cursor_monitor_unittest.cc", + "rgba_color_unittest.cc", + "test_utils.cc", + "test_utils.h", + "test_utils_unittest.cc", + ] + + if ((is_linux || is_chromeos) && rtc_use_pipewire) { + configs += [ ":gio" ] + } + + deps = [ + ":desktop_capture", + ":desktop_capture_mock", + ":primitives", + "../../rtc_base:checks", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:random", + "../../rtc_base:timeutils", + + # TODO(bugs.webrtc.org/9987): Remove this dep on rtc_base:rtc_base once + # rtc_base:threading is fully defined. + "../../rtc_base:rtc_base", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../system_wrappers", + "../../test:test_support", + ] + + if (is_win) { + sources += [ + "win/cursor_unittest.cc", + "win/cursor_unittest_resources.h", + "win/cursor_unittest_resources.rc", + "win/screen_capture_utils_unittest.cc", + "win/screen_capturer_win_directx_unittest.cc", + "win/test_support/test_window.cc", + "win/test_support/test_window.h", + "win/window_capture_utils_unittest.cc", + ] + deps += [ + "../../rtc_base/win:scoped_com_initializer", + "../../rtc_base/win:windows_version", + ] + } + + if (rtc_desktop_capture_supported) { + sources += [ + "screen_capturer_helper_unittest.cc", + "screen_capturer_unittest.cc", + "window_capturer_unittest.cc", + ] + if (is_mac) { + sources += [ "screen_capturer_mac_unittest.cc" ] + } + if (rtc_enable_win_wgc) { + sources += [ + "win/wgc_capture_source_unittest.cc", + "win/wgc_capturer_win_unittest.cc", + ] + } + deps += [ + ":desktop_capture_mock", + "../../system_wrappers:metrics", + ] + public_configs = [ ":x11_config" ] + } + } + + rtc_library("screen_drawer") { + testonly = true + + sources = [ + "screen_drawer.cc", + "screen_drawer.h", + ] + + if (is_linux || is_chromeos) { + sources += [ "screen_drawer_linux.cc" ] + libs = [ "X11" ] + } + + if (is_mac) { + sources += [ "screen_drawer_mac.cc" ] + } + + if (is_win) { + sources += [ "screen_drawer_win.cc" ] + } + + deps = [ + ":desktop_capture", + ":primitives", + "../../api:scoped_refptr", + "../../rtc_base:checks", + "../../rtc_base:logging", + "../../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + + if (is_posix || is_fuchsia) { + sources += [ + "screen_drawer_lock_posix.cc", + "screen_drawer_lock_posix.h", + ] + } + } + + rtc_library("desktop_capture_mock") { + testonly = true + + sources = [ + "mock_desktop_capturer_callback.cc", + "mock_desktop_capturer_callback.h", + ] + + if ((is_linux || is_chromeos) && rtc_use_pipewire) { + configs += [ ":gio" ] + } + + deps = [ + ":desktop_capture", + ":primitives", + "../../test:test_support", + ] + } +} + +if (is_linux || is_chromeos) { + if (rtc_use_pipewire) { + defines = [ "WEBRTC_USE_PIPEWIRE" ] + if (!build_with_mozilla) { + pkg_config("gio") { + packages = [ + "gio-2.0", + "gio-unix-2.0", + ] + } + + pkg_config("pipewire") { + packages = [ "libpipewire-0.3" ] + if (!rtc_link_pipewire) { + ignore_libs = true + } + } + } + +if (!build_with_mozilla) { + pkg_config("gbm") { + packages = [ "gbm" ] + } + pkg_config("egl") { + packages = [ "egl" ] + } + pkg_config("epoxy") { + packages = [ "epoxy" ] + ignore_libs = true + } + pkg_config("libdrm") { + packages = [ "libdrm" ] + if (!rtc_link_pipewire) { + ignore_libs = true + } + } +} + + if (!rtc_link_pipewire) { + # When libpipewire is not directly linked, use stubs to allow for dlopening of + # the binary. + if (!build_with_mozilla) { + generate_stubs("pipewire_stubs") { + configs = [ + "../../:common_config", + ":pipewire", + ":libdrm", + ] + deps = [ "../../rtc_base" ] + extra_header = "linux/wayland/pipewire_stub_header.fragment" + logging_function = "RTC_LOG(LS_VERBOSE)" + logging_include = "rtc_base/logging.h" + output_name = "linux/wayland/pipewire_stubs" + path_from_source = "modules/desktop_capture/linux/wayland" + sigs = [ + "linux/wayland/pipewire.sigs", + "linux/wayland/drm.sigs", + ] + if (!build_with_chromium) { + macro_include = "rtc_base/system/no_cfi_icall.h" + macro_deps = [ "../../rtc_base/system:no_cfi_icall" ] + } + } + } + } + + config("pipewire_config") { + if (!build_with_mozilla) { + defines = [ "WEBRTC_USE_PIPEWIRE" ] + } + if (!rtc_link_pipewire) { + defines += [ "WEBRTC_DLOPEN_PIPEWIRE" ] + } + + # Chromecast build config overrides `WEBRTC_USE_PIPEWIRE` even when + # `rtc_use_pipewire` is not set, which causes pipewire_config to not be + # included in targets. More details in: webrtc:13898 + if (is_linux && !is_castos) { + defines += [ "WEBRTC_USE_GIO" ] + } + } + } +} + +rtc_library("desktop_capture") { + visibility = [ "*" ] + defines = [] + include_dirs = [] + if (build_with_mozilla) { + include_dirs += [ "/media/libyuv/libyuv/include" ] + } + deps = [] + public_configs = [ ":x11_config" ] + sources = [ + "blank_detector_desktop_capturer_wrapper.cc", + "blank_detector_desktop_capturer_wrapper.h", + "cropped_desktop_frame.cc", + "cropped_desktop_frame.h", + "cropping_window_capturer.cc", + "cropping_window_capturer.h", + "desktop_and_cursor_composer.cc", + "desktop_and_cursor_composer.h", + "desktop_capture_metrics_helper.cc", + "desktop_capture_metrics_helper.h", + "desktop_capture_options.cc", + "desktop_capture_options.h", + "desktop_capturer.cc", + "desktop_capturer.h", + "desktop_capturer_differ_wrapper.cc", + "desktop_capturer_differ_wrapper.h", + "desktop_capturer_wrapper.cc", + "desktop_capturer_wrapper.h", + "desktop_frame_generator.cc", + "desktop_frame_generator.h", + "desktop_frame_rotation.cc", + "desktop_frame_rotation.h", + "differ_block.cc", + "differ_block.h", + "fake_desktop_capturer.cc", + "fake_desktop_capturer.h", + "fallback_desktop_capturer_wrapper.cc", + "fallback_desktop_capturer_wrapper.h", + "full_screen_application_handler.cc", + "full_screen_application_handler.h", + "full_screen_window_detector.cc", + "full_screen_window_detector.h", + "mouse_cursor.cc", + "mouse_cursor.h", + "mouse_cursor_monitor.h", + "resolution_tracker.cc", + "resolution_tracker.h", + "rgba_color.cc", + "rgba_color.h", + "screen_capture_frame_queue.h", + "screen_capturer_helper.cc", + "screen_capturer_helper.h", + "window_finder.cc", + "window_finder.h", + ] + if (is_linux && !is_castos && rtc_use_pipewire) { + sources += [ "desktop_capture_metadata.h" ] + } + if (is_mac) { + sources += [ + "mac/desktop_configuration.h", + "mac/desktop_configuration_monitor.cc", + "mac/desktop_configuration_monitor.h", + "mac/full_screen_mac_application_handler.cc", + "mac/full_screen_mac_application_handler.h", + "mac/window_list_utils.cc", + "mac/window_list_utils.h", + ] + deps += [ ":desktop_capture_objc" ] + } + if (rtc_use_x11_extensions || rtc_use_pipewire) { + include_dirs += [ "/third_party/libwebrtc/third_party/pipewire" ] + sources += [ + "mouse_cursor_monitor_linux.cc", + "screen_capturer_linux.cc", + "window_capturer_linux.cc", + ] + } + + if (rtc_use_x11_extensions) { + sources += [ + "linux/x11/mouse_cursor_monitor_x11.cc", + "linux/x11/mouse_cursor_monitor_x11.h", + "linux/x11/screen_capturer_x11.cc", + "linux/x11/screen_capturer_x11.h", + "linux/x11/shared_x_display.cc", + "linux/x11/shared_x_display.h", + "linux/x11/window_capturer_x11.cc", + "linux/x11/window_capturer_x11.h", + "linux/x11/window_finder_x11.cc", + "linux/x11/window_finder_x11.h", + "linux/x11/window_list_utils.cc", + "linux/x11/window_list_utils.h", + "linux/x11/x_atom_cache.cc", + "linux/x11/x_atom_cache.h", + "linux/x11/x_error_trap.cc", + "linux/x11/x_error_trap.h", + "linux/x11/x_server_pixel_buffer.cc", + "linux/x11/x_server_pixel_buffer.h", + "linux/x11/x_window_property.cc", + "linux/x11/x_window_property.h", + ] + libs = [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrender", + "Xrandr", + "Xtst", + ] + } + + if (!is_win && !is_mac && !rtc_use_x11_extensions && !rtc_use_pipewire && + !is_fuchsia) { + sources += [ + "mouse_cursor_monitor_null.cc", + "screen_capturer_null.cc", + "window_capturer_null.cc", + ] + } + + deps += [ + ":primitives", + "../../api:function_view", + "../../api:make_ref_counted", + "../../api:refcountedbase", + "../../api:scoped_refptr", + "../../api:sequence_checker", + "../../rtc_base:checks", + "../../rtc_base:event_tracer", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:random", + "../../rtc_base:stringutils", + "../../rtc_base:timeutils", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:arch", + "../../rtc_base/system:no_unique_address", + "../../rtc_base/system:rtc_export", + "../../system_wrappers", + "../../system_wrappers:metrics", + ] + + if (is_fuchsia) { + sources += [ + "mouse_cursor_monitor_null.cc", + "screen_capturer_fuchsia.cc", + "screen_capturer_fuchsia.h", + "window_capturer_null.cc", + ] + deps += [ + "../../rtc_base:divide_round", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sysmem", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.composition", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.scenic", + "//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp", + "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp", + ] + } + + if (is_win) { + sources += [ + "cropping_window_capturer_win.cc", + "desktop_frame_win.cc", + "desktop_frame_win.h", + "mouse_cursor_monitor_win.cc", + "screen_capturer_win.cc", + "win/cursor.cc", + "win/cursor.h", + "win/d3d_device.cc", + "win/d3d_device.h", + "win/desktop.cc", + "win/desktop.h", + "win/desktop_capture_utils.cc", + "win/desktop_capture_utils.h", + "win/display_configuration_monitor.cc", + "win/display_configuration_monitor.h", + "win/dxgi_adapter_duplicator.cc", + "win/dxgi_adapter_duplicator.h", + "win/dxgi_context.cc", + "win/dxgi_context.h", + "win/dxgi_duplicator_controller.cc", + "win/dxgi_duplicator_controller.h", + "win/dxgi_frame.cc", + "win/dxgi_frame.h", + "win/dxgi_output_duplicator.cc", + "win/dxgi_output_duplicator.h", + "win/dxgi_texture.cc", + "win/dxgi_texture.h", + "win/dxgi_texture_mapping.cc", + "win/dxgi_texture_mapping.h", + "win/dxgi_texture_staging.cc", + "win/dxgi_texture_staging.h", + "win/full_screen_win_application_handler.cc", + "win/full_screen_win_application_handler.h", + "win/scoped_gdi_object.h", + "win/scoped_thread_desktop.cc", + "win/scoped_thread_desktop.h", + "win/screen_capture_utils.cc", + "win/screen_capture_utils.h", + "win/screen_capturer_win_directx.cc", + "win/screen_capturer_win_directx.h", + "win/screen_capturer_win_gdi.cc", + "win/screen_capturer_win_gdi.h", + "win/screen_capturer_win_magnifier.cc", + "win/screen_capturer_win_magnifier.h", + "win/selected_window_context.cc", + "win/selected_window_context.h", + "win/window_capture_utils.cc", + "win/window_capture_utils.h", + "win/window_capturer_win_gdi.cc", + "win/window_capturer_win_gdi.h", + "window_capturer_win.cc", + "window_finder_win.cc", + "window_finder_win.h", + ] + libs = [ + "d3d11.lib", + "dxgi.lib", + ] + deps += [ + "../../rtc_base:win32", + "../../rtc_base/win:create_direct3d_device", + "../../rtc_base/win:get_activation_factory", + "../../rtc_base/win:windows_version", + ] + } + + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + + if (rtc_use_x11_extensions) { + deps += [ "../../rtc_base:sanitizer" ] + } + + if (!build_with_mozilla) { + deps += [ "//third_party/libyuv" ] + } else { + include_dirs += [ + "/media/libyuv", + "/media/libyuv/libyuv/include", + "/third_party/pipewire", + ] + } + + if (use_desktop_capture_differ_sse2) { + deps += [ ":desktop_capture_differ_sse2" ] + } + + if (rtc_use_pipewire) { + if (!build_with_mozilla) { + sources += [ + "linux/wayland/base_capturer_pipewire.cc", + "linux/wayland/base_capturer_pipewire.h", + "linux/wayland/egl_dmabuf.cc", + "linux/wayland/egl_dmabuf.h", + "linux/wayland/mouse_cursor_monitor_pipewire.cc", + "linux/wayland/mouse_cursor_monitor_pipewire.h", + "linux/wayland/portal_request_response.h", + "linux/wayland/restore_token_manager.cc", + "linux/wayland/restore_token_manager.h", + "linux/wayland/scoped_glib.cc", + "linux/wayland/scoped_glib.h", + "linux/wayland/screen_capture_portal_interface.cc", + "linux/wayland/screen_capture_portal_interface.h", + "linux/wayland/screencast_portal.cc", + "linux/wayland/screencast_portal.h", + "linux/wayland/screencast_stream_utils.cc", + "linux/wayland/screencast_stream_utils.h", + "linux/wayland/shared_screencast_stream.cc", + "linux/wayland/shared_screencast_stream.h", + "linux/wayland/xdg_desktop_portal_utils.cc", + "linux/wayland/xdg_desktop_portal_utils.h", + "linux/wayland/xdg_session_details.h", + ] + } else { + sources += [ + "linux/wayland/moz_base_capturer_pipewire.cc", + "linux/wayland/moz_base_capturer_pipewire.h", + ] + } + + if (!build_with_mozilla) { + configs += [ + ":gio", + ":pipewire", + ":gbm", + ":egl", + ":epoxy", + ":libdrm", + ] + } + + if (!rtc_link_pipewire) { + if (!build_with_mozilla) { + deps += [ ":pipewire_stubs" ] + } + + if (!build_with_mozilla) { + configs += [ + ":gio", + ":pipewire", + ] + } else { + defines += [ "WEBRTC_USE_PIPEWIRE" ] + include_dirs += [ "/third_party/pipewire" ] + } + } + + if (!build_with_mozilla) { + public_configs += [ ":pipewire_config" ] + } + + deps += [ "../../rtc_base:sanitizer" ] + } + + if (rtc_enable_win_wgc) { + sources += [ + "win/wgc_capture_session.cc", + "win/wgc_capture_session.h", + "win/wgc_capture_source.cc", + "win/wgc_capture_source.h", + "win/wgc_capturer_win.cc", + "win/wgc_capturer_win.h", + "win/wgc_desktop_frame.cc", + "win/wgc_desktop_frame.h", + ] + libs += [ "dwmapi.lib" ] + deps += [ + "../../rtc_base:rtc_event", + "../../rtc_base/win:hstring", + ] + } +} + +if (is_mac) { + rtc_library("desktop_capture_objc") { + # This target, needs to be separated from ":desktop_capture" because + # that is the C++ part of the target while this one is the Obj-C++ part. + # Aside from this, both represent a "desktop_capture" target. + # This target separation based on programming languages introduces a + # dependency cycle between ":desktop_capture" and + # ":desktop_capture_objc". + # To break this, ":desktop_capture_objc" shares some .h files with + # ":desktop_capture" but when external targets need one of these + # headers, they should depend on ":desktop_capture" and consider + # this target as private. + visibility = [ ":desktop_capture" ] + sources = [ + "desktop_capture_options.h", + "desktop_capturer.h", + "full_screen_application_handler.h", + "full_screen_window_detector.h", + "mac/desktop_configuration.h", + "mac/desktop_configuration.mm", + "mac/desktop_configuration_monitor.h", + "mac/desktop_frame_cgimage.h", + "mac/desktop_frame_cgimage.mm", + "mac/desktop_frame_iosurface.h", + "mac/desktop_frame_iosurface.mm", + "mac/desktop_frame_provider.h", + "mac/desktop_frame_provider.mm", + "mac/screen_capturer_mac.h", + "mac/screen_capturer_mac.mm", + "mac/window_list_utils.h", + "mouse_cursor.h", + "mouse_cursor_monitor.h", + "mouse_cursor_monitor_mac.mm", + "screen_capture_frame_queue.h", + "screen_capturer_darwin.mm", + "screen_capturer_helper.h", + "window_capturer_mac.mm", + "window_finder.h", + "window_finder_mac.h", + "window_finder_mac.mm", + ] + deps = [ + ":primitives", + "../../api:function_view", + "../../api:refcountedbase", + "../../api:scoped_refptr", + "../../api:sequence_checker", + "../../rtc_base", + "../../rtc_base:checks", + "../../rtc_base:event_tracer", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:timeutils", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:rtc_export", + "../../sdk:helpers_objc", + ] + frameworks = [ + "AppKit.framework", + "IOKit.framework", + "IOSurface.framework", + ] + } +} + +if (use_desktop_capture_differ_sse2) { + # Have to be compiled as a separate target because it needs to be compiled + # with SSE2 enabled. + rtc_library("desktop_capture_differ_sse2") { + visibility = [ ":*" ] + sources = [ + "differ_vector_sse2.cc", + "differ_vector_sse2.h", + ] + + if (is_posix || is_fuchsia) { + cflags = [ "-msse2" ] + } + } +} diff --git a/third_party/libwebrtc/modules/desktop_capture/DEPS b/third_party/libwebrtc/modules/desktop_capture/DEPS new file mode 100644 index 0000000000..8c894c4430 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/DEPS @@ -0,0 +1,19 @@ +include_rules = [ + "+system_wrappers", + "+third_party/libyuv", +] + +specific_include_rules = { + "desktop_frame_cgimage\.h": [ + "+sdk/objc", + ], + "desktop_frame_iosurface\.h": [ + "+sdk/objc", + ], + "desktop_frame_provider\.h": [ + "+sdk/objc", + ], + "screen_capturer_mac\.mm": [ + "+sdk/objc", + ], +} diff --git a/third_party/libwebrtc/modules/desktop_capture/OWNERS b/third_party/libwebrtc/modules/desktop_capture/OWNERS new file mode 100644 index 0000000000..e3bc32ee5c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/OWNERS @@ -0,0 +1,2 @@ +alcooper@chromium.org +mfoltz@chromium.org diff --git a/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.cc b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.cc new file mode 100644 index 0000000000..8e56ffc3fd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" + +#include <stdint.h> + +#include <utility> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +BlankDetectorDesktopCapturerWrapper::BlankDetectorDesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer> capturer, + RgbaColor blank_pixel, + bool check_per_capture) + : capturer_(std::move(capturer)), + blank_pixel_(blank_pixel), + check_per_capture_(check_per_capture) { + RTC_DCHECK(capturer_); +} + +BlankDetectorDesktopCapturerWrapper::~BlankDetectorDesktopCapturerWrapper() = + default; + +void BlankDetectorDesktopCapturerWrapper::Start( + DesktopCapturer::Callback* callback) { + callback_ = callback; + capturer_->Start(this); +} + +void BlankDetectorDesktopCapturerWrapper::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void BlankDetectorDesktopCapturerWrapper::CaptureFrame() { + RTC_DCHECK(callback_); + capturer_->CaptureFrame(); +} + +void BlankDetectorDesktopCapturerWrapper::SetExcludedWindow(WindowId window) { + capturer_->SetExcludedWindow(window); +} + +bool BlankDetectorDesktopCapturerWrapper::GetSourceList(SourceList* sources) { + return capturer_->GetSourceList(sources); +} + +bool BlankDetectorDesktopCapturerWrapper::SelectSource(SourceId id) { + if (check_per_capture_) { + // If we start capturing a new source, we must reset these members + // so we don't short circuit the blank detection logic. + is_first_frame_ = true; + non_blank_frame_received_ = false; + } + + return capturer_->SelectSource(id); +} + +bool BlankDetectorDesktopCapturerWrapper::FocusOnSelectedSource() { + return capturer_->FocusOnSelectedSource(); +} + +bool BlankDetectorDesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) { + return capturer_->IsOccluded(pos); +} + +void BlankDetectorDesktopCapturerWrapper::OnCaptureResult( + Result result, + std::unique_ptr<DesktopFrame> frame) { + RTC_DCHECK(callback_); + if (result != Result::SUCCESS || non_blank_frame_received_) { + callback_->OnCaptureResult(result, std::move(frame)); + return; + } + + if (!frame) { + // Capturer can call the blank detector with empty frame. Blank + // detector regards it as a blank frame. + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, + std::unique_ptr<DesktopFrame>()); + return; + } + + // If nothing has been changed in current frame, we do not need to check it + // again. + if (!frame->updated_region().is_empty() || is_first_frame_) { + last_frame_is_blank_ = IsBlankFrame(*frame); + is_first_frame_ = false; + } + RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.BlankFrameDetected", + last_frame_is_blank_); + if (!last_frame_is_blank_) { + non_blank_frame_received_ = true; + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); + return; + } + + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, + std::unique_ptr<DesktopFrame>()); +} + +bool BlankDetectorDesktopCapturerWrapper::IsBlankFrame( + const DesktopFrame& frame) const { + // We will check 7489 pixels for a frame with 1024 x 768 resolution. + for (int i = 0; i < frame.size().width() * frame.size().height(); i += 105) { + const int x = i % frame.size().width(); + const int y = i / frame.size().width(); + if (!IsBlankPixel(frame, x, y)) { + return false; + } + } + + // We are verifying the pixel in the center as well. + return IsBlankPixel(frame, frame.size().width() / 2, + frame.size().height() / 2); +} + +bool BlankDetectorDesktopCapturerWrapper::IsBlankPixel( + const DesktopFrame& frame, + int x, + int y) const { + uint8_t* pixel_data = frame.GetFrameDataAtPos(DesktopVector(x, y)); + return RgbaColor(pixel_data) == blank_pixel_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h new file mode 100644 index 0000000000..d10f9cf725 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_ +#define MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/rgba_color.h" +#include "modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +// A DesktopCapturer wrapper detects the return value of its owned +// DesktopCapturer implementation. If sampled pixels returned by the +// DesktopCapturer implementation all equal to the blank pixel, this wrapper +// returns ERROR_TEMPORARY. If the DesktopCapturer implementation fails for too +// many times, this wrapper returns ERROR_PERMANENT. +class BlankDetectorDesktopCapturerWrapper final + : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + // Creates BlankDetectorDesktopCapturerWrapper. BlankDesktopCapturerWrapper + // takes ownership of `capturer`. The `blank_pixel` is the unmodified color + // returned by the `capturer`. + BlankDetectorDesktopCapturerWrapper(std::unique_ptr<DesktopCapturer> capturer, + RgbaColor blank_pixel, + bool check_per_capture = false); + ~BlankDetectorDesktopCapturerWrapper() override; + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + private: + // DesktopCapturer::Callback interface. + void OnCaptureResult(Result result, + std::unique_ptr<DesktopFrame> frame) override; + + bool IsBlankFrame(const DesktopFrame& frame) const; + + // Detects whether pixel at (x, y) equals to `blank_pixel_`. + bool IsBlankPixel(const DesktopFrame& frame, int x, int y) const; + + const std::unique_ptr<DesktopCapturer> capturer_; + const RgbaColor blank_pixel_; + + // Whether a non-blank frame has been received. + bool non_blank_frame_received_ = false; + + // Whether the last frame is blank. + bool last_frame_is_blank_ = false; + + // Whether current frame is the first frame. + bool is_first_frame_ = true; + + // Blank inspection is made per capture instead of once for all + // screens or windows. + bool check_per_capture_ = false; + + DesktopCapturer::Callback* callback_ = nullptr; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper_unittest.cc new file mode 100644 index 0000000000..25a81edd89 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper_unittest.cc @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_frame_generator.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/fake_desktop_capturer.h" +#include "test/gtest.h" + +namespace webrtc { + +class BlankDetectorDesktopCapturerWrapperTest + : public ::testing::Test, + public DesktopCapturer::Callback { + public: + BlankDetectorDesktopCapturerWrapperTest(); + ~BlankDetectorDesktopCapturerWrapperTest() override; + + protected: + void PerfTest(DesktopCapturer* capturer); + + const int frame_width_ = 1024; + const int frame_height_ = 768; + std::unique_ptr<BlankDetectorDesktopCapturerWrapper> wrapper_; + DesktopCapturer* capturer_ = nullptr; + BlackWhiteDesktopFramePainter painter_; + int num_frames_captured_ = 0; + DesktopCapturer::Result last_result_ = DesktopCapturer::Result::SUCCESS; + std::unique_ptr<DesktopFrame> last_frame_; + + private: + // DesktopCapturer::Callback interface. + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override; + + PainterDesktopFrameGenerator frame_generator_; +}; + +BlankDetectorDesktopCapturerWrapperTest:: + BlankDetectorDesktopCapturerWrapperTest() { + frame_generator_.size()->set(frame_width_, frame_height_); + frame_generator_.set_desktop_frame_painter(&painter_); + std::unique_ptr<DesktopCapturer> capturer(new FakeDesktopCapturer()); + FakeDesktopCapturer* fake_capturer = + static_cast<FakeDesktopCapturer*>(capturer.get()); + fake_capturer->set_frame_generator(&frame_generator_); + capturer_ = fake_capturer; + wrapper_.reset(new BlankDetectorDesktopCapturerWrapper( + std::move(capturer), RgbaColor(0, 0, 0, 0))); + wrapper_->Start(this); +} + +BlankDetectorDesktopCapturerWrapperTest:: + ~BlankDetectorDesktopCapturerWrapperTest() = default; + +void BlankDetectorDesktopCapturerWrapperTest::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) { + last_result_ = result; + last_frame_ = std::move(frame); + num_frames_captured_++; +} + +void BlankDetectorDesktopCapturerWrapperTest::PerfTest( + DesktopCapturer* capturer) { + for (int i = 0; i < 10000; i++) { + capturer->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, i + 1); + } +} + +TEST_F(BlankDetectorDesktopCapturerWrapperTest, ShouldDetectBlankFrame) { + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 1); + ASSERT_EQ(last_result_, DesktopCapturer::Result::ERROR_TEMPORARY); + ASSERT_FALSE(last_frame_); +} + +TEST_F(BlankDetectorDesktopCapturerWrapperTest, ShouldPassBlankDetection) { + painter_.updated_region()->AddRect(DesktopRect::MakeXYWH(0, 0, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 1); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + + painter_.updated_region()->AddRect( + DesktopRect::MakeXYWH(frame_width_ - 100, frame_height_ - 100, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 2); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + + painter_.updated_region()->AddRect( + DesktopRect::MakeXYWH(0, frame_height_ - 100, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 3); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + + painter_.updated_region()->AddRect( + DesktopRect::MakeXYWH(frame_width_ - 100, 0, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 4); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + + painter_.updated_region()->AddRect(DesktopRect::MakeXYWH( + (frame_width_ >> 1) - 50, (frame_height_ >> 1) - 50, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 5); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); +} + +TEST_F(BlankDetectorDesktopCapturerWrapperTest, + ShouldNotCheckAfterANonBlankFrameReceived) { + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 1); + ASSERT_EQ(last_result_, DesktopCapturer::Result::ERROR_TEMPORARY); + ASSERT_FALSE(last_frame_); + + painter_.updated_region()->AddRect( + DesktopRect::MakeXYWH(frame_width_ - 100, 0, 100, 100)); + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, 2); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + + for (int i = 0; i < 100; i++) { + wrapper_->CaptureFrame(); + ASSERT_EQ(num_frames_captured_, i + 3); + ASSERT_EQ(last_result_, DesktopCapturer::Result::SUCCESS); + ASSERT_TRUE(last_frame_); + } +} + +// There is no perceptible impact by using BlankDetectorDesktopCapturerWrapper. +// i.e. less than 0.2ms per frame. +// [ OK ] DISABLED_Performance (10210 ms) +// [ OK ] DISABLED_PerformanceComparison (8791 ms) +TEST_F(BlankDetectorDesktopCapturerWrapperTest, DISABLED_Performance) { + PerfTest(wrapper_.get()); +} + +TEST_F(BlankDetectorDesktopCapturerWrapperTest, + DISABLED_PerformanceComparison) { + capturer_->Start(this); + PerfTest(capturer_); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.cc b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.cc new file mode 100644 index 0000000000..54488b7d62 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/cropped_desktop_frame.h" + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_region.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +// A DesktopFrame that is a sub-rect of another DesktopFrame. +class CroppedDesktopFrame : public DesktopFrame { + public: + CroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame, + const DesktopRect& rect); + + CroppedDesktopFrame(const CroppedDesktopFrame&) = delete; + CroppedDesktopFrame& operator=(const CroppedDesktopFrame&) = delete; + + private: + const std::unique_ptr<DesktopFrame> frame_; +}; + +std::unique_ptr<DesktopFrame> CreateCroppedDesktopFrame( + std::unique_ptr<DesktopFrame> frame, + const DesktopRect& rect) { + RTC_DCHECK(frame); + + DesktopRect intersection = DesktopRect::MakeSize(frame->size()); + intersection.IntersectWith(rect); + if (intersection.is_empty()) { + return nullptr; + } + + if (frame->size().equals(rect.size())) { + return frame; + } + + return std::unique_ptr<DesktopFrame>( + new CroppedDesktopFrame(std::move(frame), intersection)); +} + +CroppedDesktopFrame::CroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame, + const DesktopRect& rect) + : DesktopFrame(rect.size(), + frame->stride(), + frame->GetFrameDataAtPos(rect.top_left()), + frame->shared_memory()), + frame_(std::move(frame)) { + MoveFrameInfoFrom(frame_.get()); + set_top_left(frame_->top_left().add(rect.top_left())); + mutable_updated_region()->IntersectWith(rect); + mutable_updated_region()->Translate(-rect.left(), -rect.top()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.h b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.h new file mode 100644 index 0000000000..5c672c7d32 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ +#define MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// Creates a DesktopFrame to contain only the area of `rect` in the original +// `frame`. +// `frame` should not be nullptr. `rect` is in `frame` coordinate, i.e. +// `frame`->top_left() does not impact the area of `rect`. +// Returns nullptr frame if `rect` is not contained by the bounds of `frame`. +std::unique_ptr<DesktopFrame> RTC_EXPORT +CreateCroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame, + const DesktopRect& rect); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame_unittest.cc new file mode 100644 index 0000000000..9becf69636 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame_unittest.cc @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/cropped_desktop_frame.h" + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "test/gtest.h" + +namespace webrtc { + +std::unique_ptr<DesktopFrame> CreateTestFrame() { + return std::make_unique<BasicDesktopFrame>(DesktopSize(10, 20)); +} + +TEST(CroppedDesktopFrameTest, DoNotCreateWrapperIfSizeIsNotChanged) { + std::unique_ptr<DesktopFrame> original = CreateTestFrame(); + // owned by `original` and CroppedDesktopFrame. + DesktopFrame* raw_original = original.get(); + std::unique_ptr<DesktopFrame> cropped = CreateCroppedDesktopFrame( + std::move(original), DesktopRect::MakeWH(10, 20)); + ASSERT_EQ(cropped.get(), raw_original); +} + +TEST(CroppedDesktopFrameTest, CropWhenPartiallyOutOfBounds) { + std::unique_ptr<DesktopFrame> cropped = + CreateCroppedDesktopFrame(CreateTestFrame(), DesktopRect::MakeWH(11, 10)); + ASSERT_NE(nullptr, cropped); + ASSERT_EQ(cropped->size().width(), 10); + ASSERT_EQ(cropped->size().height(), 10); + ASSERT_EQ(cropped->top_left().x(), 0); + ASSERT_EQ(cropped->top_left().y(), 0); +} + +TEST(CroppedDesktopFrameTest, ReturnNullIfCropRegionIsOutOfBounds) { + std::unique_ptr<DesktopFrame> frame = CreateTestFrame(); + frame->set_top_left(DesktopVector(100, 200)); + ASSERT_EQ(nullptr, + CreateCroppedDesktopFrame( + std::move(frame), DesktopRect::MakeLTRB(101, 203, 109, 218))); +} + +TEST(CroppedDesktopFrameTest, CropASubArea) { + std::unique_ptr<DesktopFrame> cropped = CreateCroppedDesktopFrame( + CreateTestFrame(), DesktopRect::MakeLTRB(1, 2, 9, 19)); + ASSERT_EQ(cropped->size().width(), 8); + ASSERT_EQ(cropped->size().height(), 17); + ASSERT_EQ(cropped->top_left().x(), 1); + ASSERT_EQ(cropped->top_left().y(), 2); +} + +TEST(CroppedDesktopFrameTest, SetTopLeft) { + std::unique_ptr<DesktopFrame> frame = CreateTestFrame(); + frame->set_top_left(DesktopVector(100, 200)); + frame = CreateCroppedDesktopFrame(std::move(frame), + DesktopRect::MakeLTRB(1, 3, 9, 18)); + ASSERT_EQ(frame->size().width(), 8); + ASSERT_EQ(frame->size().height(), 15); + ASSERT_EQ(frame->top_left().x(), 101); + ASSERT_EQ(frame->top_left().y(), 203); +} + +TEST(CroppedDesktopFrameTest, InitializedWithZeros) { + std::unique_ptr<DesktopFrame> frame = CreateTestFrame(); + const DesktopVector frame_origin = frame->top_left(); + const DesktopSize frame_size = frame->size(); + std::unique_ptr<DesktopFrame> cropped = CreateCroppedDesktopFrame( + std::move(frame), DesktopRect::MakeOriginSize(frame_origin, frame_size)); + for (int j = 0; j < cropped->size().height(); ++j) { + for (int i = 0; i < cropped->stride(); ++i) { + ASSERT_EQ(cropped->data()[i + j * cropped->stride()], 0); + } + } +} + +TEST(CroppedDesktopFrameTest, IccProfile) { + const uint8_t fake_icc_profile_data_array[] = {0x1a, 0x00, 0x2b, 0x00, + 0x3c, 0x00, 0x4d}; + const std::vector<uint8_t> icc_profile( + fake_icc_profile_data_array, + fake_icc_profile_data_array + sizeof(fake_icc_profile_data_array)); + + std::unique_ptr<DesktopFrame> frame = CreateTestFrame(); + EXPECT_EQ(frame->icc_profile().size(), 0UL); + + frame->set_icc_profile(icc_profile); + EXPECT_EQ(frame->icc_profile().size(), 7UL); + EXPECT_EQ(frame->icc_profile(), icc_profile); + + frame = CreateCroppedDesktopFrame(std::move(frame), + DesktopRect::MakeLTRB(2, 2, 8, 18)); + EXPECT_EQ(frame->icc_profile().size(), 7UL); + EXPECT_EQ(frame->icc_profile(), icc_profile); + + std::unique_ptr<SharedDesktopFrame> shared = + SharedDesktopFrame::Wrap(std::move(frame)); + EXPECT_EQ(shared->icc_profile().size(), 7UL); + EXPECT_EQ(shared->icc_profile(), icc_profile); + + std::unique_ptr<DesktopFrame> shared_other = shared->Share(); + EXPECT_EQ(shared_other->icc_profile().size(), 7UL); + EXPECT_EQ(shared_other->icc_profile(), icc_profile); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.cc b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.cc new file mode 100644 index 0000000000..5e0faaade9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.cc @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/cropping_window_capturer.h" + +#include <stddef.h> + +#include <utility> + +#include "modules/desktop_capture/cropped_desktop_frame.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +CroppingWindowCapturer::CroppingWindowCapturer( + const DesktopCaptureOptions& options) + : options_(options), + callback_(NULL), + window_capturer_(DesktopCapturer::CreateRawWindowCapturer(options)), + selected_window_(kNullWindowId), + excluded_window_(kNullWindowId) {} + +CroppingWindowCapturer::~CroppingWindowCapturer() {} + +void CroppingWindowCapturer::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + window_capturer_->Start(callback); +} + +void CroppingWindowCapturer::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + window_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void CroppingWindowCapturer::CaptureFrame() { + if (ShouldUseScreenCapturer()) { + if (!screen_capturer_.get()) { + screen_capturer_ = DesktopCapturer::CreateRawScreenCapturer(options_); + if (excluded_window_) { + screen_capturer_->SetExcludedWindow(excluded_window_); + } + screen_capturer_->Start(this); + } + screen_capturer_->CaptureFrame(); + } else { + window_capturer_->CaptureFrame(); + } +} + +void CroppingWindowCapturer::SetExcludedWindow(WindowId window) { + excluded_window_ = window; + if (screen_capturer_.get()) { + screen_capturer_->SetExcludedWindow(window); + } +} + +bool CroppingWindowCapturer::GetSourceList(SourceList* sources) { + return window_capturer_->GetSourceList(sources); +} + +bool CroppingWindowCapturer::SelectSource(SourceId id) { + if (window_capturer_->SelectSource(id)) { + selected_window_ = id; + return true; + } + return false; +} + +bool CroppingWindowCapturer::FocusOnSelectedSource() { + return window_capturer_->FocusOnSelectedSource(); +} + +void CroppingWindowCapturer::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> screen_frame) { + if (!ShouldUseScreenCapturer()) { + RTC_LOG(LS_INFO) << "Window no longer on top when ScreenCapturer finishes"; + window_capturer_->CaptureFrame(); + return; + } + + if (result != Result::SUCCESS) { + RTC_LOG(LS_WARNING) << "ScreenCapturer failed to capture a frame"; + callback_->OnCaptureResult(result, nullptr); + return; + } + + DesktopRect window_rect = GetWindowRectInVirtualScreen(); + if (window_rect.is_empty()) { + RTC_LOG(LS_WARNING) << "Window rect is empty"; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + std::unique_ptr<DesktopFrame> cropped_frame = + CreateCroppedDesktopFrame(std::move(screen_frame), window_rect); + + if (!cropped_frame) { + RTC_LOG(LS_WARNING) << "Window is outside of the captured display"; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + callback_->OnCaptureResult(Result::SUCCESS, std::move(cropped_frame)); +} + +bool CroppingWindowCapturer::IsOccluded(const DesktopVector& pos) { + // Returns true if either capturer returns true. + if (window_capturer_->IsOccluded(pos)) { + return true; + } + if (screen_capturer_ != nullptr && screen_capturer_->IsOccluded(pos)) { + return true; + } + return false; +} + +#if !defined(WEBRTC_WIN) +// CroppingWindowCapturer is implemented only for windows. On other platforms +// the regular window capturer is used. +// static +std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer( + const DesktopCaptureOptions& options) { + return DesktopCapturer::CreateWindowCapturer(options); +} +#endif + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.h b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.h new file mode 100644 index 0000000000..56478030b1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ +#define MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// WindowCapturer implementation that uses a screen capturer to capture the +// whole screen and crops the video frame to the window area when the captured +// window is on top. +class RTC_EXPORT CroppingWindowCapturer : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + static std::unique_ptr<DesktopCapturer> CreateCapturer( + const DesktopCaptureOptions& options); + + ~CroppingWindowCapturer() override; + + // DesktopCapturer implementation. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + // DesktopCapturer::Callback implementation, passed to `screen_capturer_` to + // intercept the capture result. + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override; + + protected: + explicit CroppingWindowCapturer(const DesktopCaptureOptions& options); + + // The platform implementation should override these methods. + + // Returns true if it is OK to capture the whole screen and crop to the + // selected window, i.e. the selected window is opaque, rectangular, and not + // occluded. + virtual bool ShouldUseScreenCapturer() = 0; + + // Returns the window area relative to the top left of the virtual screen + // within the bounds of the virtual screen. This function should return the + // DesktopRect in full desktop coordinates, i.e. the top-left monitor starts + // from (0, 0). + virtual DesktopRect GetWindowRectInVirtualScreen() = 0; + + WindowId selected_window() const { return selected_window_; } + WindowId excluded_window() const { return excluded_window_; } + DesktopCapturer* window_capturer() const { return window_capturer_.get(); } + + private: + DesktopCaptureOptions options_; + DesktopCapturer::Callback* callback_; + std::unique_ptr<DesktopCapturer> window_capturer_; + std::unique_ptr<DesktopCapturer> screen_capturer_; + SourceId selected_window_; + WindowId excluded_window_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer_win.cc b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer_win.cc new file mode 100644 index 0000000000..64d9219e24 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer_win.cc @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/cropping_window_capturer.h" +#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/selected_window_context.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" +#include "rtc_base/win/windows_version.h" + +namespace webrtc { + +namespace { + +// Used to pass input data for verifying the selected window is on top. +struct TopWindowVerifierContext : public SelectedWindowContext { + TopWindowVerifierContext(HWND selected_window, + HWND excluded_window, + DesktopRect selected_window_rect, + WindowCaptureHelperWin* window_capture_helper) + : SelectedWindowContext(selected_window, + selected_window_rect, + window_capture_helper), + excluded_window(excluded_window) { + RTC_DCHECK_NE(selected_window, excluded_window); + } + + // Determines whether the selected window is on top (not occluded by any + // windows except for those it owns or any excluded window). + bool IsTopWindow() { + if (!IsSelectedWindowValid()) { + return false; + } + + // Enumerate all top-level windows above the selected window in Z-order, + // checking whether any overlaps it. This uses FindWindowEx rather than + // EnumWindows because the latter excludes certain system windows (e.g. the + // Start menu & other taskbar menus) that should be detected here to avoid + // inadvertent capture. + int num_retries = 0; + while (true) { + HWND hwnd = nullptr; + while ((hwnd = FindWindowEx(nullptr, hwnd, nullptr, nullptr))) { + if (hwnd == selected_window()) { + // Windows are enumerated in top-down Z-order, so we can stop + // enumerating upon reaching the selected window & report it's on top. + return true; + } + + // Ignore the excluded window. + if (hwnd == excluded_window) { + continue; + } + + // Ignore windows that aren't visible on the current desktop. + if (!window_capture_helper()->IsWindowVisibleOnCurrentDesktop(hwnd)) { + continue; + } + + // Ignore Chrome notification windows, especially the notification for + // the ongoing window sharing. Notes: + // - This only works with notifications from Chrome, not other Apps. + // - All notifications from Chrome will be ignored. + // - This may cause part or whole of notification window being cropped + // into the capturing of the target window if there is overlapping. + if (window_capture_helper()->IsWindowChromeNotification(hwnd)) { + continue; + } + + // Ignore windows owned by the selected window since we want to capture + // them. + if (IsWindowOwnedBySelectedWindow(hwnd)) { + continue; + } + + // Check whether this window intersects with the selected window. + if (IsWindowOverlappingSelectedWindow(hwnd)) { + // If intersection is not empty, the selected window is not on top. + return false; + } + } + + DWORD lastError = GetLastError(); + if (lastError == ERROR_SUCCESS) { + // The enumeration completed successfully without finding the selected + // window (which may have been closed). + RTC_LOG(LS_WARNING) << "Failed to find selected window (only expected " + "if it was closed)"; + RTC_DCHECK(!IsWindow(selected_window())); + return false; + } else if (lastError == ERROR_INVALID_WINDOW_HANDLE) { + // This error may occur if a window is closed around the time it's + // enumerated; retry the enumeration in this case up to 10 times + // (this should be a rare race & unlikely to recur). + if (++num_retries <= 10) { + RTC_LOG(LS_WARNING) << "Enumeration failed due to race with a window " + "closing; retrying - retry #" + << num_retries; + continue; + } else { + RTC_LOG(LS_ERROR) + << "Exhausted retry allowance around window enumeration failures " + "due to races with windows closing"; + } + } + + // The enumeration failed with an unexpected error (or more repeats of + // an infrequently-expected error than anticipated). After logging this & + // firing an assert when enabled, report that the selected window isn't + // topmost to avoid inadvertent capture of other windows. + RTC_LOG(LS_ERROR) << "Failed to enumerate windows: " << lastError; + RTC_DCHECK_NOTREACHED(); + return false; + } + } + + const HWND excluded_window; +}; + +class CroppingWindowCapturerWin : public CroppingWindowCapturer { + public: + explicit CroppingWindowCapturerWin(const DesktopCaptureOptions& options) + : CroppingWindowCapturer(options), + enumerate_current_process_windows_( + options.enumerate_current_process_windows()), + full_screen_window_detector_(options.full_screen_window_detector()) {} + + void CaptureFrame() override; + + private: + bool ShouldUseScreenCapturer() override; + DesktopRect GetWindowRectInVirtualScreen() override; + + // Returns either selected by user sourceId or sourceId provided by + // FullScreenWindowDetector + WindowId GetWindowToCapture() const; + + // The region from GetWindowRgn in the desktop coordinate if the region is + // rectangular, or the rect from GetWindowRect if the region is not set. + DesktopRect window_region_rect_; + + WindowCaptureHelperWin window_capture_helper_; + + bool enumerate_current_process_windows_; + + rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_; +}; + +void CroppingWindowCapturerWin::CaptureFrame() { + DesktopCapturer* win_capturer = window_capturer(); + if (win_capturer) { + // Feed the actual list of windows into full screen window detector. + if (full_screen_window_detector_) { + full_screen_window_detector_->UpdateWindowListIfNeeded( + selected_window(), [this](DesktopCapturer::SourceList* sources) { + // Get the list of top level windows, including ones with empty + // title. win_capturer_->GetSourceList can't be used here + // cause it filters out the windows with empty titles and + // it uses responsiveness check which could lead to performance + // issues. + SourceList result; + int window_list_flags = + enumerate_current_process_windows_ + ? GetWindowListFlags::kNone + : GetWindowListFlags::kIgnoreCurrentProcessWindows; + + if (!webrtc::GetWindowList(window_list_flags, &result)) + return false; + + // Filter out windows not visible on current desktop + auto it = std::remove_if( + result.begin(), result.end(), [this](const auto& source) { + HWND hwnd = reinterpret_cast<HWND>(source.id); + return !window_capture_helper_ + .IsWindowVisibleOnCurrentDesktop(hwnd); + }); + result.erase(it, result.end()); + + sources->swap(result); + return true; + }); + } + win_capturer->SelectSource(GetWindowToCapture()); + } + + CroppingWindowCapturer::CaptureFrame(); +} + +bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() { + if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8 && + window_capture_helper_.IsAeroEnabled()) { + return false; + } + + const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture()); + // Check if the window is visible on current desktop. + if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) { + return false; + } + + // Check if the window is a translucent layered window. + const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE); + if (window_ex_style & WS_EX_LAYERED) { + COLORREF color_ref_key = 0; + BYTE alpha = 0; + DWORD flags = 0; + + // GetLayeredWindowAttributes fails if the window was setup with + // UpdateLayeredWindow. We have no way to know the opacity of the window in + // that case. This happens for Stiky Note (crbug/412726). + if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags)) + return false; + + // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause + // the previous GetLayeredWindowAttributes to fail. So we only need to check + // the window wide color key or alpha. + if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) { + return false; + } + } + + if (!GetWindowRect(selected, &window_region_rect_)) { + return false; + } + + DesktopRect content_rect; + if (!GetWindowContentRect(selected, &content_rect)) { + return false; + } + + DesktopRect region_rect; + // Get the window region and check if it is rectangular. + const int region_type = + GetWindowRegionTypeWithBoundary(selected, ®ion_rect); + + // Do not use the screen capturer if the region is empty or not rectangular. + if (region_type == COMPLEXREGION || region_type == NULLREGION) { + return false; + } + + if (region_type == SIMPLEREGION) { + // The `region_rect` returned from GetRgnBox() is always in window + // coordinate. + region_rect.Translate(window_region_rect_.left(), + window_region_rect_.top()); + // MSDN: The window region determines the area *within* the window where the + // system permits drawing. + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx. + // + // `region_rect` should always be inside of `window_region_rect_`. So after + // the intersection, `window_region_rect_` == `region_rect`. If so, what's + // the point of the intersecting operations? Why cannot we directly retrieve + // `window_region_rect_` from GetWindowRegionTypeWithBoundary() function? + // TODO(zijiehe): Figure out the purpose of these intersections. + window_region_rect_.IntersectWith(region_rect); + content_rect.IntersectWith(region_rect); + } + + // Check if the client area is out of the screen area. When the window is + // maximized, only its client area is visible in the screen, the border will + // be hidden. So we are using `content_rect` here. + if (!GetFullscreenRect().ContainsRect(content_rect)) { + return false; + } + + // Check if the window is occluded by any other window, excluding the child + // windows, context menus, and `excluded_window_`. + // `content_rect` is preferred, see the comments on + // IsWindowIntersectWithSelectedWindow(). + TopWindowVerifierContext context(selected, + reinterpret_cast<HWND>(excluded_window()), + content_rect, &window_capture_helper_); + return context.IsTopWindow(); +} + +DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() { + TRACE_EVENT0("webrtc", + "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen"); + DesktopRect window_rect; + HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture()); + if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect, + /*original_rect*/ nullptr)) { + RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError(); + return window_rect; + } + window_rect.IntersectWith(window_region_rect_); + + // Convert `window_rect` to be relative to the top-left of the virtual screen. + DesktopRect screen_rect(GetFullscreenRect()); + window_rect.IntersectWith(screen_rect); + window_rect.Translate(-screen_rect.left(), -screen_rect.top()); + return window_rect; +} + +WindowId CroppingWindowCapturerWin::GetWindowToCapture() const { + const auto selected_source = selected_window(); + const auto full_screen_source = + full_screen_window_detector_ + ? full_screen_window_detector_->FindFullScreenWindow(selected_source) + : 0; + return full_screen_source ? full_screen_source : selected_source; +} + +} // namespace + +// static +std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr<DesktopCapturer> capturer( + new CroppingWindowCapturerWin(options)); + if (capturer && options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.cc new file mode 100644 index 0000000000..dd688ac5f2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.cc @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_and_cursor_composer.h" + +#include <stdint.h> +#include <string.h> + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +// Helper function that blends one image into another. Source image must be +// pre-multiplied with the alpha channel. Destination is assumed to be opaque. +void AlphaBlend(uint8_t* dest, + int dest_stride, + const uint8_t* src, + int src_stride, + const DesktopSize& size) { + for (int y = 0; y < size.height(); ++y) { + for (int x = 0; x < size.width(); ++x) { + uint32_t base_alpha = 255 - src[x * DesktopFrame::kBytesPerPixel + 3]; + if (base_alpha == 255) { + continue; + } else if (base_alpha == 0) { + memcpy(dest + x * DesktopFrame::kBytesPerPixel, + src + x * DesktopFrame::kBytesPerPixel, + DesktopFrame::kBytesPerPixel); + } else { + dest[x * DesktopFrame::kBytesPerPixel] = + dest[x * DesktopFrame::kBytesPerPixel] * base_alpha / 255 + + src[x * DesktopFrame::kBytesPerPixel]; + dest[x * DesktopFrame::kBytesPerPixel + 1] = + dest[x * DesktopFrame::kBytesPerPixel + 1] * base_alpha / 255 + + src[x * DesktopFrame::kBytesPerPixel + 1]; + dest[x * DesktopFrame::kBytesPerPixel + 2] = + dest[x * DesktopFrame::kBytesPerPixel + 2] * base_alpha / 255 + + src[x * DesktopFrame::kBytesPerPixel + 2]; + } + } + src += src_stride; + dest += dest_stride; + } +} + +// DesktopFrame wrapper that draws mouse on a frame and restores original +// content before releasing the underlying frame. +class DesktopFrameWithCursor : public DesktopFrame { + public: + // Takes ownership of `frame`. + DesktopFrameWithCursor(std::unique_ptr<DesktopFrame> frame, + const MouseCursor& cursor, + const DesktopVector& position, + const DesktopRect& previous_cursor_rect, + bool cursor_changed); + ~DesktopFrameWithCursor() override; + + DesktopFrameWithCursor(const DesktopFrameWithCursor&) = delete; + DesktopFrameWithCursor& operator=(const DesktopFrameWithCursor&) = delete; + + DesktopRect cursor_rect() const { return cursor_rect_; } + + private: + const std::unique_ptr<DesktopFrame> original_frame_; + + DesktopVector restore_position_; + std::unique_ptr<DesktopFrame> restore_frame_; + DesktopRect cursor_rect_; +}; + +DesktopFrameWithCursor::DesktopFrameWithCursor( + std::unique_ptr<DesktopFrame> frame, + const MouseCursor& cursor, + const DesktopVector& position, + const DesktopRect& previous_cursor_rect, + bool cursor_changed) + : DesktopFrame(frame->size(), + frame->stride(), + frame->data(), + frame->shared_memory()), + original_frame_(std::move(frame)) { + MoveFrameInfoFrom(original_frame_.get()); + + DesktopVector image_pos = position.subtract(cursor.hotspot()); + cursor_rect_ = DesktopRect::MakeSize(cursor.image()->size()); + cursor_rect_.Translate(image_pos); + DesktopVector cursor_origin = cursor_rect_.top_left(); + cursor_rect_.IntersectWith(DesktopRect::MakeSize(size())); + + if (!previous_cursor_rect.equals(cursor_rect_)) { + mutable_updated_region()->AddRect(cursor_rect_); + // TODO(crbug:1323241) Update this code to properly handle the case where + // |previous_cursor_rect| is outside of the boundaries of |frame|. + // Any boundary check has to take into account the fact that + // |previous_cursor_rect| can be in DPI or in pixels, based on the platform + // we're running on. + mutable_updated_region()->AddRect(previous_cursor_rect); + } else if (cursor_changed) { + mutable_updated_region()->AddRect(cursor_rect_); + } + + if (cursor_rect_.is_empty()) + return; + + // Copy original screen content under cursor to `restore_frame_`. + restore_position_ = cursor_rect_.top_left(); + restore_frame_.reset(new BasicDesktopFrame(cursor_rect_.size())); + restore_frame_->CopyPixelsFrom(*this, cursor_rect_.top_left(), + DesktopRect::MakeSize(restore_frame_->size())); + + // Blit the cursor. + uint8_t* cursor_rect_data = + reinterpret_cast<uint8_t*>(data()) + cursor_rect_.top() * stride() + + cursor_rect_.left() * DesktopFrame::kBytesPerPixel; + DesktopVector origin_shift = cursor_rect_.top_left().subtract(cursor_origin); + AlphaBlend(cursor_rect_data, stride(), + cursor.image()->data() + + origin_shift.y() * cursor.image()->stride() + + origin_shift.x() * DesktopFrame::kBytesPerPixel, + cursor.image()->stride(), cursor_rect_.size()); +} + +DesktopFrameWithCursor::~DesktopFrameWithCursor() { + // Restore original content of the frame. + if (restore_frame_) { + DesktopRect target_rect = DesktopRect::MakeSize(restore_frame_->size()); + target_rect.Translate(restore_position_); + CopyPixelsFrom(restore_frame_->data(), restore_frame_->stride(), + target_rect); + } +} + +} // namespace + +DesktopAndCursorComposer::DesktopAndCursorComposer( + std::unique_ptr<DesktopCapturer> desktop_capturer, + const DesktopCaptureOptions& options) + : DesktopAndCursorComposer(desktop_capturer.release(), + MouseCursorMonitor::Create(options).release()) {} + +DesktopAndCursorComposer::DesktopAndCursorComposer( + DesktopCapturer* desktop_capturer, + MouseCursorMonitor* mouse_monitor) + : desktop_capturer_(desktop_capturer), mouse_monitor_(mouse_monitor) { + RTC_DCHECK(desktop_capturer_); +} + +DesktopAndCursorComposer::~DesktopAndCursorComposer() = default; + +std::unique_ptr<DesktopAndCursorComposer> +DesktopAndCursorComposer::CreateWithoutMouseCursorMonitor( + std::unique_ptr<DesktopCapturer> desktop_capturer) { + return std::unique_ptr<DesktopAndCursorComposer>( + new DesktopAndCursorComposer(desktop_capturer.release(), nullptr)); +} + +void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + if (mouse_monitor_) + mouse_monitor_->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION); + desktop_capturer_->Start(this); +} + +void DesktopAndCursorComposer::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + desktop_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void DesktopAndCursorComposer::CaptureFrame() { + if (mouse_monitor_) + mouse_monitor_->Capture(); + desktop_capturer_->CaptureFrame(); +} + +void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) { + desktop_capturer_->SetExcludedWindow(window); +} + +bool DesktopAndCursorComposer::GetSourceList(SourceList* sources) { + return desktop_capturer_->GetSourceList(sources); +} + +bool DesktopAndCursorComposer::SelectSource(SourceId id) { + return desktop_capturer_->SelectSource(id); +} + +bool DesktopAndCursorComposer::FocusOnSelectedSource() { + return desktop_capturer_->FocusOnSelectedSource(); +} + +bool DesktopAndCursorComposer::IsOccluded(const DesktopVector& pos) { + return desktop_capturer_->IsOccluded(pos); +} + +#if defined(WEBRTC_USE_GIO) +DesktopCaptureMetadata DesktopAndCursorComposer::GetMetadata() { + return desktop_capturer_->GetMetadata(); +} +#endif // defined(WEBRTC_USE_GIO) + +void DesktopAndCursorComposer::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) { + if (frame && cursor_) { + if (!frame->may_contain_cursor() && + frame->rect().Contains(cursor_position_) && + !desktop_capturer_->IsOccluded(cursor_position_)) { + DesktopVector relative_position = + cursor_position_.subtract(frame->top_left()); +#if defined(WEBRTC_MAC) || defined(CHROMEOS) + // On OSX, the logical(DIP) and physical coordinates are used mixingly. + // For example, the captured cursor has its size in physical pixels(2x) + // and location in logical(DIP) pixels on Retina monitor. This will cause + // problem when the desktop is mixed with Retina and non-Retina monitors. + // So we use DIP pixel for all location info and compensate with the scale + // factor of current frame to the `relative_position`. + const float scale = frame->scale_factor(); + relative_position.set(relative_position.x() * scale, + relative_position.y() * scale); +#endif + auto frame_with_cursor = std::make_unique<DesktopFrameWithCursor>( + std::move(frame), *cursor_, relative_position, previous_cursor_rect_, + cursor_changed_); + previous_cursor_rect_ = frame_with_cursor->cursor_rect(); + cursor_changed_ = false; + frame = std::move(frame_with_cursor); + frame->set_may_contain_cursor(true); + } + } + + callback_->OnCaptureResult(result, std::move(frame)); +} + +void DesktopAndCursorComposer::OnMouseCursor(MouseCursor* cursor) { + cursor_changed_ = true; + cursor_.reset(cursor); +} + +void DesktopAndCursorComposer::OnMouseCursorPosition( + const DesktopVector& position) { + cursor_position_ = position; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.h new file mode 100644 index 0000000000..a078b3eeef --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ + +#include <memory> +#if defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_metadata.h" +#endif // defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// A wrapper for DesktopCapturer that also captures mouse using specified +// MouseCursorMonitor and renders it on the generated streams. +class RTC_EXPORT DesktopAndCursorComposer + : public DesktopCapturer, + public DesktopCapturer::Callback, + public MouseCursorMonitor::Callback { + public: + // Creates a new composer that captures mouse cursor using + // MouseCursorMonitor::Create(options) and renders it into the frames + // generated by `desktop_capturer`. + DesktopAndCursorComposer(std::unique_ptr<DesktopCapturer> desktop_capturer, + const DesktopCaptureOptions& options); + + ~DesktopAndCursorComposer() override; + + DesktopAndCursorComposer(const DesktopAndCursorComposer&) = delete; + DesktopAndCursorComposer& operator=(const DesktopAndCursorComposer&) = delete; + + // Creates a new composer that relies on an external source for cursor shape + // and position information via the MouseCursorMonitor::Callback interface. + static std::unique_ptr<DesktopAndCursorComposer> + CreateWithoutMouseCursorMonitor( + std::unique_ptr<DesktopCapturer> desktop_capturer); + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; +#if defined(WEBRTC_USE_GIO) + DesktopCaptureMetadata GetMetadata() override; +#endif // defined(WEBRTC_USE_GIO) + + // MouseCursorMonitor::Callback interface. + void OnMouseCursor(MouseCursor* cursor) override; + void OnMouseCursorPosition(const DesktopVector& position) override; + + private: + // Allows test cases to use a fake MouseCursorMonitor implementation. + friend class DesktopAndCursorComposerTest; + + // Constructor to delegate both deprecated and new constructors and allows + // test cases to use a fake MouseCursorMonitor implementation. + DesktopAndCursorComposer(DesktopCapturer* desktop_capturer, + MouseCursorMonitor* mouse_monitor); + + // DesktopCapturer::Callback interface. + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override; + + const std::unique_ptr<DesktopCapturer> desktop_capturer_; + const std::unique_ptr<MouseCursorMonitor> mouse_monitor_; + + DesktopCapturer::Callback* callback_; + + std::unique_ptr<MouseCursor> cursor_; + DesktopVector cursor_position_; + DesktopRect previous_cursor_rect_; + bool cursor_changed_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc new file mode 100644 index 0000000000..c26dc208ac --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_and_cursor_composer.h" + +#include <stdint.h> +#include <string.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "rtc_base/arraysize.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using testing::ElementsAre; + +const int kFrameXCoord = 100; +const int kFrameYCoord = 200; +const int kScreenWidth = 100; +const int kScreenHeight = 100; +const int kCursorWidth = 10; +const int kCursorHeight = 10; + +const int kTestCursorSize = 3; +const uint32_t kTestCursorData[kTestCursorSize][kTestCursorSize] = { + { + 0xffffffff, + 0x99990000, + 0xaa222222, + }, + { + 0x88008800, + 0xaa0000aa, + 0xaa333333, + }, + { + 0x00000000, + 0xaa0000aa, + 0xaa333333, + }, +}; + +uint32_t GetFakeFramePixelValue(const DesktopVector& p) { + uint32_t r = 100 + p.x(); + uint32_t g = 100 + p.y(); + uint32_t b = 100 + p.x() + p.y(); + return b + (g << 8) + (r << 16) + 0xff000000; +} + +uint32_t GetFramePixel(const DesktopFrame& frame, const DesktopVector& pos) { + return *reinterpret_cast<uint32_t*>(frame.GetFrameDataAtPos(pos)); +} + +// Blends two pixel values taking into account alpha. +uint32_t BlendPixels(uint32_t dest, uint32_t src) { + uint8_t alpha = 255 - ((src & 0xff000000) >> 24); + uint32_t r = + ((dest & 0x00ff0000) >> 16) * alpha / 255 + ((src & 0x00ff0000) >> 16); + uint32_t g = + ((dest & 0x0000ff00) >> 8) * alpha / 255 + ((src & 0x0000ff00) >> 8); + uint32_t b = (dest & 0x000000ff) * alpha / 255 + (src & 0x000000ff); + return b + (g << 8) + (r << 16) + 0xff000000; +} + +DesktopFrame* CreateTestFrame(int width = kScreenWidth, + int height = kScreenHeight) { + DesktopFrame* frame = new BasicDesktopFrame(DesktopSize(width, height)); + uint32_t* data = reinterpret_cast<uint32_t*>(frame->data()); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + *(data++) = GetFakeFramePixelValue(DesktopVector(x, y)); + } + } + return frame; +} + +MouseCursor* CreateTestCursor(DesktopVector hotspot) { + std::unique_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight))); + uint32_t* data = reinterpret_cast<uint32_t*>(image->data()); + // Set four pixels near the hotspot and leave all other blank. + for (int y = 0; y < kTestCursorSize; ++y) { + for (int x = 0; x < kTestCursorSize; ++x) { + data[(hotspot.y() + y) * kCursorWidth + (hotspot.x() + x)] = + kTestCursorData[y][x]; + } + } + return new MouseCursor(image.release(), hotspot); +} + +class FakeScreenCapturer : public DesktopCapturer { + public: + FakeScreenCapturer() {} + + void Start(Callback* callback) override { callback_ = callback; } + + void CaptureFrame() override { + callback_->OnCaptureResult( + next_frame_ ? Result::SUCCESS : Result::ERROR_TEMPORARY, + std::move(next_frame_)); + } + + void SetNextFrame(std::unique_ptr<DesktopFrame> next_frame) { + next_frame_ = std::move(next_frame); + } + + bool IsOccluded(const DesktopVector& pos) override { return is_occluded_; } + + void set_is_occluded(bool value) { is_occluded_ = value; } + + private: + Callback* callback_ = nullptr; + + std::unique_ptr<DesktopFrame> next_frame_; + bool is_occluded_ = false; +}; + +class FakeMouseMonitor : public MouseCursorMonitor { + public: + FakeMouseMonitor() : changed_(true) {} + + void SetState(CursorState state, const DesktopVector& pos) { + state_ = state; + position_ = pos; + } + + void SetHotspot(const DesktopVector& hotspot) { + if (!hotspot_.equals(hotspot)) + changed_ = true; + hotspot_ = hotspot; + } + + void Init(Callback* callback, Mode mode) { callback_ = callback; } + + void Capture() override { + if (changed_) { + callback_->OnMouseCursor(CreateTestCursor(hotspot_)); + } + callback_->OnMouseCursorPosition(position_); + } + + private: + Callback* callback_; + CursorState state_; + DesktopVector position_; + DesktopVector hotspot_; + bool changed_; +}; + +void VerifyFrame(const DesktopFrame& frame, + MouseCursorMonitor::CursorState state, + const DesktopVector& pos) { + // Verify that all other pixels are set to their original values. + DesktopRect image_rect = + DesktopRect::MakeWH(kTestCursorSize, kTestCursorSize); + image_rect.Translate(pos); + + for (int y = 0; y < kScreenHeight; ++y) { + for (int x = 0; x < kScreenWidth; ++x) { + DesktopVector p(x, y); + if (state == MouseCursorMonitor::INSIDE && image_rect.Contains(p)) { + EXPECT_EQ(BlendPixels(GetFakeFramePixelValue(p), + kTestCursorData[y - pos.y()][x - pos.x()]), + GetFramePixel(frame, p)); + } else { + EXPECT_EQ(GetFakeFramePixelValue(p), GetFramePixel(frame, p)); + } + } + } +} + +} // namespace + +bool operator==(const DesktopRect& left, const DesktopRect& right) { + return left.equals(right); +} + +std::ostream& operator<<(std::ostream& out, const DesktopRect& rect) { + out << "{" << rect.left() << "+" << rect.top() << "-" << rect.width() << "x" + << rect.height() << "}"; + return out; +} + +class DesktopAndCursorComposerTest : public ::testing::Test, + public DesktopCapturer::Callback { + public: + explicit DesktopAndCursorComposerTest(bool include_cursor = true) + : fake_screen_(new FakeScreenCapturer()), + fake_cursor_(include_cursor ? new FakeMouseMonitor() : nullptr), + blender_(fake_screen_, fake_cursor_) { + blender_.Start(this); + } + + // DesktopCapturer::Callback interface + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override { + frame_ = std::move(frame); + } + + protected: + // Owned by `blender_`. + FakeScreenCapturer* fake_screen_; + FakeMouseMonitor* fake_cursor_; + + DesktopAndCursorComposer blender_; + std::unique_ptr<DesktopFrame> frame_; +}; + +class DesktopAndCursorComposerNoCursorMonitorTest + : public DesktopAndCursorComposerTest { + public: + DesktopAndCursorComposerNoCursorMonitorTest() + : DesktopAndCursorComposerTest(false) {} +}; + +TEST_F(DesktopAndCursorComposerTest, CursorShouldBeIgnoredIfNoFrameCaptured) { + struct { + int x, y; + int hotspot_x, hotspot_y; + bool inside; + } tests[] = { + {0, 0, 0, 0, true}, {50, 50, 0, 0, true}, {100, 50, 0, 0, true}, + {50, 100, 0, 0, true}, {100, 100, 0, 0, true}, {0, 0, 2, 5, true}, + {1, 1, 2, 5, true}, {50, 50, 2, 5, true}, {100, 100, 2, 5, true}, + {0, 0, 5, 2, true}, {50, 50, 5, 2, true}, {100, 100, 5, 2, true}, + {0, 0, 0, 0, false}, + }; + + for (size_t i = 0; i < arraysize(tests); i++) { + SCOPED_TRACE(i); + + DesktopVector hotspot(tests[i].hotspot_x, tests[i].hotspot_y); + fake_cursor_->SetHotspot(hotspot); + + MouseCursorMonitor::CursorState state = tests[i].inside + ? MouseCursorMonitor::INSIDE + : MouseCursorMonitor::OUTSIDE; + DesktopVector pos(tests[i].x, tests[i].y); + fake_cursor_->SetState(state, pos); + + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + + blender_.CaptureFrame(); + // If capturer captured nothing, then cursor should be ignored, not matter + // its state or position. + EXPECT_EQ(frame_, nullptr); + } +} + +TEST_F(DesktopAndCursorComposerTest, CursorShouldBeIgnoredIfFrameMayContainIt) { + // We can't use a shared frame because we need to detect modifications + // compared to a control. + std::unique_ptr<DesktopFrame> control_frame(CreateTestFrame()); + control_frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord)); + + struct { + int x; + int y; + bool may_contain_cursor; + } tests[] = { + {100, 200, true}, + {100, 200, false}, + {150, 250, true}, + {150, 250, false}, + }; + + for (size_t i = 0; i < arraysize(tests); i++) { + SCOPED_TRACE(i); + + std::unique_ptr<DesktopFrame> frame(CreateTestFrame()); + frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord)); + frame->set_may_contain_cursor(tests[i].may_contain_cursor); + fake_screen_->SetNextFrame(std::move(frame)); + + const DesktopVector abs_pos(tests[i].x, tests[i].y); + fake_cursor_->SetState(MouseCursorMonitor::INSIDE, abs_pos); + blender_.CaptureFrame(); + + // If the frame may already have contained the cursor, then `CaptureFrame()` + // should not have modified it, so it should be the same as the control. + EXPECT_TRUE(frame_); + const DesktopVector rel_pos(abs_pos.subtract(control_frame->top_left())); + if (tests[i].may_contain_cursor) { + EXPECT_EQ( + *reinterpret_cast<uint32_t*>(frame_->GetFrameDataAtPos(rel_pos)), + *reinterpret_cast<uint32_t*>( + control_frame->GetFrameDataAtPos(rel_pos))); + + } else { + // `CaptureFrame()` should have modified the frame to have the cursor. + EXPECT_NE( + *reinterpret_cast<uint32_t*>(frame_->GetFrameDataAtPos(rel_pos)), + *reinterpret_cast<uint32_t*>( + control_frame->GetFrameDataAtPos(rel_pos))); + EXPECT_TRUE(frame_->may_contain_cursor()); + } + } +} + +TEST_F(DesktopAndCursorComposerTest, + CursorShouldBeIgnoredIfItIsOutOfDesktopFrame) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord)); + // The frame covers (100, 200) - (200, 300). + + struct { + int x; + int y; + } tests[] = { + {0, 0}, {50, 50}, {50, 150}, {100, 150}, {50, 200}, + {99, 200}, {100, 199}, {200, 300}, {200, 299}, {199, 300}, + {-1, -1}, {-10000, -10000}, {10000, 10000}, + }; + for (size_t i = 0; i < arraysize(tests); i++) { + SCOPED_TRACE(i); + + fake_screen_->SetNextFrame(frame->Share()); + // The CursorState is ignored when using absolute cursor position. + fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE, + DesktopVector(tests[i].x, tests[i].y)); + blender_.CaptureFrame(); + VerifyFrame(*frame_, MouseCursorMonitor::OUTSIDE, DesktopVector(0, 0)); + } +} + +TEST_F(DesktopAndCursorComposerTest, IsOccludedShouldBeConsidered) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord)); + // The frame covers (100, 200) - (200, 300). + + struct { + int x; + int y; + } tests[] = { + {100, 200}, {101, 200}, {100, 201}, {101, 201}, {150, 250}, {199, 299}, + }; + fake_screen_->set_is_occluded(true); + for (size_t i = 0; i < arraysize(tests); i++) { + SCOPED_TRACE(i); + + fake_screen_->SetNextFrame(frame->Share()); + // The CursorState is ignored when using absolute cursor position. + fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE, + DesktopVector(tests[i].x, tests[i].y)); + blender_.CaptureFrame(); + VerifyFrame(*frame_, MouseCursorMonitor::OUTSIDE, DesktopVector()); + } +} + +TEST_F(DesktopAndCursorComposerTest, CursorIncluded) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord)); + // The frame covers (100, 200) - (200, 300). + + struct { + int x; + int y; + } tests[] = { + {100, 200}, {101, 200}, {100, 201}, {101, 201}, {150, 250}, {199, 299}, + }; + for (size_t i = 0; i < arraysize(tests); i++) { + SCOPED_TRACE(i); + + const DesktopVector abs_pos(tests[i].x, tests[i].y); + const DesktopVector rel_pos(abs_pos.subtract(frame->top_left())); + + fake_screen_->SetNextFrame(frame->Share()); + // The CursorState is ignored when using absolute cursor position. + fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE, abs_pos); + blender_.CaptureFrame(); + VerifyFrame(*frame_, MouseCursorMonitor::INSIDE, rel_pos); + + // Verify that the cursor is erased before the frame buffer is returned to + // the screen capturer. + frame_.reset(); + VerifyFrame(*frame, MouseCursorMonitor::OUTSIDE, DesktopVector()); + } +} + +TEST_F(DesktopAndCursorComposerNoCursorMonitorTest, + UpdatedRegionIncludesOldAndNewCursorRectsIfMoved) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + DesktopRect first_cursor_rect; + { + // Block to scope test_cursor, which is invalidated by OnMouseCursor. + MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0)); + first_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size()); + blender_.OnMouseCursor(test_cursor); + } + blender_.OnMouseCursorPosition(DesktopVector(0, 0)); + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + + DesktopVector cursor_move_offset(1, 1); + DesktopRect second_cursor_rect = first_cursor_rect; + second_cursor_rect.Translate(cursor_move_offset); + blender_.OnMouseCursorPosition(cursor_move_offset); + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + + EXPECT_TRUE(frame->updated_region().is_empty()); + DesktopRegion expected_region; + expected_region.AddRect(first_cursor_rect); + expected_region.AddRect(second_cursor_rect); + EXPECT_TRUE(frame_->updated_region().Equals(expected_region)); +} + +TEST_F(DesktopAndCursorComposerNoCursorMonitorTest, + UpdatedRegionIncludesOldAndNewCursorRectsIfShapeChanged) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + DesktopRect first_cursor_rect; + { + // Block to scope test_cursor, which is invalidated by OnMouseCursor. + MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0)); + first_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size()); + blender_.OnMouseCursor(test_cursor); + } + blender_.OnMouseCursorPosition(DesktopVector(0, 0)); + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + + // Create a second cursor, the same shape as the first. Since the code doesn't + // compare the cursor pixels, this is sufficient, and avoids needing two test + // cursor bitmaps. + DesktopRect second_cursor_rect; + { + MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0)); + second_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size()); + blender_.OnMouseCursor(test_cursor); + } + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + + EXPECT_TRUE(frame->updated_region().is_empty()); + DesktopRegion expected_region; + expected_region.AddRect(first_cursor_rect); + expected_region.AddRect(second_cursor_rect); + EXPECT_TRUE(frame_->updated_region().Equals(expected_region)); +} + +TEST_F(DesktopAndCursorComposerNoCursorMonitorTest, + UpdatedRegionUnchangedIfCursorUnchanged) { + std::unique_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + blender_.OnMouseCursor(CreateTestCursor(DesktopVector(0, 0))); + blender_.OnMouseCursorPosition(DesktopVector(0, 0)); + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + fake_screen_->SetNextFrame(frame->Share()); + blender_.CaptureFrame(); + + EXPECT_TRUE(frame->updated_region().is_empty()); + EXPECT_TRUE(frame_->updated_region().is_empty()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_differ_sse2_gn/moz.build b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_differ_sse2_gn/moz.build new file mode 100644 index 0000000000..1d9b39c90e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_differ_sse2_gn/moz.build @@ -0,0 +1,142 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### + ### DO NOT edit it by hand. ### + +COMPILE_FLAGS["OS_INCLUDES"] = [] +AllowCompilerWarnings() + +DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1" +DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True +DEFINES["RTC_ENABLE_VP9"] = True +DEFINES["WEBRTC_ENABLE_AVX2"] = True +DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0" +DEFINES["WEBRTC_LIBRARY_IMPL"] = True +DEFINES["WEBRTC_MOZILLA_BUILD"] = True +DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0" + +FINAL_LIBRARY = "webrtc" + + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/ipc/chromium/src", + "/third_party/libwebrtc/", + "/third_party/libwebrtc/third_party/abseil-cpp/", + "/tools/profiler/public" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.cc" +] + +if not CONFIG["MOZ_DEBUG"]: + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0" + DEFINES["NDEBUG"] = True + DEFINES["NVALGRIND"] = True + +if CONFIG["MOZ_DEBUG"] == "1": + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1" + +if CONFIG["OS_TARGET"] == "Darwin": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["WEBRTC_MAC"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True + DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0" + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "Linux": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["USE_AURA"] = "1" + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_NSS_CERTS"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_UDEV"] = True + DEFINES["WEBRTC_LINUX"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_GNU_SOURCE"] = True + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "OpenBSD": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_BSD"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True + DEFINES["NOMINMAX"] = True + DEFINES["NTDDI_VERSION"] = "0x0A000000" + DEFINES["PSAPI_VERSION"] = "2" + DEFINES["UNICODE"] = True + DEFINES["USE_AURA"] = "1" + DEFINES["WEBRTC_WIN"] = True + DEFINES["WIN32"] = True + DEFINES["WIN32_LEAN_AND_MEAN"] = True + DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP" + DEFINES["WINVER"] = "0x0A00" + DEFINES["_ATL_NO_OPENGL"] = True + DEFINES["_CRT_RAND_S"] = True + DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True + DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True + DEFINES["_HAS_EXCEPTIONS"] = "0" + DEFINES["_HAS_NODISCARD"] = True + DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True + DEFINES["_SECURE_ATL"] = True + DEFINES["_UNICODE"] = True + DEFINES["_WIN32_WINNT"] = "0x0A00" + DEFINES["_WINDOWS"] = True + DEFINES["__STD_C"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0" + +if CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +Library("desktop_capture_differ_sse2_gn") diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_gn/moz.build b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_gn/moz.build new file mode 100644 index 0000000000..8631744ab1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_gn/moz.build @@ -0,0 +1,439 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### + ### DO NOT edit it by hand. ### +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +COMPILE_FLAGS["OS_INCLUDES"] = [] +AllowCompilerWarnings() + +DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1" +DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True +DEFINES["RTC_ENABLE_VP9"] = True +DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0" +DEFINES["WEBRTC_LIBRARY_IMPL"] = True +DEFINES["WEBRTC_MOZILLA_BUILD"] = True +DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0" + +FINAL_LIBRARY = "webrtc" + + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/ipc/chromium/src", + "/media/libyuv/", + "/media/libyuv/libyuv/include/", + "/third_party/libwebrtc/", + "/third_party/libwebrtc/third_party/abseil-cpp/", + "/third_party/pipewire/", + "/tools/profiler/public" +] + +SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/blank_detector_desktop_capturer_wrapper.cc", + "/third_party/libwebrtc/modules/desktop_capture/cropped_desktop_frame.cc", + "/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.cc", + "/third_party/libwebrtc/modules/desktop_capture/differ_block.cc", + "/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.cc", + "/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.cc", + "/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.cc", + "/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.cc", + "/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.cc", + "/third_party/libwebrtc/modules/desktop_capture/rgba_color.cc", + "/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.cc", + "/third_party/libwebrtc/modules/desktop_capture/window_finder.cc" +] + +if not CONFIG["MOZ_DEBUG"]: + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0" + DEFINES["NDEBUG"] = True + DEFINES["NVALGRIND"] = True + +if CONFIG["MOZ_DEBUG"] == "1": + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1" + +if CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_MAC"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True + DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0" + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc", + "/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc", + "/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc" + ] + +if CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_AURA"] = "1" + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_NSS_CERTS"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_UDEV"] = True + DEFINES["WEBRTC_LINUX"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + + OS_LIBS += [ + "rt" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc", + "/third_party/libwebrtc/modules/desktop_capture/screen_capturer_linux.cc", + "/third_party/libwebrtc/modules/desktop_capture/window_capturer_linux.cc" + ] + +if CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_BSD"] = True + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["WEBRTC_USE_X11"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc", + "/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc", + "/third_party/libwebrtc/modules/desktop_capture/screen_capturer_linux.cc", + "/third_party/libwebrtc/modules/desktop_capture/window_capturer_linux.cc" + ] + +if CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True + DEFINES["NOMINMAX"] = True + DEFINES["NTDDI_VERSION"] = "0x0A000000" + DEFINES["PSAPI_VERSION"] = "2" + DEFINES["UNICODE"] = True + DEFINES["USE_AURA"] = "1" + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_WIN"] = True + DEFINES["WIN32"] = True + DEFINES["WIN32_LEAN_AND_MEAN"] = True + DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP" + DEFINES["WINVER"] = "0x0A00" + DEFINES["_ATL_NO_OPENGL"] = True + DEFINES["_CRT_RAND_S"] = True + DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True + DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True + DEFINES["_HAS_EXCEPTIONS"] = "0" + DEFINES["_HAS_NODISCARD"] = True + DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True + DEFINES["_SECURE_ATL"] = True + DEFINES["_UNICODE"] = True + DEFINES["_WIN32_WINNT"] = "0x0A00" + DEFINES["_WINDOWS"] = True + DEFINES["__STD_C"] = True + + OS_LIBS += [ + "crypt32", + "d3d11", + "dxgi", + "iphlpapi", + "secur32", + "winmm" + ] + + SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/cropping_window_capturer_win.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.cc", + "/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc", + "/third_party/libwebrtc/modules/desktop_capture/screen_capturer_win.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc", + "/third_party/libwebrtc/modules/desktop_capture/window_capturer_win.cc", + "/third_party/libwebrtc/modules/desktop_capture/window_finder_win.cc" + ] + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "arm": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + DEFINES["WEBRTC_ARCH_ARM"] = True + DEFINES["WEBRTC_ARCH_ARM_V7"] = True + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + DEFINES["WEBRTC_USE_PIPEWIRE"] = True + DEFINES["_GNU_SOURCE"] = True + + SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc" + ] + +if CONFIG["CPU_ARCH"] == "ppc64": + + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_USE_X11"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc" + ] + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0" + +if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_USE_PIPEWIRE"] = True + DEFINES["_GNU_SOURCE"] = True + + SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc" + ] + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_USE_PIPEWIRE"] = True + DEFINES["_GNU_SOURCE"] = True + + SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc" + ] + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_USE_PIPEWIRE"] = True + DEFINES["_GNU_SOURCE"] = True + + SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc" + ] + +if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_USE_X11"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc" + ] + +if CONFIG["CPU_ARCH"] == "arm" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_USE_X11"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc" + ] + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_USE_X11"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc" + ] + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_USE_X11"] = True + + OS_LIBS += [ + "X11", + "Xcomposite", + "Xdamage", + "Xext", + "Xfixes", + "Xrandr", + "Xrender", + "Xtst" + ] + + UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc", + "/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc" + ] + +Library("desktop_capture_gn") diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metadata.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metadata.h new file mode 100644 index 0000000000..faca156e33 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metadata.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_ + +#if defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/linux/wayland/xdg_session_details.h" +#endif // defined(WEBRTC_USE_GIO) + +namespace webrtc { + +// Container for the metadata associated with a desktop capturer. +struct DesktopCaptureMetadata { +#if defined(WEBRTC_USE_GIO) + // Details about the XDG desktop session handle (used by wayland + // implementation in remoting) + xdg_portal::SessionDetails session_details; +#endif // defined(WEBRTC_USE_GIO) +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.cc new file mode 100644 index 0000000000..6b741ef4bb --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.cc @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { +namespace { +// This enum is logged via UMA so entries should not be reordered or have their +// values changed. This should also be kept in sync with the values in the +// DesktopCapturerId namespace. +enum class SequentialDesktopCapturerId { + kUnknown = 0, + kWgcCapturerWin = 1, + kScreenCapturerWinMagnifier = 2, + kWindowCapturerWinGdi = 3, + kScreenCapturerWinGdi = 4, + kScreenCapturerWinDirectx = 5, + kMaxValue = kScreenCapturerWinDirectx +}; +} // namespace + +void RecordCapturerImpl(uint32_t capturer_id) { + SequentialDesktopCapturerId sequential_id; + switch (capturer_id) { + case DesktopCapturerId::kWgcCapturerWin: + sequential_id = SequentialDesktopCapturerId::kWgcCapturerWin; + break; + case DesktopCapturerId::kScreenCapturerWinMagnifier: + sequential_id = SequentialDesktopCapturerId::kScreenCapturerWinMagnifier; + break; + case DesktopCapturerId::kWindowCapturerWinGdi: + sequential_id = SequentialDesktopCapturerId::kWindowCapturerWinGdi; + break; + case DesktopCapturerId::kScreenCapturerWinGdi: + sequential_id = SequentialDesktopCapturerId::kScreenCapturerWinGdi; + break; + case DesktopCapturerId::kScreenCapturerWinDirectx: + sequential_id = SequentialDesktopCapturerId::kScreenCapturerWinDirectx; + break; + case DesktopCapturerId::kUnknown: + default: + sequential_id = SequentialDesktopCapturerId::kUnknown; + } + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.DesktopCapture.Win.DesktopCapturerImpl", + static_cast<int>(sequential_id), + static_cast<int>(SequentialDesktopCapturerId::kMaxValue)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.h new file mode 100644 index 0000000000..37542b84bb --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_metrics_helper.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_ + +#include <stdint.h> + +namespace webrtc { + +void RecordCapturerImpl(uint32_t capturer_id); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_objc_gn/moz.build b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_objc_gn/moz.build new file mode 100644 index 0000000000..e884abb54d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_objc_gn/moz.build @@ -0,0 +1,72 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### + ### DO NOT edit it by hand. ### + +CMMFLAGS += [ + "-fobjc-arc" +] + +COMPILE_FLAGS["OS_INCLUDES"] = [] +AllowCompilerWarnings() + +DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1" +DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True +DEFINES["RTC_ENABLE_VP9"] = True +DEFINES["WEBRTC_ENABLE_AVX2"] = True +DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0" +DEFINES["WEBRTC_LIBRARY_IMPL"] = True +DEFINES["WEBRTC_MAC"] = True +DEFINES["WEBRTC_MOZILLA_BUILD"] = True +DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0" +DEFINES["WEBRTC_POSIX"] = True +DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True +DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0" +DEFINES["__STDC_CONSTANT_MACROS"] = True +DEFINES["__STDC_FORMAT_MACROS"] = True + +FINAL_LIBRARY = "webrtc" + + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/ipc/chromium/src", + "/third_party/libwebrtc/", + "/third_party/libwebrtc/sdk/objc/", + "/third_party/libwebrtc/sdk/objc/base/", + "/third_party/libwebrtc/third_party/abseil-cpp/", + "/tools/profiler/public" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm", + "/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm", + "/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm", + "/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm", + "/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm", + "/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm", + "/third_party/libwebrtc/modules/desktop_capture/screen_capturer_darwin.mm", + "/third_party/libwebrtc/modules/desktop_capture/window_capturer_mac.mm", + "/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.mm" +] + +if not CONFIG["MOZ_DEBUG"]: + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0" + DEFINES["NDEBUG"] = True + DEFINES["NVALGRIND"] = True + +if CONFIG["MOZ_DEBUG"] == "1": + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1" + DEFINES["_DEBUG"] = True + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +Library("desktop_capture_objc_gn") diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.cc new file mode 100644 index 0000000000..767dbfefa5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.cc @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capture_options.h" + +#include "api/make_ref_counted.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" +#elif defined(WEBRTC_WIN) +#include "modules/desktop_capture/win/full_screen_win_application_handler.h" +#endif +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) +#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" +#endif + +namespace webrtc { + +DesktopCaptureOptions::DesktopCaptureOptions() {} +DesktopCaptureOptions::DesktopCaptureOptions( + const DesktopCaptureOptions& options) = default; +DesktopCaptureOptions::DesktopCaptureOptions(DesktopCaptureOptions&& options) = + default; +DesktopCaptureOptions::~DesktopCaptureOptions() {} + +DesktopCaptureOptions& DesktopCaptureOptions::operator=( + const DesktopCaptureOptions& options) = default; +DesktopCaptureOptions& DesktopCaptureOptions::operator=( + DesktopCaptureOptions&& options) = default; + +// static +DesktopCaptureOptions DesktopCaptureOptions::CreateDefault() { + DesktopCaptureOptions result; +#if defined(WEBRTC_USE_X11) + result.set_x_display(SharedXDisplay::CreateDefault()); +#endif +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) + result.set_screencast_stream(SharedScreenCastStream::CreateDefault()); +#endif +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + result.set_configuration_monitor( + rtc::make_ref_counted<DesktopConfigurationMonitor>()); + result.set_full_screen_window_detector( + rtc::make_ref_counted<FullScreenWindowDetector>( + CreateFullScreenMacApplicationHandler)); +#elif defined(WEBRTC_WIN) + result.set_full_screen_window_detector( + rtc::make_ref_counted<FullScreenWindowDetector>( + CreateFullScreenWinApplicationHandler)); +#endif + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.h new file mode 100644 index 0000000000..c11efdb615 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_options.h @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ + +#include "api/scoped_refptr.h" +#include "rtc_base/system/rtc_export.h" + +#if defined(WEBRTC_USE_X11) +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#endif + +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) +#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#endif + +#include "modules/desktop_capture/full_screen_window_detector.h" + +namespace webrtc { + +// An object that stores initialization parameters for screen and window +// capturers. +class RTC_EXPORT DesktopCaptureOptions { + public: + // Returns instance of DesktopCaptureOptions with default parameters. On Linux + // also initializes X window connection. x_display() will be set to null if + // X11 connection failed (e.g. DISPLAY isn't set). + static DesktopCaptureOptions CreateDefault(); + + DesktopCaptureOptions(); + DesktopCaptureOptions(const DesktopCaptureOptions& options); + DesktopCaptureOptions(DesktopCaptureOptions&& options); + ~DesktopCaptureOptions(); + + DesktopCaptureOptions& operator=(const DesktopCaptureOptions& options); + DesktopCaptureOptions& operator=(DesktopCaptureOptions&& options); + +#if defined(WEBRTC_USE_X11) + const rtc::scoped_refptr<SharedXDisplay>& x_display() const { + return x_display_; + } + void set_x_display(rtc::scoped_refptr<SharedXDisplay> x_display) { + x_display_ = x_display; + } +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + // TODO(zijiehe): Remove both DesktopConfigurationMonitor and + // FullScreenChromeWindowDetector out of DesktopCaptureOptions. It's not + // reasonable for external consumers to set these two parameters. + const rtc::scoped_refptr<DesktopConfigurationMonitor>& configuration_monitor() + const { + return configuration_monitor_; + } + // If nullptr is set, ScreenCapturer won't work and WindowCapturer may return + // inaccurate result from IsOccluded() function. + void set_configuration_monitor( + rtc::scoped_refptr<DesktopConfigurationMonitor> m) { + configuration_monitor_ = m; + } + + bool allow_iosurface() const { return allow_iosurface_; } + void set_allow_iosurface(bool allow) { allow_iosurface_ = allow; } +#endif + + const rtc::scoped_refptr<FullScreenWindowDetector>& + full_screen_window_detector() const { + return full_screen_window_detector_; + } + void set_full_screen_window_detector( + rtc::scoped_refptr<FullScreenWindowDetector> detector) { + full_screen_window_detector_ = detector; + } + + // Flag indicating that the capturer should use screen change notifications. + // Enables/disables use of XDAMAGE in the X11 capturer. + bool use_update_notifications() const { return use_update_notifications_; } + void set_use_update_notifications(bool use_update_notifications) { + use_update_notifications_ = use_update_notifications; + } + + // Flag indicating if desktop effects (e.g. Aero) should be disabled when the + // capturer is active. Currently used only on Windows. + bool disable_effects() const { return disable_effects_; } + void set_disable_effects(bool disable_effects) { + disable_effects_ = disable_effects; + } + + // Flag that should be set if the consumer uses updated_region() and the + // capturer should try to provide correct updated_region() for the frames it + // generates (e.g. by comparing each frame with the previous one). + bool detect_updated_region() const { return detect_updated_region_; } + void set_detect_updated_region(bool detect_updated_region) { + detect_updated_region_ = detect_updated_region; + } + +#if defined(WEBRTC_WIN) + // Enumerating windows owned by the current process on Windows has some + // complications due to |GetWindowText*()| APIs potentially causing a + // deadlock (see the comments in the `GetWindowListHandler()` function in + // window_capture_utils.cc for more details on the deadlock). + // To avoid this issue, consumers can either ensure that the thread that runs + // their message loop never waits on `GetSourceList()`, or they can set this + // flag to false which will prevent windows running in the current process + // from being enumerated and included in the results. Consumers can still + // provide the WindowId for their own windows to `SelectSource()` and capture + // them. + bool enumerate_current_process_windows() const { + return enumerate_current_process_windows_; + } + void set_enumerate_current_process_windows( + bool enumerate_current_process_windows) { + enumerate_current_process_windows_ = enumerate_current_process_windows; + } + + bool allow_use_magnification_api() const { + return allow_use_magnification_api_; + } + void set_allow_use_magnification_api(bool allow) { + allow_use_magnification_api_ = allow; + } + // Allowing directx based capturer or not, this capturer works on windows 7 + // with platform update / windows 8 or upper. + bool allow_directx_capturer() const { return allow_directx_capturer_; } + void set_allow_directx_capturer(bool enabled) { + allow_directx_capturer_ = enabled; + } + + // Flag that may be set to allow use of the cropping window capturer (which + // captures the screen & crops that to the window region in some cases). An + // advantage of using this is significantly higher capture frame rates than + // capturing the window directly. A disadvantage of using this is the + // possibility of capturing unrelated content (e.g. overlapping windows that + // aren't detected properly, or neighboring regions when moving/resizing the + // captured window). Note: this flag influences the behavior of calls to + // DesktopCapturer::CreateWindowCapturer; calls to + // CroppingWindowCapturer::CreateCapturer ignore the flag (treat it as true). + bool allow_cropping_window_capturer() const { + return allow_cropping_window_capturer_; + } + void set_allow_cropping_window_capturer(bool allow) { + allow_cropping_window_capturer_ = allow; + } + +#if defined(RTC_ENABLE_WIN_WGC) + // This flag enables the WGC capturer for both window and screen capture. + // This capturer should offer similar or better performance than the cropping + // capturer without the disadvantages listed above. However, the WGC capturer + // is only available on Windows 10 version 1809 (Redstone 5) and up. This flag + // will have no affect on older versions. + // If set, and running a supported version of Win10, this flag will take + // precedence over the cropping, directx, and magnification flags. + bool allow_wgc_capturer() const { return allow_wgc_capturer_; } + void set_allow_wgc_capturer(bool allow) { allow_wgc_capturer_ = allow; } + + // This flag enables the WGC capturer for fallback capturer. + // The flag is useful when the first capturer (eg. WindowCapturerWinGdi) is + // unreliable in certain devices where WGC is supported, but not used by + // default. + bool allow_wgc_capturer_fallback() const { + return allow_wgc_capturer_fallback_; + } + void set_allow_wgc_capturer_fallback(bool allow) { + allow_wgc_capturer_fallback_ = allow; + } +#endif // defined(RTC_ENABLE_WIN_WGC) +#endif // defined(WEBRTC_WIN) + +#if defined(WEBRTC_USE_PIPEWIRE) + bool allow_pipewire() const { return allow_pipewire_; } + void set_allow_pipewire(bool allow) { allow_pipewire_ = allow; } + +#if !defined(WEBRTC_MOZILLA_BUILD) + const rtc::scoped_refptr<SharedScreenCastStream>& screencast_stream() const { + return screencast_stream_; + } + void set_screencast_stream( + rtc::scoped_refptr<SharedScreenCastStream> stream) { + screencast_stream_ = stream; + } + + void set_width(uint32_t width) { width_ = width; } + uint32_t get_width() const { return width_; } + + void set_height(uint32_t height) { height_ = height; } + uint32_t get_height() const { return height_; } +#endif +#endif + + private: +#if defined(WEBRTC_USE_X11) + rtc::scoped_refptr<SharedXDisplay> x_display_; +#endif +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) + // An instance of shared PipeWire ScreenCast stream we share between + // BaseCapturerPipeWire and MouseCursorMonitorPipeWire as cursor information + // is sent together with screen content. + rtc::scoped_refptr<SharedScreenCastStream> screencast_stream_; +#endif +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; + bool allow_iosurface_ = false; +#endif + + rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_; + +#if defined(WEBRTC_WIN) + bool enumerate_current_process_windows_ = true; + bool allow_use_magnification_api_ = false; + bool allow_directx_capturer_ = false; + bool allow_cropping_window_capturer_ = false; +#if defined(RTC_ENABLE_WIN_WGC) + bool allow_wgc_capturer_ = false; + bool allow_wgc_capturer_fallback_ = false; +#endif +#endif +#if defined(WEBRTC_USE_X11) + bool use_update_notifications_ = false; +#else + bool use_update_notifications_ = true; +#endif + bool disable_effects_ = true; + bool detect_updated_region_ = false; +#if defined(WEBRTC_USE_PIPEWIRE) + bool allow_pipewire_ = false; + uint32_t width_ = 0; + uint32_t height_ = 0; +#endif +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capture_types.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_types.h new file mode 100644 index 0000000000..94be2fb68b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capture_types.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ + +#ifndef XP_WIN +#include <sys/types.h> // pid_t +#endif +#include <stdint.h> + +#ifdef XP_WIN // Moving this into the global namespace +typedef int pid_t; // matching what used to be in +#endif // video_capture_defines.h + +namespace webrtc { + +enum class CaptureType { kWindow, kScreen }; + +// Type used to identify windows on the desktop. Values are platform-specific: +// - On Windows: HWND cast to intptr_t. +// - On Linux (with X11): X11 Window (unsigned long) type cast to intptr_t. +// - On OSX: integer window number. +typedef intptr_t WindowId; + +const WindowId kNullWindowId = 0; + +// Type used to identify screens on the desktop. Values are platform-specific: +// - On Windows: integer display device index. +// - On OSX: CGDirectDisplayID cast to intptr_t. +// - On Linux (with X11): TBD. +// - On ChromeOS: display::Display::id() is an int64_t. +// On Windows, ScreenId is implementation dependent: sending a ScreenId from one +// implementation to another usually won't work correctly. +#if defined(CHROMEOS) + typedef int64_t ScreenId; +#else + typedef intptr_t ScreenId; +#endif + +// The screen id corresponds to all screen combined together. +const ScreenId kFullDesktopScreenId = -1; + +const ScreenId kInvalidScreenId = -2; + +typedef intptr_t ProcessId; +const ProcessId DesktopProcessId = 0; + +// Integers to attach to each DesktopFrame to differentiate the generator of +// the frame. The entries in this namespace should remain in sync with the +// SequentialDesktopCapturerId enum, which is logged via UMA. +// `kScreenCapturerWinGdi` and `kScreenCapturerWinDirectx` values are preserved +// to maintain compatibility +namespace DesktopCapturerId { +constexpr uint32_t CreateFourCC(char a, char b, char c, char d) { + return ((static_cast<uint32_t>(a)) | (static_cast<uint32_t>(b) << 8) | + (static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24)); +} + +constexpr uint32_t kUnknown = 0; +constexpr uint32_t kWgcCapturerWin = 1; +constexpr uint32_t kScreenCapturerWinMagnifier = 2; +constexpr uint32_t kWindowCapturerWinGdi = 3; +constexpr uint32_t kScreenCapturerWinGdi = CreateFourCC('G', 'D', 'I', ' '); +constexpr uint32_t kScreenCapturerWinDirectx = CreateFourCC('D', 'X', 'G', 'I'); +} // namespace DesktopCapturerId + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.cc new file mode 100644 index 0000000000..9022652ec2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer.h" + +#include <stdlib.h> +#include <string.h> + +#include <cstring> +#include <utility> + +#include "modules/desktop_capture/cropping_window_capturer.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" + +#if defined(RTC_ENABLE_WIN_WGC) +#include "modules/desktop_capture/win/wgc_capturer_win.h" +#include "rtc_base/win/windows_version.h" +#endif // defined(RTC_ENABLE_WIN_WGC) + +#if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) +#include <gtk/gtk.h> +#include <gtk/gtkx.h> +#endif + +namespace webrtc { + +DesktopCapturer::~DesktopCapturer() = default; + +void DesktopCapturer::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {} + +void DesktopCapturer::SetExcludedWindow(WindowId window) {} + +bool DesktopCapturer::GetSourceList(SourceList* sources) { + return true; +} + +bool DesktopCapturer::SelectSource(SourceId id) { + return false; +} + +bool DesktopCapturer::FocusOnSelectedSource() { + return false; +} + +bool DesktopCapturer::IsOccluded(const DesktopVector& pos) { + return false; +} + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateWindowCapturer( + const DesktopCaptureOptions& options) { +#if defined(RTC_ENABLE_WIN_WGC) + if (options.allow_wgc_capturer() && IsWgcSupported(CaptureType::kWindow)) { + return WgcCapturerWin::CreateRawWindowCapturer(options); + } +#endif // defined(RTC_ENABLE_WIN_WGC) + +#if defined(WEBRTC_WIN) + if (options.allow_cropping_window_capturer()) { + return CroppingWindowCapturer::CreateCapturer(options); + } +#endif // defined(WEBRTC_WIN) + + std::unique_ptr<DesktopCapturer> capturer = CreateRawWindowCapturer(options); + if (capturer && options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateScreenCapturer( + const DesktopCaptureOptions& options) { +#if defined(RTC_ENABLE_WIN_WGC) + if (options.allow_wgc_capturer() && IsWgcSupported(CaptureType::kScreen)) { + return WgcCapturerWin::CreateRawScreenCapturer(options); + } +#endif // defined(RTC_ENABLE_WIN_WGC) + + std::unique_ptr<DesktopCapturer> capturer = CreateRawScreenCapturer(options); + if (capturer && options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateTabCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr<DesktopCapturer> capturer = CreateRawTabCapturer(options); + if (capturer && options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} + +#if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) +bool DesktopCapturer::IsRunningUnderWayland() { + const char* xdg_session_type = getenv("XDG_SESSION_TYPE"); + if (!xdg_session_type || strncmp(xdg_session_type, "wayland", 7) != 0) + return false; + + if (!(getenv("WAYLAND_DISPLAY"))) + return false; + + return true; +} +#endif // defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.h new file mode 100644 index 0000000000..6e9b95e214 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <string> +#include <type_traits> +#include <vector> + +#if defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_metadata.h" +#endif // defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class DesktopCaptureOptions; +class DesktopFrame; + +// Abstract interface for screen and window capturers. +class RTC_EXPORT DesktopCapturer { + public: + enum class Result { + // The frame was captured successfully. + SUCCESS, + + // There was a temporary error. The caller should continue calling + // CaptureFrame(), in the expectation that it will eventually recover. + ERROR_TEMPORARY, + + // Capture has failed and will keep failing if the caller tries calling + // CaptureFrame() again. + ERROR_PERMANENT, + + MAX_VALUE = ERROR_PERMANENT + }; + + // Interface that must be implemented by the DesktopCapturer consumers. + class Callback { + public: + // Called after a frame has been captured. `frame` is not nullptr if and + // only if `result` is SUCCESS. + virtual void OnCaptureResult(Result result, + std::unique_ptr<DesktopFrame> frame) = 0; + + protected: + virtual ~Callback() {} + }; + +#if defined(CHROMEOS) + typedef int64_t SourceId; +#else + typedef intptr_t SourceId; +#endif + + static_assert(std::is_same<SourceId, ScreenId>::value, + "SourceId should be a same type as ScreenId."); + + struct Source { + // The unique id to represent a Source of current DesktopCapturer. + SourceId id; + pid_t pid; + + // Title of the window or screen in UTF-8 encoding, maybe empty. This field + // should not be used to identify a source. + std::string title; + }; + + typedef std::vector<Source> SourceList; + + virtual ~DesktopCapturer(); + + // Called at the beginning of a capturing session. `callback` must remain + // valid until capturer is destroyed. + virtual void Start(Callback* callback) = 0; + + // Sets SharedMemoryFactory that will be used to create buffers for the + // captured frames. The factory can be invoked on a thread other than the one + // where CaptureFrame() is called. It will be destroyed on the same thread. + // Shared memory is currently supported only by some DesktopCapturer + // implementations. + virtual void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory); + + // Captures next frame, and involve callback provided by Start() function. + // Pending capture requests are canceled when DesktopCapturer is deleted. + virtual void CaptureFrame() = 0; + + // Sets the window to be excluded from the captured image in the future + // Capture calls. Used to exclude the screenshare notification window for + // screen capturing. + virtual void SetExcludedWindow(WindowId window); + + // TODO(zijiehe): Following functions should be pure virtual. The default + // implementations are for backward compatibility only. Remove default + // implementations once all DesktopCapturer implementations in Chromium have + // implemented these functions. + + // Gets a list of sources current capturer supports. Returns false in case of + // a failure. + // For DesktopCapturer implementations to capture screens, this function + // should return monitors. + // For DesktopCapturer implementations to capture windows, this function + // should only return root windows owned by applications. + virtual bool GetSourceList(SourceList* sources); + + // Selects a source to be captured. Returns false in case of a failure (e.g. + // if there is no source with the specified type and id.) + virtual bool SelectSource(SourceId id); + + // Brings the selected source to the front and sets the input focus on it. + // Returns false in case of a failure or no source has been selected or the + // implementation does not support this functionality. + virtual bool FocusOnSelectedSource(); + + // Returns true if the `pos` on the selected source is covered by other + // elements on the display, and is not visible to the users. + // `pos` is in full desktop coordinates, i.e. the top-left monitor always + // starts from (0, 0). + // The return value if `pos` is out of the scope of the source is undefined. + virtual bool IsOccluded(const DesktopVector& pos); + + // Creates a DesktopCapturer instance which targets to capture windows. + static std::unique_ptr<DesktopCapturer> CreateWindowCapturer( + const DesktopCaptureOptions& options); + + // Creates a DesktopCapturer instance which targets to capture screens. + static std::unique_ptr<DesktopCapturer> CreateScreenCapturer( + const DesktopCaptureOptions& options); + + // Creates a DesktopCapturer instance which targets to capture tab. + static std::unique_ptr<DesktopCapturer> CreateTabCapturer( + const DesktopCaptureOptions& options); + +#if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) + static bool IsRunningUnderWayland(); + + virtual void UpdateResolution(uint32_t width, uint32_t height) {} +#endif // defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11) + +#if defined(WEBRTC_USE_GIO) + // Populates implementation specific metadata into the passed in pointer. + // Classes can choose to override it or use the default no-op implementation. + virtual DesktopCaptureMetadata GetMetadata() { return {}; } +#endif // defined(WEBRTC_USE_GIO) + + protected: + // CroppingWindowCapturer needs to create raw capturers without wrappers, so + // the following two functions are protected. + + // Creates a platform specific DesktopCapturer instance which targets to + // capture windows. + static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // Creates a platform specific DesktopCapturer instance which targets to + // capture screens. + static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer( + const DesktopCaptureOptions& options); + + // Creates a platform specific DesktopCapturer instance which targets to + // capture apps. + static std::unique_ptr<DesktopCapturer> CreateRawAppCapturer( + const DesktopCaptureOptions& options); + + // Creates a DesktopCapturer instance which targets to capture tabs + static std::unique_ptr<DesktopCapturer> CreateRawTabCapturer( + const DesktopCaptureOptions& options); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc new file mode 100644 index 0000000000..77543e4060 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" + +#include <stdint.h> +#include <string.h> + +#include <utility> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/differ_block.h" +#include "rtc_base/checks.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { + +// Returns true if (0, 0) - (`width`, `height`) vector in `old_buffer` and +// `new_buffer` are equal. `width` should be less than 32 +// (defined by kBlockSize), otherwise BlockDifference() should be used. +bool PartialBlockDifference(const uint8_t* old_buffer, + const uint8_t* new_buffer, + int width, + int height, + int stride) { + RTC_DCHECK_LT(width, kBlockSize); + const int width_bytes = width * DesktopFrame::kBytesPerPixel; + for (int i = 0; i < height; i++) { + if (memcmp(old_buffer, new_buffer, width_bytes) != 0) { + return true; + } + old_buffer += stride; + new_buffer += stride; + } + return false; +} + +// Compares columns in the range of [`left`, `right`), in a row in the +// range of [`top`, `top` + `height`), starts from `old_buffer` and +// `new_buffer`, and outputs updated regions into `output`. `stride` is the +// DesktopFrame::stride(). +void CompareRow(const uint8_t* old_buffer, + const uint8_t* new_buffer, + const int left, + const int right, + const int top, + const int bottom, + const int stride, + DesktopRegion* const output) { + const int block_x_offset = kBlockSize * DesktopFrame::kBytesPerPixel; + const int width = right - left; + const int height = bottom - top; + const int block_count = (width - 1) / kBlockSize; + const int last_block_width = width - block_count * kBlockSize; + RTC_DCHECK_GT(last_block_width, 0); + RTC_DCHECK_LE(last_block_width, kBlockSize); + + // The first block-column in a continuous dirty area in current block-row. + int first_dirty_x_block = -1; + + // We always need to add dirty area into `output` in the last block, so handle + // it separatedly. + for (int x = 0; x < block_count; x++) { + if (BlockDifference(old_buffer, new_buffer, height, stride)) { + if (first_dirty_x_block == -1) { + // This is the first dirty block in a continuous dirty area. + first_dirty_x_block = x; + } + } else if (first_dirty_x_block != -1) { + // The block on the left is the last dirty block in a continuous + // dirty area. + output->AddRect( + DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top, + x * kBlockSize + left, bottom)); + first_dirty_x_block = -1; + } + old_buffer += block_x_offset; + new_buffer += block_x_offset; + } + + bool last_block_diff; + if (last_block_width < kBlockSize) { + // The last one is a partial vector. + last_block_diff = PartialBlockDifference(old_buffer, new_buffer, + last_block_width, height, stride); + } else { + last_block_diff = BlockDifference(old_buffer, new_buffer, height, stride); + } + if (last_block_diff) { + if (first_dirty_x_block == -1) { + first_dirty_x_block = block_count; + } + output->AddRect(DesktopRect::MakeLTRB( + first_dirty_x_block * kBlockSize + left, top, right, bottom)); + } else if (first_dirty_x_block != -1) { + output->AddRect( + DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top, + block_count * kBlockSize + left, bottom)); + } +} + +// Compares `rect` area in `old_frame` and `new_frame`, and outputs dirty +// regions into `output`. +void CompareFrames(const DesktopFrame& old_frame, + const DesktopFrame& new_frame, + DesktopRect rect, + DesktopRegion* const output) { + RTC_DCHECK(old_frame.size().equals(new_frame.size())); + RTC_DCHECK_EQ(old_frame.stride(), new_frame.stride()); + rect.IntersectWith(DesktopRect::MakeSize(old_frame.size())); + + const int y_block_count = (rect.height() - 1) / kBlockSize; + const int last_y_block_height = rect.height() - y_block_count * kBlockSize; + // Offset from the start of one block-row to the next. + const int block_y_stride = old_frame.stride() * kBlockSize; + const uint8_t* prev_block_row_start = + old_frame.GetFrameDataAtPos(rect.top_left()); + const uint8_t* curr_block_row_start = + new_frame.GetFrameDataAtPos(rect.top_left()); + + int top = rect.top(); + // The last row may have a different height, so we handle it separately. + for (int y = 0; y < y_block_count; y++) { + CompareRow(prev_block_row_start, curr_block_row_start, rect.left(), + rect.right(), top, top + kBlockSize, old_frame.stride(), output); + top += kBlockSize; + prev_block_row_start += block_y_stride; + curr_block_row_start += block_y_stride; + } + CompareRow(prev_block_row_start, curr_block_row_start, rect.left(), + rect.right(), top, top + last_y_block_height, old_frame.stride(), + output); +} + +} // namespace + +DesktopCapturerDifferWrapper::DesktopCapturerDifferWrapper( + std::unique_ptr<DesktopCapturer> base_capturer) + : base_capturer_(std::move(base_capturer)) { + RTC_DCHECK(base_capturer_); +} + +DesktopCapturerDifferWrapper::~DesktopCapturerDifferWrapper() {} + +void DesktopCapturerDifferWrapper::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + base_capturer_->Start(this); +} + +void DesktopCapturerDifferWrapper::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + base_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void DesktopCapturerDifferWrapper::CaptureFrame() { + base_capturer_->CaptureFrame(); +} + +void DesktopCapturerDifferWrapper::SetExcludedWindow(WindowId window) { + base_capturer_->SetExcludedWindow(window); +} + +bool DesktopCapturerDifferWrapper::GetSourceList(SourceList* sources) { + return base_capturer_->GetSourceList(sources); +} + +bool DesktopCapturerDifferWrapper::SelectSource(SourceId id) { + return base_capturer_->SelectSource(id); +} + +bool DesktopCapturerDifferWrapper::FocusOnSelectedSource() { + return base_capturer_->FocusOnSelectedSource(); +} + +bool DesktopCapturerDifferWrapper::IsOccluded(const DesktopVector& pos) { + return base_capturer_->IsOccluded(pos); +} + +#if defined(WEBRTC_USE_GIO) +DesktopCaptureMetadata DesktopCapturerDifferWrapper::GetMetadata() { + return base_capturer_->GetMetadata(); +} +#endif // defined(WEBRTC_USE_GIO) + +void DesktopCapturerDifferWrapper::OnCaptureResult( + Result result, + std::unique_ptr<DesktopFrame> input_frame) { + int64_t start_time_nanos = rtc::TimeNanos(); + if (!input_frame) { + callback_->OnCaptureResult(result, nullptr); + return; + } + RTC_DCHECK(result == Result::SUCCESS); + + std::unique_ptr<SharedDesktopFrame> frame = + SharedDesktopFrame::Wrap(std::move(input_frame)); + if (last_frame_ && (last_frame_->size().width() != frame->size().width() || + last_frame_->size().height() != frame->size().height() || + last_frame_->stride() != frame->stride())) { + last_frame_.reset(); + } + + if (last_frame_) { + DesktopRegion hints; + hints.Swap(frame->mutable_updated_region()); + for (DesktopRegion::Iterator it(hints); !it.IsAtEnd(); it.Advance()) { + CompareFrames(*last_frame_, *frame, it.rect(), + frame->mutable_updated_region()); + } + } else { + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + } + last_frame_ = frame->Share(); + + frame->set_capture_time_ms(frame->capture_time_ms() + + (rtc::TimeNanos() - start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(result, std::move(frame)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h new file mode 100644 index 0000000000..6ebb5d7bc3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ + +#include <memory> +#if defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_metadata.h" +#endif // defined(WEBRTC_USE_GIO) +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// DesktopCapturer wrapper that calculates updated_region() by comparing frames +// content. This class always expects the underlying DesktopCapturer +// implementation returns a superset of updated regions in DestkopFrame. If a +// DesktopCapturer implementation does not know the updated region, it should +// set updated_region() to full frame. +// +// This class marks entire frame as updated if the frame size or frame stride +// has been changed. +class RTC_EXPORT DesktopCapturerDifferWrapper + : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + // Creates a DesktopCapturerDifferWrapper with a DesktopCapturer + // implementation, and takes its ownership. + explicit DesktopCapturerDifferWrapper( + std::unique_ptr<DesktopCapturer> base_capturer); + + ~DesktopCapturerDifferWrapper() override; + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; +#if defined(WEBRTC_USE_GIO) + DesktopCaptureMetadata GetMetadata() override; +#endif // defined(WEBRTC_USE_GIO) + private: + // DesktopCapturer::Callback interface. + void OnCaptureResult(Result result, + std::unique_ptr<DesktopFrame> frame) override; + + const std::unique_ptr<DesktopCapturer> base_capturer_; + DesktopCapturer::Callback* callback_; + std::unique_ptr<SharedDesktopFrame> last_frame_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc new file mode 100644 index 0000000000..9ccef3cc10 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" + +#include <initializer_list> +#include <memory> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/differ_block.h" +#include "modules/desktop_capture/fake_desktop_capturer.h" +#include "modules/desktop_capture/mock_desktop_capturer_callback.h" +#include "rtc_base/random.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/cpu_features_wrapper.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +// Compares and asserts `frame`.updated_region() equals to `rects`. This +// function does not care about the order of the `rects` and it does not expect +// DesktopRegion to return an exact area for each rectangle in `rects`. +template <template <typename, typename...> class T = std::initializer_list, + typename... Rect> +void AssertUpdatedRegionIs(const DesktopFrame& frame, + const T<DesktopRect, Rect...>& rects) { + DesktopRegion region; + for (const auto& rect : rects) { + region.AddRect(rect); + } + ASSERT_TRUE(frame.updated_region().Equals(region)); +} + +// Compares and asserts `frame`.updated_region() covers all rectangles in +// `rects`, but does not cover areas other than a kBlockSize expansion. This +// function does not care about the order of the `rects`, and it does not expect +// DesktopRegion to return an exact area of each rectangle in `rects`. +template <template <typename, typename...> class T = std::initializer_list, + typename... Rect> +void AssertUpdatedRegionCovers(const DesktopFrame& frame, + const T<DesktopRect, Rect...>& rects) { + DesktopRegion region; + for (const auto& rect : rects) { + region.AddRect(rect); + } + + // Intersect of `rects` and `frame`.updated_region() should be `rects`. i.e. + // `frame`.updated_region() should be a superset of `rects`. + DesktopRegion intersect(region); + intersect.IntersectWith(frame.updated_region()); + ASSERT_TRUE(region.Equals(intersect)); + + // Difference between `rects` and `frame`.updated_region() should not cover + // areas which have larger than twice of kBlockSize width and height. + // + // Explanation of the 'twice' of kBlockSize (indeed kBlockSize * 2 - 2) is + // following, + // (Each block in the following grid is a 8 x 8 pixels area. X means the real + // updated area, m means the updated area marked by + // DesktopCapturerDifferWrapper.) + // +---+---+---+---+---+---+---+---+ + // | X | m | m | m | m | m | m | m | + // +---+---+---+---+---+---+---+---+ + // | m | m | m | m | m | m | m | m | + // +---+---+---+---+---+---+---+---+ + // | m | m | m | m | m | m | m | m | + // +---+---+---+---+---+---+---+---+ + // | m | m | m | m | m | m | m | X | + // +---+---+---+---+---+---+---+---+ + // The top left [0, 0] - [8, 8] and right bottom [56, 24] - [64, 32] blocks of + // this area are updated. But since DesktopCapturerDifferWrapper compares + // 32 x 32 blocks by default, this entire area is marked as updated. So the + // [8, 8] - [56, 32] is expected to be covered in the difference. + // + // But if [0, 0] - [8, 8] and [64, 24] - [72, 32] blocks are updated, + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // | X | m | m | m | | | | | m | m | m | m | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // | m | m | m | m | | | | | m | m | m | m | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // | m | m | m | m | | | | | m | m | m | m | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // | m | m | m | m | | | | | X | m | m | m | + // +---+---+---+---+---+---+---+---+---+---+---+---+ + // the [8, 8] - [64, 32] is not expected to be covered in the difference. As + // DesktopCapturerDifferWrapper should only mark [0, 0] - [32, 32] and + // [64, 0] - [96, 32] as updated. + DesktopRegion differ(frame.updated_region()); + differ.Subtract(region); + for (DesktopRegion::Iterator it(differ); !it.IsAtEnd(); it.Advance()) { + ASSERT_TRUE(it.rect().width() <= kBlockSize * 2 - 2 || + it.rect().height() <= kBlockSize * 2 - 2); + } +} + +// Executes a DesktopCapturerDifferWrapper::Capture() and compares its output +// DesktopFrame::updated_region() with `updated_region` if `check_result` is +// true. If `exactly_match` is true, AssertUpdatedRegionIs() will be used, +// otherwise AssertUpdatedRegionCovers() will be used. +template <template <typename, typename...> class T = std::initializer_list, + typename... Rect> +void ExecuteDifferWrapperCase(BlackWhiteDesktopFramePainter* frame_painter, + DesktopCapturerDifferWrapper* capturer, + MockDesktopCapturerCallback* callback, + const T<DesktopRect, Rect...>& updated_region, + bool check_result, + bool exactly_match) { + EXPECT_CALL(*callback, OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, + ::testing::_)) + .Times(1) + .WillOnce( + ::testing::Invoke([&updated_region, check_result, exactly_match]( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame) { + ASSERT_EQ(result, DesktopCapturer::Result::SUCCESS); + if (check_result) { + if (exactly_match) { + AssertUpdatedRegionIs(**frame, updated_region); + } else { + AssertUpdatedRegionCovers(**frame, updated_region); + } + } + })); + for (const auto& rect : updated_region) { + frame_painter->updated_region()->AddRect(rect); + } + capturer->CaptureFrame(); +} + +// Executes a DesktopCapturerDifferWrapper::Capture(), if updated_region() is +// not set, this function will reset DesktopCapturerDifferWrapper internal +// DesktopFrame into black. +void ExecuteCapturer(DesktopCapturerDifferWrapper* capturer, + MockDesktopCapturerCallback* callback) { + EXPECT_CALL(*callback, OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, + ::testing::_)) + .Times(1); + capturer->CaptureFrame(); +} + +void ExecuteDifferWrapperTest(bool with_hints, + bool enlarge_updated_region, + bool random_updated_region, + bool check_result) { + const bool updated_region_should_exactly_match = + with_hints && !enlarge_updated_region && !random_updated_region; + BlackWhiteDesktopFramePainter frame_painter; + PainterDesktopFrameGenerator frame_generator; + frame_generator.set_desktop_frame_painter(&frame_painter); + std::unique_ptr<FakeDesktopCapturer> fake(new FakeDesktopCapturer()); + fake->set_frame_generator(&frame_generator); + DesktopCapturerDifferWrapper capturer(std::move(fake)); + MockDesktopCapturerCallback callback; + frame_generator.set_provide_updated_region_hints(with_hints); + frame_generator.set_enlarge_updated_region(enlarge_updated_region); + frame_generator.set_add_random_updated_region(random_updated_region); + + capturer.Start(&callback); + + EXPECT_CALL(callback, OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, + ::testing::_)) + .Times(1) + .WillOnce(::testing::Invoke([](DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame) { + ASSERT_EQ(result, DesktopCapturer::Result::SUCCESS); + AssertUpdatedRegionIs(**frame, + {DesktopRect::MakeSize((*frame)->size())}); + })); + capturer.CaptureFrame(); + + ExecuteDifferWrapperCase(&frame_painter, &capturer, &callback, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(300, 300, 400, 400)}, + check_result, updated_region_should_exactly_match); + ExecuteCapturer(&capturer, &callback); + + ExecuteDifferWrapperCase( + &frame_painter, &capturer, &callback, + {DesktopRect::MakeLTRB(0, 0, 40, 40), + DesktopRect::MakeLTRB(0, frame_generator.size()->height() - 40, 40, + frame_generator.size()->height()), + DesktopRect::MakeLTRB(frame_generator.size()->width() - 40, 0, + frame_generator.size()->width(), 40), + DesktopRect::MakeLTRB(frame_generator.size()->width() - 40, + frame_generator.size()->height() - 40, + frame_generator.size()->width(), + frame_generator.size()->height())}, + check_result, updated_region_should_exactly_match); + + Random random(rtc::TimeMillis()); + // Fuzzing tests. + for (int i = 0; i < 1000; i++) { + if (enlarge_updated_region) { + frame_generator.set_enlarge_range(random.Rand(1, 50)); + } + frame_generator.size()->set(random.Rand(500, 2000), random.Rand(500, 2000)); + ExecuteCapturer(&capturer, &callback); + std::vector<DesktopRect> updated_region; + for (int j = random.Rand(50); j >= 0; j--) { + // At least a 1 x 1 updated region. + const int left = random.Rand(0, frame_generator.size()->width() - 2); + const int top = random.Rand(0, frame_generator.size()->height() - 2); + const int right = random.Rand(left + 1, frame_generator.size()->width()); + const int bottom = random.Rand(top + 1, frame_generator.size()->height()); + updated_region.push_back(DesktopRect::MakeLTRB(left, top, right, bottom)); + } + ExecuteDifferWrapperCase(&frame_painter, &capturer, &callback, + updated_region, check_result, + updated_region_should_exactly_match); + } +} + +} // namespace + +TEST(DesktopCapturerDifferWrapperTest, CaptureWithoutHints) { + ExecuteDifferWrapperTest(false, false, false, true); +} + +TEST(DesktopCapturerDifferWrapperTest, CaptureWithHints) { + ExecuteDifferWrapperTest(true, false, false, true); +} + +TEST(DesktopCapturerDifferWrapperTest, CaptureWithEnlargedHints) { + ExecuteDifferWrapperTest(true, true, false, true); +} + +TEST(DesktopCapturerDifferWrapperTest, CaptureWithRandomHints) { + ExecuteDifferWrapperTest(true, false, true, true); +} + +TEST(DesktopCapturerDifferWrapperTest, CaptureWithEnlargedAndRandomHints) { + ExecuteDifferWrapperTest(true, true, true, true); +} + +// When hints are provided, DesktopCapturerDifferWrapper has a slightly better +// performance in current configuration, but not so significant. Following is +// one run result. +// [ RUN ] DISABLED_CaptureWithoutHintsPerf +// [ OK ] DISABLED_CaptureWithoutHintsPerf (7118 ms) +// [ RUN ] DISABLED_CaptureWithHintsPerf +// [ OK ] DISABLED_CaptureWithHintsPerf (5580 ms) +// [ RUN ] DISABLED_CaptureWithEnlargedHintsPerf +// [ OK ] DISABLED_CaptureWithEnlargedHintsPerf (5974 ms) +// [ RUN ] DISABLED_CaptureWithRandomHintsPerf +// [ OK ] DISABLED_CaptureWithRandomHintsPerf (6184 ms) +// [ RUN ] DISABLED_CaptureWithEnlargedAndRandomHintsPerf +// [ OK ] DISABLED_CaptureWithEnlargedAndRandomHintsPerf (6347 ms) +TEST(DesktopCapturerDifferWrapperTest, DISABLED_CaptureWithoutHintsPerf) { + int64_t started = rtc::TimeMillis(); + ExecuteDifferWrapperTest(false, false, false, false); + ASSERT_LE(rtc::TimeMillis() - started, 15000); +} + +TEST(DesktopCapturerDifferWrapperTest, DISABLED_CaptureWithHintsPerf) { + int64_t started = rtc::TimeMillis(); + ExecuteDifferWrapperTest(true, false, false, false); + ASSERT_LE(rtc::TimeMillis() - started, 15000); +} + +TEST(DesktopCapturerDifferWrapperTest, DISABLED_CaptureWithEnlargedHintsPerf) { + int64_t started = rtc::TimeMillis(); + ExecuteDifferWrapperTest(true, true, false, false); + ASSERT_LE(rtc::TimeMillis() - started, 15000); +} + +TEST(DesktopCapturerDifferWrapperTest, DISABLED_CaptureWithRandomHintsPerf) { + int64_t started = rtc::TimeMillis(); + ExecuteDifferWrapperTest(true, false, true, false); + ASSERT_LE(rtc::TimeMillis() - started, 15000); +} + +TEST(DesktopCapturerDifferWrapperTest, + DISABLED_CaptureWithEnlargedAndRandomHintsPerf) { + int64_t started = rtc::TimeMillis(); + ExecuteDifferWrapperTest(true, true, true, false); + ASSERT_LE(rtc::TimeMillis() - started, 15000); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.cc new file mode 100644 index 0000000000..4bbdd6c94f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.cc @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer_wrapper.h" + +#include <utility> + +#include "rtc_base/checks.h" + +namespace webrtc { + +DesktopCapturerWrapper::DesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer> base_capturer) + : base_capturer_(std::move(base_capturer)) { + RTC_DCHECK(base_capturer_); +} + +DesktopCapturerWrapper::~DesktopCapturerWrapper() = default; + +void DesktopCapturerWrapper::Start(Callback* callback) { + base_capturer_->Start(callback); +} + +void DesktopCapturerWrapper::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + base_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void DesktopCapturerWrapper::CaptureFrame() { + base_capturer_->CaptureFrame(); +} + +void DesktopCapturerWrapper::SetExcludedWindow(WindowId window) { + base_capturer_->SetExcludedWindow(window); +} + +bool DesktopCapturerWrapper::GetSourceList(SourceList* sources) { + return base_capturer_->GetSourceList(sources); +} + +bool DesktopCapturerWrapper::SelectSource(SourceId id) { + return base_capturer_->SelectSource(id); +} + +bool DesktopCapturerWrapper::FocusOnSelectedSource() { + return base_capturer_->FocusOnSelectedSource(); +} + +bool DesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) { + return base_capturer_->IsOccluded(pos); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.h b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.h new file mode 100644 index 0000000000..e0f50d79e1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_capturer_wrapper.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +// Wraps a DesktopCapturer and forwards all the function calls to it. +class DesktopCapturerWrapper : public DesktopCapturer { + public: + explicit DesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer> base_capturer); + ~DesktopCapturerWrapper() override; + + // DesktopCapturer implementations. + void Start(Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + protected: + // Guaranteed to be valid. + const std::unique_ptr<DesktopCapturer> base_capturer_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame.cc new file mode 100644 index 0000000000..3e7b4770c6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame.cc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame.h" + +#include <string.h> + +#include <cmath> +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/checks.h" +#include "libyuv/include/libyuv/planar_functions.h" + +namespace webrtc { + +DesktopFrame::DesktopFrame(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory) + : data_(data), + shared_memory_(shared_memory), + size_(size), + stride_(stride), + capture_time_ms_(0), + capturer_id_(DesktopCapturerId::kUnknown) { + RTC_DCHECK(size_.width() >= 0); + RTC_DCHECK(size_.height() >= 0); +} + +DesktopFrame::~DesktopFrame() = default; + +void DesktopFrame::CopyPixelsFrom(const uint8_t* src_buffer, + int src_stride, + const DesktopRect& dest_rect) { + RTC_CHECK(DesktopRect::MakeSize(size()).ContainsRect(dest_rect)); + + uint8_t* dest = GetFrameDataAtPos(dest_rect.top_left()); + // TODO(crbug.com/1330019): Temporary workaround for a known libyuv crash when + // the height or width is 0. Remove this once this change has been merged. + if (dest_rect.width() && dest_rect.height()) { + libyuv::CopyPlane(src_buffer, src_stride, dest, stride(), + DesktopFrame::kBytesPerPixel * dest_rect.width(), + dest_rect.height()); + } +} + +void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame, + const DesktopVector& src_pos, + const DesktopRect& dest_rect) { + RTC_CHECK(DesktopRect::MakeSize(src_frame.size()) + .ContainsRect( + DesktopRect::MakeOriginSize(src_pos, dest_rect.size()))); + + CopyPixelsFrom(src_frame.GetFrameDataAtPos(src_pos), src_frame.stride(), + dest_rect); +} + +bool DesktopFrame::CopyIntersectingPixelsFrom(const DesktopFrame& src_frame, + double horizontal_scale, + double vertical_scale) { + const DesktopVector& origin = top_left(); + const DesktopVector& src_frame_origin = src_frame.top_left(); + + DesktopVector src_frame_offset = src_frame_origin.subtract(origin); + + // Determine the intersection, first adjusting its origin to account for any + // DPI scaling. + DesktopRect intersection_rect = src_frame.rect(); + if (horizontal_scale != 1.0 || vertical_scale != 1.0) { + DesktopVector origin_adjustment( + static_cast<int>( + std::round((horizontal_scale - 1.0) * src_frame_offset.x())), + static_cast<int>( + std::round((vertical_scale - 1.0) * src_frame_offset.y()))); + + intersection_rect.Translate(origin_adjustment); + + src_frame_offset = src_frame_offset.add(origin_adjustment); + } + + intersection_rect.IntersectWith(rect()); + if (intersection_rect.is_empty()) { + return false; + } + + // Translate the intersection rect to be relative to the outer rect. + intersection_rect.Translate(-origin.x(), -origin.y()); + + // Determine source position for the copy (offsets of outer frame from + // source origin, if positive). + int32_t src_pos_x = std::max(0, -src_frame_offset.x()); + int32_t src_pos_y = std::max(0, -src_frame_offset.y()); + + CopyPixelsFrom(src_frame, DesktopVector(src_pos_x, src_pos_y), + intersection_rect); + return true; +} + +DesktopRect DesktopFrame::rect() const { + const float scale = scale_factor(); + // Only scale the size. + return DesktopRect::MakeXYWH(top_left().x(), top_left().y(), + size().width() / scale, size().height() / scale); +} + +float DesktopFrame::scale_factor() const { + float scale = 1.0f; + +#if defined(WEBRTC_MAC) || defined(CHROMEOS) + // At least on Windows the logical and physical pixel are the same + // See http://crbug.com/948362. + if (!dpi().is_zero() && dpi().x() == dpi().y()) + scale = dpi().x() / kStandardDPI; +#endif + + return scale; +} + +uint8_t* DesktopFrame::GetFrameDataAtPos(const DesktopVector& pos) const { + return data() + stride() * pos.y() + DesktopFrame::kBytesPerPixel * pos.x(); +} + +void DesktopFrame::CopyFrameInfoFrom(const DesktopFrame& other) { + set_dpi(other.dpi()); + set_capture_time_ms(other.capture_time_ms()); + set_capturer_id(other.capturer_id()); + *mutable_updated_region() = other.updated_region(); + set_top_left(other.top_left()); + set_icc_profile(other.icc_profile()); +} + +void DesktopFrame::MoveFrameInfoFrom(DesktopFrame* other) { + set_dpi(other->dpi()); + set_capture_time_ms(other->capture_time_ms()); + set_capturer_id(other->capturer_id()); + mutable_updated_region()->Swap(other->mutable_updated_region()); + set_top_left(other->top_left()); + set_icc_profile(other->icc_profile()); +} + +BasicDesktopFrame::BasicDesktopFrame(DesktopSize size) + : DesktopFrame(size, + kBytesPerPixel * size.width(), + new uint8_t[kBytesPerPixel * size.width() * size.height()](), + nullptr) {} + +BasicDesktopFrame::~BasicDesktopFrame() { + delete[] data_; +} + +// static +DesktopFrame* BasicDesktopFrame::CopyOf(const DesktopFrame& frame) { + DesktopFrame* result = new BasicDesktopFrame(frame.size()); + // TODO(crbug.com/1330019): Temporary workaround for a known libyuv crash when + // the height or width is 0. Remove this once this change has been merged. + if (frame.size().width() && frame.size().height()) { + libyuv::CopyPlane(frame.data(), frame.stride(), result->data(), + result->stride(), frame.size().width() * kBytesPerPixel, + frame.size().height()); + } + result->CopyFrameInfoFrom(frame); + return result; +} + +// static +std::unique_ptr<DesktopFrame> SharedMemoryDesktopFrame::Create( + DesktopSize size, + SharedMemoryFactory* shared_memory_factory) { + RTC_DCHECK(shared_memory_factory); + + size_t buffer_size = size.height() * size.width() * kBytesPerPixel; + std::unique_ptr<SharedMemory> shared_memory = + shared_memory_factory->CreateSharedMemory(buffer_size); + if (!shared_memory) + return nullptr; + + return std::make_unique<SharedMemoryDesktopFrame>( + size, size.width() * kBytesPerPixel, std::move(shared_memory)); +} + +SharedMemoryDesktopFrame::SharedMemoryDesktopFrame(DesktopSize size, + int stride, + SharedMemory* shared_memory) + : DesktopFrame(size, + stride, + reinterpret_cast<uint8_t*>(shared_memory->data()), + shared_memory) {} + +SharedMemoryDesktopFrame::SharedMemoryDesktopFrame( + DesktopSize size, + int stride, + std::unique_ptr<SharedMemory> shared_memory) + : SharedMemoryDesktopFrame(size, stride, shared_memory.release()) {} + +SharedMemoryDesktopFrame::~SharedMemoryDesktopFrame() { + delete shared_memory_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame.h b/third_party/libwebrtc/modules/desktop_capture/desktop_frame.h new file mode 100644 index 0000000000..3ee1867e70 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame.h @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +const float kStandardDPI = 96.0f; + +// DesktopFrame represents a video frame captured from the screen. +class RTC_EXPORT DesktopFrame { + public: + // DesktopFrame objects always hold BGRA data. + static const int kBytesPerPixel = 4; + + virtual ~DesktopFrame(); + + DesktopFrame(const DesktopFrame&) = delete; + DesktopFrame& operator=(const DesktopFrame&) = delete; + + // Returns the rectangle in full desktop coordinates to indicate it covers + // the area of top_left() to top_letf() + size() / scale_factor(). + DesktopRect rect() const; + + // Returns the scale factor from DIPs to physical pixels of the frame. + // Assumes same scale in both X and Y directions at present. + float scale_factor() const; + + // Size of the frame. In physical coordinates, mapping directly from the + // underlying buffer. + const DesktopSize& size() const { return size_; } + + // The top-left of the frame in full desktop coordinates. E.g. the top left + // monitor should start from (0, 0). The desktop coordinates may be scaled by + // OS, but this is always consistent with the MouseCursorMonitor. + const DesktopVector& top_left() const { return top_left_; } + void set_top_left(const DesktopVector& top_left) { top_left_ = top_left; } + + // Distance in the buffer between two neighboring rows in bytes. + int stride() const { return stride_; } + + // Data buffer used for the frame. + uint8_t* data() const { return data_; } + + // SharedMemory used for the buffer or NULL if memory is allocated on the + // heap. The result is guaranteed to be deleted only after the frame is + // deleted (classes that inherit from DesktopFrame must ensure it). + SharedMemory* shared_memory() const { return shared_memory_; } + + // Indicates region of the screen that has changed since the previous frame. + const DesktopRegion& updated_region() const { return updated_region_; } + DesktopRegion* mutable_updated_region() { return &updated_region_; } + + // DPI of the screen being captured. May be set to zero, e.g. if DPI is + // unknown. + const DesktopVector& dpi() const { return dpi_; } + void set_dpi(const DesktopVector& dpi) { dpi_ = dpi; } + + // Indicates if this frame may have the mouse cursor in it. Capturers that + // support cursor capture may set this to true. If the cursor was + // outside of the captured area, this may be true even though the cursor is + // not in the image. + bool may_contain_cursor() const { return may_contain_cursor_; } + void set_may_contain_cursor(bool may_contain_cursor) { + may_contain_cursor_ = may_contain_cursor; + } + + // Time taken to capture the frame in milliseconds. + int64_t capture_time_ms() const { return capture_time_ms_; } + void set_capture_time_ms(int64_t time_ms) { capture_time_ms_ = time_ms; } + + // Copies pixels from a buffer or another frame. `dest_rect` rect must lay + // within bounds of this frame. + void CopyPixelsFrom(const uint8_t* src_buffer, + int src_stride, + const DesktopRect& dest_rect); + void CopyPixelsFrom(const DesktopFrame& src_frame, + const DesktopVector& src_pos, + const DesktopRect& dest_rect); + + // Copies pixels from another frame, with the copied & overwritten regions + // representing the intersection between the two frames. Returns true if + // pixels were copied, or false if there's no intersection. The scale factors + // represent the ratios between pixel space & offset coordinate space (e.g. + // 2.0 would indicate the frames are scaled down by 50% for display, so any + // offset between their origins should be doubled). + bool CopyIntersectingPixelsFrom(const DesktopFrame& src_frame, + double horizontal_scale, + double vertical_scale); + + // A helper to return the data pointer of a frame at the specified position. + uint8_t* GetFrameDataAtPos(const DesktopVector& pos) const; + + // The DesktopCapturer implementation which generates current DesktopFrame. + // Not all DesktopCapturer implementations set this field; it's set to + // kUnknown by default. + uint32_t capturer_id() const { return capturer_id_; } + void set_capturer_id(uint32_t capturer_id) { capturer_id_ = capturer_id; } + + // Copies various information from `other`. Anything initialized in + // constructor are not copied. + // This function is usually used when sharing a source DesktopFrame with + // several clients: the original DesktopFrame should be kept unchanged. For + // example, BasicDesktopFrame::CopyOf() and SharedDesktopFrame::Share(). + void CopyFrameInfoFrom(const DesktopFrame& other); + + // Copies various information from `other`. Anything initialized in + // constructor are not copied. Not like CopyFrameInfoFrom() function, this + // function uses swap or move constructor to avoid data copy. It won't break + // the `other`, but some of its information may be missing after this + // operation. E.g. other->updated_region_; + // This function is usually used when wrapping a DesktopFrame: the wrapper + // instance takes the ownership of `other`, so other components cannot access + // `other` anymore. For example, CroppedDesktopFrame and + // DesktopFrameWithCursor. + void MoveFrameInfoFrom(DesktopFrame* other); + + // Set and get the ICC profile of the frame data pixels. Useful to build the + // a ColorSpace object from clients of webrtc library like chromium. The + // format of an ICC profile is defined in the following specification + // http://www.color.org/specification/ICC1v43_2010-12.pdf. + const std::vector<uint8_t>& icc_profile() const { return icc_profile_; } + void set_icc_profile(const std::vector<uint8_t>& icc_profile) { + icc_profile_ = icc_profile; + } + + protected: + DesktopFrame(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory); + + // Ownership of the buffers is defined by the classes that inherit from this + // class. They must guarantee that the buffer is not deleted before the frame + // is deleted. + uint8_t* const data_; + SharedMemory* const shared_memory_; + + private: + const DesktopSize size_; + const int stride_; + + DesktopRegion updated_region_; + DesktopVector top_left_; + DesktopVector dpi_; + bool may_contain_cursor_ = false; + int64_t capture_time_ms_; + uint32_t capturer_id_; + std::vector<uint8_t> icc_profile_; +}; + +// A DesktopFrame that stores data in the heap. +class RTC_EXPORT BasicDesktopFrame : public DesktopFrame { + public: + // The entire data buffer used for the frame is initialized with zeros. + explicit BasicDesktopFrame(DesktopSize size); + + ~BasicDesktopFrame() override; + + BasicDesktopFrame(const BasicDesktopFrame&) = delete; + BasicDesktopFrame& operator=(const BasicDesktopFrame&) = delete; + + // Creates a BasicDesktopFrame that contains copy of `frame`. + // TODO(zijiehe): Return std::unique_ptr<DesktopFrame> + static DesktopFrame* CopyOf(const DesktopFrame& frame); +}; + +// A DesktopFrame that stores data in shared memory. +class RTC_EXPORT SharedMemoryDesktopFrame : public DesktopFrame { + public: + // May return nullptr if `shared_memory_factory` failed to create a + // SharedMemory instance. + // `shared_memory_factory` should not be nullptr. + static std::unique_ptr<DesktopFrame> Create( + DesktopSize size, + SharedMemoryFactory* shared_memory_factory); + + // Takes ownership of `shared_memory`. + // Deprecated, use the next constructor. + SharedMemoryDesktopFrame(DesktopSize size, + int stride, + SharedMemory* shared_memory); + + // Preferred. + SharedMemoryDesktopFrame(DesktopSize size, + int stride, + std::unique_ptr<SharedMemory> shared_memory); + + ~SharedMemoryDesktopFrame() override; + + SharedMemoryDesktopFrame(const SharedMemoryDesktopFrame&) = delete; + SharedMemoryDesktopFrame& operator=(const SharedMemoryDesktopFrame&) = delete; + + private: + // Avoid unexpected order of parameter evaluation. + // Executing both std::unique_ptr<T>::operator->() and + // std::unique_ptr<T>::release() in the member initializer list is not safe. + // Depends on the order of parameter evaluation, + // std::unique_ptr<T>::operator->() may trigger assertion failure if it has + // been evaluated after std::unique_ptr<T>::release(). By using this + // constructor, std::unique_ptr<T>::operator->() won't be involved anymore. + SharedMemoryDesktopFrame(DesktopRect rect, + int stride, + SharedMemory* shared_memory); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.cc new file mode 100644 index 0000000000..b5dfc28e46 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.cc @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame_generator.h" + +#include <stdint.h> +#include <string.h> + +#include <memory> + +#include "modules/desktop_capture/rgba_color.h" +#include "rtc_base/checks.h" +#include "rtc_base/random.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { + +// Sets `updated_region` to `frame`. If `enlarge_updated_region` is +// true, this function will randomly enlarge each DesktopRect in +// `updated_region`. But the enlarged DesktopRegion won't excceed the +// frame->size(). If `add_random_updated_region` is true, several random +// rectangles will also be included in `frame`. +void SetUpdatedRegion(DesktopFrame* frame, + const DesktopRegion& updated_region, + bool enlarge_updated_region, + int enlarge_range, + bool add_random_updated_region) { + const DesktopRect screen_rect = DesktopRect::MakeSize(frame->size()); + Random random(rtc::TimeMicros()); + frame->mutable_updated_region()->Clear(); + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + DesktopRect rect = it.rect(); + if (enlarge_updated_region && enlarge_range > 0) { + rect.Extend(random.Rand(enlarge_range), random.Rand(enlarge_range), + random.Rand(enlarge_range), random.Rand(enlarge_range)); + rect.IntersectWith(screen_rect); + } + frame->mutable_updated_region()->AddRect(rect); + } + + if (add_random_updated_region) { + for (int i = random.Rand(10); i >= 0; i--) { + // At least a 1 x 1 updated region. + const int left = random.Rand(0, frame->size().width() - 2); + const int top = random.Rand(0, frame->size().height() - 2); + const int right = random.Rand(left + 1, frame->size().width()); + const int bottom = random.Rand(top + 1, frame->size().height()); + frame->mutable_updated_region()->AddRect( + DesktopRect::MakeLTRB(left, top, right, bottom)); + } + } +} + +// Paints pixels in `rect` of `frame` to `color`. +void PaintRect(DesktopFrame* frame, DesktopRect rect, RgbaColor rgba_color) { + static_assert(DesktopFrame::kBytesPerPixel == sizeof(uint32_t), + "kBytesPerPixel should be 4."); + RTC_DCHECK_GE(frame->size().width(), rect.right()); + RTC_DCHECK_GE(frame->size().height(), rect.bottom()); + uint32_t color = rgba_color.ToUInt32(); + uint8_t* row = frame->GetFrameDataAtPos(rect.top_left()); + for (int i = 0; i < rect.height(); i++) { + uint32_t* column = reinterpret_cast<uint32_t*>(row); + for (int j = 0; j < rect.width(); j++) { + column[j] = color; + } + row += frame->stride(); + } +} + +// Paints pixels in `region` of `frame` to `color`. +void PaintRegion(DesktopFrame* frame, + DesktopRegion* region, + RgbaColor rgba_color) { + region->IntersectWith(DesktopRect::MakeSize(frame->size())); + for (DesktopRegion::Iterator it(*region); !it.IsAtEnd(); it.Advance()) { + PaintRect(frame, it.rect(), rgba_color); + } +} + +} // namespace + +DesktopFrameGenerator::DesktopFrameGenerator() {} +DesktopFrameGenerator::~DesktopFrameGenerator() {} + +DesktopFramePainter::DesktopFramePainter() {} +DesktopFramePainter::~DesktopFramePainter() {} + +PainterDesktopFrameGenerator::PainterDesktopFrameGenerator() + : size_(1024, 768), + return_frame_(true), + provide_updated_region_hints_(false), + enlarge_updated_region_(false), + enlarge_range_(20), + add_random_updated_region_(false), + painter_(nullptr) {} +PainterDesktopFrameGenerator::~PainterDesktopFrameGenerator() {} + +std::unique_ptr<DesktopFrame> PainterDesktopFrameGenerator::GetNextFrame( + SharedMemoryFactory* factory) { + if (!return_frame_) { + return nullptr; + } + + std::unique_ptr<DesktopFrame> frame = std::unique_ptr<DesktopFrame>( + factory ? SharedMemoryDesktopFrame::Create(size_, factory).release() + : new BasicDesktopFrame(size_)); + if (painter_) { + DesktopRegion updated_region; + if (!painter_->Paint(frame.get(), &updated_region)) { + return nullptr; + } + + if (provide_updated_region_hints_) { + SetUpdatedRegion(frame.get(), updated_region, enlarge_updated_region_, + enlarge_range_, add_random_updated_region_); + } else { + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + } + } + + return frame; +} + +DesktopSize* PainterDesktopFrameGenerator::size() { + return &size_; +} + +void PainterDesktopFrameGenerator::set_return_frame(bool return_frame) { + return_frame_ = return_frame; +} + +void PainterDesktopFrameGenerator::set_provide_updated_region_hints( + bool provide_updated_region_hints) { + provide_updated_region_hints_ = provide_updated_region_hints; +} + +void PainterDesktopFrameGenerator::set_enlarge_updated_region( + bool enlarge_updated_region) { + enlarge_updated_region_ = enlarge_updated_region; +} + +void PainterDesktopFrameGenerator::set_enlarge_range(int enlarge_range) { + enlarge_range_ = enlarge_range; +} + +void PainterDesktopFrameGenerator::set_add_random_updated_region( + bool add_random_updated_region) { + add_random_updated_region_ = add_random_updated_region; +} + +void PainterDesktopFrameGenerator::set_desktop_frame_painter( + DesktopFramePainter* painter) { + painter_ = painter; +} + +BlackWhiteDesktopFramePainter::BlackWhiteDesktopFramePainter() {} +BlackWhiteDesktopFramePainter::~BlackWhiteDesktopFramePainter() {} + +DesktopRegion* BlackWhiteDesktopFramePainter::updated_region() { + return &updated_region_; +} + +bool BlackWhiteDesktopFramePainter::Paint(DesktopFrame* frame, + DesktopRegion* updated_region) { + RTC_DCHECK(updated_region->is_empty()); + memset(frame->data(), 0, frame->stride() * frame->size().height()); + PaintRegion(frame, &updated_region_, RgbaColor(0xFFFFFFFF)); + updated_region_.Swap(updated_region); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.h b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.h new file mode 100644 index 0000000000..3dedee9344 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_generator.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +// An interface to generate a DesktopFrame. +class DesktopFrameGenerator { + public: + DesktopFrameGenerator(); + virtual ~DesktopFrameGenerator(); + + virtual std::unique_ptr<DesktopFrame> GetNextFrame( + SharedMemoryFactory* factory) = 0; +}; + +// An interface to paint a DesktopFrame. This interface is used by +// PainterDesktopFrameGenerator. +class DesktopFramePainter { + public: + DesktopFramePainter(); + virtual ~DesktopFramePainter(); + + virtual bool Paint(DesktopFrame* frame, DesktopRegion* updated_region) = 0; +}; + +// An implementation of DesktopFrameGenerator to take care about the +// DesktopFrame size, filling updated_region(), etc, but leaves the real +// painting work to a DesktopFramePainter implementation. +class PainterDesktopFrameGenerator final : public DesktopFrameGenerator { + public: + PainterDesktopFrameGenerator(); + ~PainterDesktopFrameGenerator() override; + + std::unique_ptr<DesktopFrame> GetNextFrame( + SharedMemoryFactory* factory) override; + + // Sets the size of the frame which will be returned in next GetNextFrame() + // call. + DesktopSize* size(); + + // Decides whether BaseDesktopFrameGenerator returns a frame in next Capture() + // callback. If return_frame_ is true, BaseDesktopFrameGenerator will create a + // frame according to both size_ and SharedMemoryFactory input, and uses + // Paint() function to paint it. + void set_return_frame(bool return_frame); + + // Decides whether MockScreenCapturer returns a frame with updated regions. + // MockScreenCapturer will keep DesktopFrame::updated_region() empty if this + // field is false. + void set_provide_updated_region_hints(bool provide_updated_region_hints); + + // Decides whether MockScreenCapturer randomly enlarges updated regions in the + // DesktopFrame. Set this field to true to simulate an inaccurate updated + // regions' return from OS APIs. + void set_enlarge_updated_region(bool enlarge_updated_region); + + // The range to enlarge a updated region if `enlarge_updated_region_` is true. + // If this field is less than zero, it will be treated as zero, and + // `enlarge_updated_region_` will be ignored. + void set_enlarge_range(int enlarge_range); + + // Decides whether BaseDesktopFrameGenerator randomly add some updated regions + // in the DesktopFrame. Set this field to true to simulate an inaccurate + // updated regions' return from OS APIs. + void set_add_random_updated_region(bool add_random_updated_region); + + // Sets the painter object to do the real painting work, if no `painter_` has + // been set to this instance, the DesktopFrame returned by GetNextFrame() + // function will keep in an undefined but valid state. + // PainterDesktopFrameGenerator does not take ownership of the `painter`. + void set_desktop_frame_painter(DesktopFramePainter* painter); + + private: + DesktopSize size_; + bool return_frame_; + bool provide_updated_region_hints_; + bool enlarge_updated_region_; + int enlarge_range_; + bool add_random_updated_region_; + DesktopFramePainter* painter_; +}; + +// An implementation of DesktopFramePainter to paint black on +// mutable_updated_region(), and white elsewhere. +class BlackWhiteDesktopFramePainter final : public DesktopFramePainter { + public: + BlackWhiteDesktopFramePainter(); + ~BlackWhiteDesktopFramePainter() override; + + // The black regions of the frame which will be returned in next Paint() + // call. BlackWhiteDesktopFramePainter will draw a white frame, with black + // in the updated_region_. Each Paint() call will consume updated_region_. + DesktopRegion* updated_region(); + + // DesktopFramePainter interface. + bool Paint(DesktopFrame* frame, DesktopRegion* updated_region) override; + + private: + DesktopRegion updated_region_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.cc new file mode 100644 index 0000000000..6e4e42708e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.cc @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame_rotation.h" + +#include "rtc_base/checks.h" +#include "libyuv/include/libyuv/rotate_argb.h" + +namespace webrtc { + +namespace { + +libyuv::RotationMode ToLibyuvRotationMode(Rotation rotation) { + switch (rotation) { + case Rotation::CLOCK_WISE_0: + return libyuv::kRotate0; + case Rotation::CLOCK_WISE_90: + return libyuv::kRotate90; + case Rotation::CLOCK_WISE_180: + return libyuv::kRotate180; + case Rotation::CLOCK_WISE_270: + return libyuv::kRotate270; + } + RTC_DCHECK_NOTREACHED(); + return libyuv::kRotate0; +} + +DesktopRect RotateAndOffsetRect(DesktopRect rect, + DesktopSize size, + Rotation rotation, + DesktopVector offset) { + DesktopRect result = RotateRect(rect, size, rotation); + result.Translate(offset); + return result; +} + +} // namespace + +Rotation ReverseRotation(Rotation rotation) { + switch (rotation) { + case Rotation::CLOCK_WISE_0: + return rotation; + case Rotation::CLOCK_WISE_90: + return Rotation::CLOCK_WISE_270; + case Rotation::CLOCK_WISE_180: + return Rotation::CLOCK_WISE_180; + case Rotation::CLOCK_WISE_270: + return Rotation::CLOCK_WISE_90; + } + RTC_DCHECK_NOTREACHED(); + return Rotation::CLOCK_WISE_0; +} + +DesktopSize RotateSize(DesktopSize size, Rotation rotation) { + switch (rotation) { + case Rotation::CLOCK_WISE_0: + case Rotation::CLOCK_WISE_180: + return size; + case Rotation::CLOCK_WISE_90: + case Rotation::CLOCK_WISE_270: + return DesktopSize(size.height(), size.width()); + } + RTC_DCHECK_NOTREACHED(); + return DesktopSize(); +} + +DesktopRect RotateRect(DesktopRect rect, DesktopSize size, Rotation rotation) { + switch (rotation) { + case Rotation::CLOCK_WISE_0: + return rect; + case Rotation::CLOCK_WISE_90: + return DesktopRect::MakeXYWH(size.height() - rect.bottom(), rect.left(), + rect.height(), rect.width()); + case Rotation::CLOCK_WISE_180: + return DesktopRect::MakeXYWH(size.width() - rect.right(), + size.height() - rect.bottom(), rect.width(), + rect.height()); + case Rotation::CLOCK_WISE_270: + return DesktopRect::MakeXYWH(rect.top(), size.width() - rect.right(), + rect.height(), rect.width()); + } + RTC_DCHECK_NOTREACHED(); + return DesktopRect(); +} + +void RotateDesktopFrame(const DesktopFrame& source, + const DesktopRect& source_rect, + const Rotation& rotation, + const DesktopVector& target_offset, + DesktopFrame* target) { + RTC_DCHECK(target); + RTC_DCHECK(DesktopRect::MakeSize(source.size()).ContainsRect(source_rect)); + // The rectangle in `target`. + const DesktopRect target_rect = + RotateAndOffsetRect(source_rect, source.size(), rotation, target_offset); + RTC_DCHECK(DesktopRect::MakeSize(target->size()).ContainsRect(target_rect)); + + if (target_rect.is_empty()) { + return; + } + + int result = libyuv::ARGBRotate( + source.GetFrameDataAtPos(source_rect.top_left()), source.stride(), + target->GetFrameDataAtPos(target_rect.top_left()), target->stride(), + source_rect.width(), source_rect.height(), + ToLibyuvRotationMode(rotation)); + RTC_DCHECK_EQ(result, 0); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.h b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.h new file mode 100644 index 0000000000..6b51b2f883 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_ + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Represents the rotation of a DesktopFrame. +enum class Rotation { + CLOCK_WISE_0, + CLOCK_WISE_90, + CLOCK_WISE_180, + CLOCK_WISE_270, +}; + +// Rotates input DesktopFrame `source`, copies pixel in an unrotated rectangle +// `source_rect` into the target rectangle of another DesktopFrame `target`. +// Target rectangle here is the rotated `source_rect` plus `target_offset`. +// `rotation` specifies `source` to `target` rotation. `source_rect` is in +// `source` coordinate. `target_offset` is in `target` coordinate. +// This function triggers check failure if `source` does not cover the +// `source_rect`, or `target` does not cover the rotated `rect`. +void RotateDesktopFrame(const DesktopFrame& source, + const DesktopRect& source_rect, + const Rotation& rotation, + const DesktopVector& target_offset, + DesktopFrame* target); + +// Returns a reverse rotation of `rotation`. +Rotation ReverseRotation(Rotation rotation); + +// Returns a rotated DesktopSize of `size`. +DesktopSize RotateSize(DesktopSize size, Rotation rotation); + +// Returns a rotated DesktopRect of `rect`. The `size` represents the size of +// the DesktopFrame which `rect` belongs in. +DesktopRect RotateRect(DesktopRect rect, DesktopSize size, Rotation rotation); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation_unittest.cc new file mode 100644 index 0000000000..782ca63e61 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_rotation_unittest.cc @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame_rotation.h" + +#include <stdint.h> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/test_utils.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +// A DesktopFrame implementation which stores data in an external int array. +class ArrayDesktopFrame : public DesktopFrame { + public: + ArrayDesktopFrame(DesktopSize size, uint32_t* data); + ~ArrayDesktopFrame() override; +}; + +ArrayDesktopFrame::ArrayDesktopFrame(DesktopSize size, uint32_t* data) + : DesktopFrame(size, + size.width() * kBytesPerPixel, + reinterpret_cast<uint8_t*>(data), + nullptr) {} + +ArrayDesktopFrame::~ArrayDesktopFrame() = default; + +} // namespace + +TEST(DesktopFrameRotationTest, CopyRect3x4) { + // A DesktopFrame of 4-pixel width by 3-pixel height. + static uint32_t frame_pixels[] = { + 0, 1, 2, 3, // + 4, 5, 6, 7, // + 8, 9, 10, 11, // + }; + ArrayDesktopFrame frame(DesktopSize(4, 3), frame_pixels); + + { + BasicDesktopFrame target(DesktopSize(4, 3)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_0, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(frame, target)); + } + + // After Rotating clock-wise 90 degree + { + static uint32_t expected_pixels[] = { + 8, 4, 0, // + 9, 5, 1, // + 10, 6, 2, // + 11, 7, 3, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 4), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 4)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 180 degree + { + static uint32_t expected_pixels[] = { + 11, 10, 9, 8, // + 7, 6, 5, 4, // + 3, 2, 1, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(4, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(4, 3)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_180, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 270 degree + { + static uint32_t expected_pixels[] = { + 3, 7, 11, // + 2, 6, 10, // + 1, 5, 9, // + 0, 4, 8, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 4), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 4)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } +} + +TEST(DesktopFrameRotationTest, CopyRect3x5) { + // A DesktopFrame of 5-pixel width by 3-pixel height. + static uint32_t frame_pixels[] = { + 0, 1, 2, 3, 4, // + 5, 6, 7, 8, 9, // + 10, 11, 12, 13, 14, // + }; + ArrayDesktopFrame frame(DesktopSize(5, 3), frame_pixels); + + { + BasicDesktopFrame target(DesktopSize(5, 3)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_0, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, frame)); + } + + // After Rotating clock-wise 90 degree + { + static uint32_t expected_pixels[] = { + 10, 5, 0, // + 11, 6, 1, // + 12, 7, 2, // + 13, 8, 3, // + 14, 9, 4, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 180 degree + { + static uint32_t expected_pixels[]{ + 14, 13, 12, 11, 10, // + 9, 8, 7, 6, 5, // + 4, 3, 2, 1, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(5, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(5, 3)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_180, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 270 degree + { + static uint32_t expected_pixels[] = { + 4, 9, 14, // + 3, 8, 13, // + 2, 7, 12, // + 1, 6, 11, // + 0, 5, 10, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } +} + +TEST(DesktopFrameRotationTest, PartialCopyRect3x5) { + // A DesktopFrame of 5-pixel width by 3-pixel height. + static uint32_t frame_pixels[] = { + 0, 1, 2, 3, 4, // + 5, 6, 7, 8, 9, // + 10, 11, 12, 13, 14, // + }; + ArrayDesktopFrame frame(DesktopSize(5, 3), frame_pixels); + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, // + 0, 6, 7, 8, 0, // + 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(5, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(5, 3)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 1), + Rotation::CLOCK_WISE_0, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + { + static uint32_t expected_pixels[] = { + 0, 1, 2, 3, 0, // + 0, 6, 7, 8, 0, // + 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(5, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(5, 3)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 0, 3, 2), + Rotation::CLOCK_WISE_0, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 90 degree + { + static uint32_t expected_pixels[] = { + 0, 0, 0, // + 0, 6, 0, // + 0, 7, 0, // + 0, 8, 0, // + 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 1), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, // + 11, 6, 0, // + 12, 7, 0, // + 13, 8, 0, // + 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 2), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 180 degree + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, // + 0, 8, 7, 6, 0, // + 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(5, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(5, 3)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 1), + Rotation::CLOCK_WISE_180, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + { + static uint32_t expected_pixels[] = { + 0, 13, 12, 11, 0, // + 0, 8, 7, 6, 0, // + 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(5, 3), expected_pixels); + + BasicDesktopFrame target(DesktopSize(5, 3)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 2), + Rotation::CLOCK_WISE_180, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + // After Rotating clock-wise 270 degree + { + static uint32_t expected_pixels[] = { + 0, 0, 0, // + 0, 8, 0, // + 0, 7, 0, // + 0, 6, 0, // + 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 1, 3, 1), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, // + 3, 8, 0, // + 2, 7, 0, // + 1, 6, 0, // + 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(3, 5), expected_pixels); + + BasicDesktopFrame target(DesktopSize(3, 5)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeXYWH(1, 0, 3, 2), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + } +} + +TEST(DesktopFrameRotationTest, WithOffset) { + // A DesktopFrame of 4-pixel width by 3-pixel height. + static uint32_t frame_pixels[] = { + 0, 1, 2, 3, // + 4, 5, 6, 7, // + 8, 9, 10, 11, // + }; + ArrayDesktopFrame frame(DesktopSize(4, 3), frame_pixels); + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 1, 2, 3, 0, 0, 0, // + 0, 4, 5, 6, 7, 0, 0, 0, // + 0, 8, 9, 10, 11, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(8, 6), expected_pixels); + + BasicDesktopFrame target(DesktopSize(8, 6)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_0, DesktopVector(1, 1), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + target.mutable_updated_region()->Subtract( + DesktopRect::MakeOriginSize(DesktopVector(1, 1), frame.size())); + ASSERT_TRUE(target.updated_region().is_empty()); + } + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 11, 10, 9, 8, 0, 0, 0, // + 0, 7, 6, 5, 4, 0, 0, 0, // + 0, 3, 2, 1, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(8, 6), expected_pixels); + + BasicDesktopFrame target(DesktopSize(8, 6)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_180, DesktopVector(1, 1), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + target.mutable_updated_region()->Subtract( + DesktopRect::MakeOriginSize(DesktopVector(1, 1), frame.size())); + ASSERT_TRUE(target.updated_region().is_empty()); + } + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, 0, // + 0, 8, 4, 0, 0, 0, // + 0, 9, 5, 1, 0, 0, // + 0, 10, 6, 2, 0, 0, // + 0, 11, 7, 3, 0, 0, // + 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(6, 8), expected_pixels); + + BasicDesktopFrame target(DesktopSize(6, 8)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_90, DesktopVector(1, 1), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + target.mutable_updated_region()->Subtract( + DesktopRect::MakeXYWH(1, 1, 3, 4)); + ASSERT_TRUE(target.updated_region().is_empty()); + } + + { + static uint32_t expected_pixels[] = { + 0, 0, 0, 0, 0, 0, // + 0, 3, 7, 11, 0, 0, // + 0, 2, 6, 10, 0, 0, // + 0, 1, 5, 9, 0, 0, // + 0, 0, 4, 8, 0, 0, // + 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, // + }; + ArrayDesktopFrame expected(DesktopSize(6, 8), expected_pixels); + + BasicDesktopFrame target(DesktopSize(6, 8)); + ClearDesktopFrame(&target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_270, DesktopVector(1, 1), &target); + ASSERT_TRUE(DesktopFrameDataEquals(target, expected)); + target.mutable_updated_region()->Subtract( + DesktopRect::MakeXYWH(1, 1, 3, 4)); + ASSERT_TRUE(target.updated_region().is_empty()); + } +} + +// On a typical machine (Intel(R) Xeon(R) E5-1650 v3 @ 3.50GHz, with O2 +// optimization, the following case uses ~1.4s to finish. It means entirely +// rotating one 2048 x 1536 frame, which is a large enough number to cover most +// of desktop computer users, uses around 14ms. +TEST(DesktopFrameRotationTest, DISABLED_PerformanceTest) { + BasicDesktopFrame frame(DesktopSize(2048, 1536)); + BasicDesktopFrame target(DesktopSize(1536, 2048)); + BasicDesktopFrame target2(DesktopSize(2048, 1536)); + for (int i = 0; i < 100; i++) { + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_0, DesktopVector(), &target2); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_180, DesktopVector(), &target2); + } +} + +// On a typical machine (Intel(R) Xeon(R) E5-1650 v3 @ 3.50GHz, with O2 +// optimization, the following case uses ~6.7s to finish. It means entirely +// rotating one 4096 x 3072 frame uses around 67ms. +TEST(DesktopFrameRotationTest, DISABLED_PerformanceTestOnLargeScreen) { + BasicDesktopFrame frame(DesktopSize(4096, 3072)); + BasicDesktopFrame target(DesktopSize(3072, 4096)); + BasicDesktopFrame target2(DesktopSize(4096, 3072)); + for (int i = 0; i < 100; i++) { + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_90, DesktopVector(), &target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_270, DesktopVector(), &target); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_0, DesktopVector(), &target2); + RotateDesktopFrame(frame, DesktopRect::MakeSize(frame.size()), + Rotation::CLOCK_WISE_180, DesktopVector(), &target2); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_unittest.cc new file mode 100644 index 0000000000..ce0cbb45f5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_unittest.cc @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame.h" + +#include <memory> + +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/test_utils.h" +#include "rtc_base/arraysize.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +std::unique_ptr<DesktopFrame> CreateTestFrame(DesktopRect rect, + int pixels_value) { + DesktopSize size = rect.size(); + auto frame = std::make_unique<BasicDesktopFrame>(size); + frame->set_top_left(rect.top_left()); + memset(frame->data(), pixels_value, frame->stride() * size.height()); + return frame; +} + +struct TestData { + const char* description; + DesktopRect dest_frame_rect; + DesktopRect src_frame_rect; + double horizontal_scale; + double vertical_scale; + DesktopRect expected_overlap_rect; +}; + +void RunTest(const TestData& test) { + // Copy a source frame with all bits set into a dest frame with none set. + auto dest_frame = CreateTestFrame(test.dest_frame_rect, 0); + auto src_frame = CreateTestFrame(test.src_frame_rect, 0xff); + + dest_frame->CopyIntersectingPixelsFrom( + *src_frame, test.horizontal_scale, test.vertical_scale); + + // Translate the expected overlap rect to be relative to the dest frame/rect. + DesktopVector dest_frame_origin = test.dest_frame_rect.top_left(); + DesktopRect relative_expected_overlap_rect = test.expected_overlap_rect; + relative_expected_overlap_rect.Translate(-dest_frame_origin.x(), + -dest_frame_origin.y()); + + // Confirm bits are now set in the dest frame if & only if they fall in the + // expected range. + for (int y = 0; y < dest_frame->size().height(); ++y) { + SCOPED_TRACE(y); + + for (int x = 0; x < dest_frame->size().width(); ++x) { + SCOPED_TRACE(x); + + DesktopVector point(x, y); + uint8_t* data = dest_frame->GetFrameDataAtPos(point); + uint32_t pixel_value = *reinterpret_cast<uint32_t*>(data); + bool was_copied = pixel_value == 0xffffffff; + ASSERT_TRUE(was_copied || pixel_value == 0); + + bool expected_to_be_copied = + relative_expected_overlap_rect.Contains(point); + + ASSERT_EQ(was_copied, expected_to_be_copied); + } + } +} + +void RunTests(const TestData* tests, int num_tests) { + for (int i = 0; i < num_tests; i++) { + const TestData& test = tests[i]; + + SCOPED_TRACE(test.description); + + RunTest(test); + } +} + +} // namespace + +TEST(DesktopFrameTest, CopyIntersectingPixelsMatchingRects) { + const TestData tests[] = { + {"0 origin", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 2, 2)}, + + {"Negative origin", + DesktopRect::MakeXYWH(-1, -1, 2, 2), + DesktopRect::MakeXYWH(-1, -1, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(-1, -1, 2, 2)} + }; + + RunTests(tests, arraysize(tests)); +} + +TEST(DesktopFrameTest, CopyIntersectingPixelsMatchingRectsScaled) { + // The scale factors shouldn't affect matching rects (they're only applied + // to any difference between the origins) + const TestData tests[] = { + {"0 origin 2x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 2, 2), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 0, 2, 2)}, + + {"0 origin 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 2, 2), + 0.5, 0.5, + DesktopRect::MakeXYWH(0, 0, 2, 2)}, + + {"Negative origin 2x", + DesktopRect::MakeXYWH(-1, -1, 2, 2), + DesktopRect::MakeXYWH(-1, -1, 2, 2), + 2.0, 2.0, + DesktopRect::MakeXYWH(-1, -1, 2, 2)}, + + {"Negative origin 0.5x", + DesktopRect::MakeXYWH(-1, -1, 2, 2), + DesktopRect::MakeXYWH(-1, -1, 2, 2), + 0.5, 0.5, + DesktopRect::MakeXYWH(-1, -1, 2, 2)} + }; + + RunTests(tests, arraysize(tests)); +} + +TEST(DesktopFrameTest, CopyIntersectingPixelsFullyContainedRects) { + const TestData tests[] = { + {"0 origin top left", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 1, 1), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 1, 1)}, + + {"0 origin bottom right", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(1, 1, 1, 1), + 1.0, 1.0, + DesktopRect::MakeXYWH(1, 1, 1, 1)}, + + {"Negative origin bottom left", + DesktopRect::MakeXYWH(-1, -1, 2, 2), + DesktopRect::MakeXYWH(-1, 0, 1, 1), + 1.0, 1.0, + DesktopRect::MakeXYWH(-1, 0, 1, 1)} + }; + + RunTests(tests, arraysize(tests)); +} + +TEST(DesktopFrameTest, CopyIntersectingPixelsFullyContainedRectsScaled) { + const TestData tests[] = { + {"0 origin top left 2x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 1, 1), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 0, 1, 1)}, + + {"0 origin top left 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 0, 1, 1), + 0.5, 0.5, + DesktopRect::MakeXYWH(0, 0, 1, 1)}, + + {"0 origin bottom left 2x", + DesktopRect::MakeXYWH(0, 0, 4, 4), + DesktopRect::MakeXYWH(1, 1, 2, 2), + 2.0, 2.0, + DesktopRect::MakeXYWH(2, 2, 2, 2)}, + + {"0 origin bottom middle 2x/1x", + DesktopRect::MakeXYWH(0, 0, 4, 3), + DesktopRect::MakeXYWH(1, 1, 2, 2), + 2.0, 1.0, + DesktopRect::MakeXYWH(2, 1, 2, 2)}, + + {"0 origin middle 0.5x", + DesktopRect::MakeXYWH(0, 0, 3, 3), + DesktopRect::MakeXYWH(2, 2, 1, 1), + 0.5, 0.5, + DesktopRect::MakeXYWH(1, 1, 1, 1)}, + + {"Negative origin bottom left 2x", + DesktopRect::MakeXYWH(-1, -1, 3, 3), + DesktopRect::MakeXYWH(-1, 0, 1, 1), + 2.0, 2.0, + DesktopRect::MakeXYWH(-1, 1, 1, 1)}, + + {"Negative origin near middle 0.5x", + DesktopRect::MakeXYWH(-2, -2, 2, 2), + DesktopRect::MakeXYWH(0, 0, 1, 1), + 0.5, 0.5, + DesktopRect::MakeXYWH(-1, -1, 1, 1)} + }; + + RunTests(tests, arraysize(tests)); +} + + +TEST(DesktopFrameTest, CopyIntersectingPixelsPartiallyContainedRects) { + const TestData tests[] = { + {"Top left", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-1, -1, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 1, 1)}, + + {"Top right", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(1, -1, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(1, 0, 1, 1)}, + + {"Bottom right", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(1, 1, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(1, 1, 1, 1)}, + + {"Bottom left", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-1, 1, 2, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 1, 1, 1)} + }; + + RunTests(tests, arraysize(tests)); +} + +TEST(DesktopFrameTest, CopyIntersectingPixelsPartiallyContainedRectsScaled) { + const TestData tests[] = { + {"Top left 2x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-1, -1, 3, 3), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 0, 1, 1)}, + + {"Top right 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(2, -2, 2, 2), + 0.5, 0.5, + DesktopRect::MakeXYWH(1, 0, 1, 1)}, + + {"Bottom right 2x", + DesktopRect::MakeXYWH(0, 0, 3, 3), + DesktopRect::MakeXYWH(-1, 1, 3, 3), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 2, 1, 1)}, + + {"Bottom left 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-2, 2, 2, 2), + 0.5, 0.5, + DesktopRect::MakeXYWH(0, 1, 1, 1)} + }; + + RunTests(tests, arraysize(tests)); +} + + +TEST(DesktopFrameTest, CopyIntersectingPixelsUncontainedRects) { + const TestData tests[] = { + {"Left", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-1, 0, 1, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + {"Top", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, -1, 2, 1), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + {"Right", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(2, 0, 1, 2), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + + {"Bottom", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 2, 2, 1), + 1.0, 1.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)} + }; + + RunTests(tests, arraysize(tests)); +} + +TEST(DesktopFrameTest, CopyIntersectingPixelsUncontainedRectsScaled) { + const TestData tests[] = { + {"Left 2x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(-1, 0, 2, 2), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + {"Top 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, -2, 2, 1), + 0.5, 0.5, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + {"Right 2x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(1, 0, 1, 2), + 2.0, 2.0, + DesktopRect::MakeXYWH(0, 0, 0, 0)}, + + + {"Bottom 0.5x", + DesktopRect::MakeXYWH(0, 0, 2, 2), + DesktopRect::MakeXYWH(0, 4, 2, 1), + 0.5, 0.5, + DesktopRect::MakeXYWH(0, 0, 0, 0)} + }; + + RunTests(tests, arraysize(tests)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.cc new file mode 100644 index 0000000000..262ebbdec0 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_frame_win.h" + +#include <utility> + +#include "rtc_base/logging.h" + +namespace webrtc { + +DesktopFrameWin::DesktopFrameWin(DesktopSize size, + int stride, + uint8_t* data, + std::unique_ptr<SharedMemory> shared_memory, + HBITMAP bitmap) + : DesktopFrame(size, stride, data, shared_memory.get()), + bitmap_(bitmap), + owned_shared_memory_(std::move(shared_memory)) {} + +DesktopFrameWin::~DesktopFrameWin() { + DeleteObject(bitmap_); +} + +// static +std::unique_ptr<DesktopFrameWin> DesktopFrameWin::Create( + DesktopSize size, + SharedMemoryFactory* shared_memory_factory, + HDC hdc) { + int bytes_per_row = size.width() * kBytesPerPixel; + int buffer_size = bytes_per_row * size.height(); + + // Describe a device independent bitmap (DIB) that is the size of the desktop. + BITMAPINFO bmi = {}; + bmi.bmiHeader.biHeight = -size.height(); + bmi.bmiHeader.biWidth = size.width(); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = DesktopFrameWin::kBytesPerPixel * 8; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biSizeImage = bytes_per_row * size.height(); + + std::unique_ptr<SharedMemory> shared_memory; + HANDLE section_handle = nullptr; + if (shared_memory_factory) { + shared_memory = shared_memory_factory->CreateSharedMemory(buffer_size); + if (!shared_memory) { + RTC_LOG(LS_WARNING) << "Failed to allocate shared memory"; + return nullptr; + } + section_handle = shared_memory->handle(); + } + void* data = nullptr; + HBITMAP bitmap = + CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &data, section_handle, 0); + if (!bitmap) { + RTC_LOG(LS_WARNING) << "Failed to allocate new window frame " + << GetLastError(); + return nullptr; + } + + return std::unique_ptr<DesktopFrameWin>( + new DesktopFrameWin(size, bytes_per_row, reinterpret_cast<uint8_t*>(data), + std::move(shared_memory), bitmap)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.h b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.h new file mode 100644 index 0000000000..f8faad6777 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_frame_win.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ + +#include <windows.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +// DesktopFrame implementation used by screen and window captures on Windows. +// Frame data is stored in a GDI bitmap. +class DesktopFrameWin : public DesktopFrame { + public: + ~DesktopFrameWin() override; + + DesktopFrameWin(const DesktopFrameWin&) = delete; + DesktopFrameWin& operator=(const DesktopFrameWin&) = delete; + + static std::unique_ptr<DesktopFrameWin> + Create(DesktopSize size, SharedMemoryFactory* shared_memory_factory, HDC hdc); + + HBITMAP bitmap() { return bitmap_; } + + private: + DesktopFrameWin(DesktopSize size, + int stride, + uint8_t* data, + std::unique_ptr<SharedMemory> shared_memory, + HBITMAP bitmap); + + HBITMAP bitmap_; + std::unique_ptr<SharedMemory> owned_shared_memory_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.cc new file mode 100644 index 0000000000..e0a5d7af83 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.cc @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_geometry.h" + +#include <algorithm> +#include <cmath> + +namespace webrtc { + +bool DesktopRect::Contains(const DesktopVector& point) const { + return point.x() >= left() && point.x() < right() && point.y() >= top() && + point.y() < bottom(); +} + +bool DesktopRect::ContainsRect(const DesktopRect& rect) const { + return rect.left() >= left() && rect.right() <= right() && + rect.top() >= top() && rect.bottom() <= bottom(); +} + +void DesktopRect::IntersectWith(const DesktopRect& rect) { + left_ = std::max(left(), rect.left()); + top_ = std::max(top(), rect.top()); + right_ = std::min(right(), rect.right()); + bottom_ = std::min(bottom(), rect.bottom()); + if (is_empty()) { + left_ = 0; + top_ = 0; + right_ = 0; + bottom_ = 0; + } +} + +void DesktopRect::UnionWith(const DesktopRect& rect) { + if (is_empty()) { + *this = rect; + return; + } + + if (rect.is_empty()) { + return; + } + + left_ = std::min(left(), rect.left()); + top_ = std::min(top(), rect.top()); + right_ = std::max(right(), rect.right()); + bottom_ = std::max(bottom(), rect.bottom()); +} + +void DesktopRect::Translate(int32_t dx, int32_t dy) { + left_ += dx; + top_ += dy; + right_ += dx; + bottom_ += dy; +} + +void DesktopRect::Extend(int32_t left_offset, + int32_t top_offset, + int32_t right_offset, + int32_t bottom_offset) { + left_ -= left_offset; + top_ -= top_offset; + right_ += right_offset; + bottom_ += bottom_offset; +} + +void DesktopRect::Scale(double horizontal, double vertical) { + right_ += static_cast<int>(std::round(width() * (horizontal - 1))); + bottom_ += static_cast<int>(std::round(height() * (vertical - 1))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.h b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.h new file mode 100644 index 0000000000..691455df57 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ + +#include <stdint.h> + +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// A vector in the 2D integer space. E.g. can be used to represent screen DPI. +class DesktopVector { + public: + DesktopVector() : x_(0), y_(0) {} + DesktopVector(int32_t x, int32_t y) : x_(x), y_(y) {} + + int32_t x() const { return x_; } + int32_t y() const { return y_; } + bool is_zero() const { return x_ == 0 && y_ == 0; } + + bool equals(const DesktopVector& other) const { + return x_ == other.x_ && y_ == other.y_; + } + + void set(int32_t x, int32_t y) { + x_ = x; + y_ = y; + } + + DesktopVector add(const DesktopVector& other) const { + return DesktopVector(x() + other.x(), y() + other.y()); + } + DesktopVector subtract(const DesktopVector& other) const { + return DesktopVector(x() - other.x(), y() - other.y()); + } + + DesktopVector operator-() const { return DesktopVector(-x_, -y_); } + + private: + int32_t x_; + int32_t y_; +}; + +// Type used to represent screen/window size. +class DesktopSize { + public: + DesktopSize() : width_(0), height_(0) {} + DesktopSize(int32_t width, int32_t height) : width_(width), height_(height) {} + + int32_t width() const { return width_; } + int32_t height() const { return height_; } + + bool is_empty() const { return width_ <= 0 || height_ <= 0; } + + bool equals(const DesktopSize& other) const { + return width_ == other.width_ && height_ == other.height_; + } + + void set(int32_t width, int32_t height) { + width_ = width; + height_ = height; + } + + private: + int32_t width_; + int32_t height_; +}; + +// Represents a rectangle on the screen. +class RTC_EXPORT DesktopRect { + public: + static DesktopRect MakeSize(const DesktopSize& size) { + return DesktopRect(0, 0, size.width(), size.height()); + } + static DesktopRect MakeWH(int32_t width, int32_t height) { + return DesktopRect(0, 0, width, height); + } + static DesktopRect MakeXYWH(int32_t x, + int32_t y, + int32_t width, + int32_t height) { + return DesktopRect(x, y, x + width, y + height); + } + static DesktopRect MakeLTRB(int32_t left, + int32_t top, + int32_t right, + int32_t bottom) { + return DesktopRect(left, top, right, bottom); + } + static DesktopRect MakeOriginSize(const DesktopVector& origin, + const DesktopSize& size) { + return MakeXYWH(origin.x(), origin.y(), size.width(), size.height()); + } + + DesktopRect() : left_(0), top_(0), right_(0), bottom_(0) {} + + int32_t left() const { return left_; } + int32_t top() const { return top_; } + int32_t right() const { return right_; } + int32_t bottom() const { return bottom_; } + int32_t width() const { return right_ - left_; } + int32_t height() const { return bottom_ - top_; } + + void set_width(int32_t width) { right_ = left_ + width; } + void set_height(int32_t height) { bottom_ = top_ + height; } + + DesktopVector top_left() const { return DesktopVector(left_, top_); } + DesktopSize size() const { return DesktopSize(width(), height()); } + + bool is_empty() const { return left_ >= right_ || top_ >= bottom_; } + + bool equals(const DesktopRect& other) const { + return left_ == other.left_ && top_ == other.top_ && + right_ == other.right_ && bottom_ == other.bottom_; + } + + // Returns true if `point` lies within the rectangle boundaries. + bool Contains(const DesktopVector& point) const; + + // Returns true if `rect` lies within the boundaries of this rectangle. + bool ContainsRect(const DesktopRect& rect) const; + + // Finds intersection with `rect`. + void IntersectWith(const DesktopRect& rect); + + // Extends the rectangle to cover `rect`. If `this` is empty, replaces `this` + // with `rect`; if `rect` is empty, this function takes no effect. + void UnionWith(const DesktopRect& rect); + + // Adds (dx, dy) to the position of the rectangle. + void Translate(int32_t dx, int32_t dy); + void Translate(DesktopVector d) { Translate(d.x(), d.y()); } + + // Enlarges current DesktopRect by subtracting `left_offset` and `top_offset` + // from `left_` and `top_`, and adding `right_offset` and `bottom_offset` to + // `right_` and `bottom_`. This function does not normalize the result, so + // `left_` and `top_` may be less than zero or larger than `right_` and + // `bottom_`. + void Extend(int32_t left_offset, + int32_t top_offset, + int32_t right_offset, + int32_t bottom_offset); + + // Scales current DesktopRect. This function does not impact the `top_` and + // `left_`. + void Scale(double horizontal, double vertical); + + private: + DesktopRect(int32_t left, int32_t top, int32_t right, int32_t bottom) + : left_(left), top_(top), right_(right), bottom_(bottom) {} + + int32_t left_; + int32_t top_; + int32_t right_; + int32_t bottom_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_geometry_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry_unittest.cc new file mode 100644 index 0000000000..f4a07fa46b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_geometry_unittest.cc @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_geometry.h" + +#include "test/gtest.h" + +namespace webrtc { + +TEST(DesktopRectTest, UnionBetweenTwoNonEmptyRects) { + DesktopRect rect = DesktopRect::MakeLTRB(1, 1, 2, 2); + rect.UnionWith(DesktopRect::MakeLTRB(-2, -2, -1, -1)); + ASSERT_TRUE(rect.equals(DesktopRect::MakeLTRB(-2, -2, 2, 2))); +} + +TEST(DesktopRectTest, UnionWithEmptyRect) { + DesktopRect rect = DesktopRect::MakeWH(1, 1); + rect.UnionWith(DesktopRect()); + ASSERT_TRUE(rect.equals(DesktopRect::MakeWH(1, 1))); + + rect = DesktopRect::MakeXYWH(1, 1, 2, 2); + rect.UnionWith(DesktopRect()); + ASSERT_TRUE(rect.equals(DesktopRect::MakeXYWH(1, 1, 2, 2))); + + rect = DesktopRect::MakeXYWH(1, 1, 2, 2); + rect.UnionWith(DesktopRect::MakeXYWH(3, 3, 0, 0)); + ASSERT_TRUE(rect.equals(DesktopRect::MakeXYWH(1, 1, 2, 2))); +} + +TEST(DesktopRectTest, EmptyRectUnionWithNonEmptyOne) { + DesktopRect rect; + rect.UnionWith(DesktopRect::MakeWH(1, 1)); + ASSERT_TRUE(rect.equals(DesktopRect::MakeWH(1, 1))); + + rect = DesktopRect(); + rect.UnionWith(DesktopRect::MakeXYWH(1, 1, 2, 2)); + ASSERT_TRUE(rect.equals(DesktopRect::MakeXYWH(1, 1, 2, 2))); + + rect = DesktopRect::MakeXYWH(3, 3, 0, 0); + rect.UnionWith(DesktopRect::MakeXYWH(1, 1, 2, 2)); + ASSERT_TRUE(rect.equals(DesktopRect::MakeXYWH(1, 1, 2, 2))); +} + +TEST(DesktopRectTest, EmptyRectUnionWithEmptyOne) { + DesktopRect rect; + rect.UnionWith(DesktopRect()); + ASSERT_TRUE(rect.is_empty()); + + rect = DesktopRect::MakeXYWH(1, 1, 0, 0); + rect.UnionWith(DesktopRect()); + ASSERT_TRUE(rect.is_empty()); + + rect = DesktopRect(); + rect.UnionWith(DesktopRect::MakeXYWH(1, 1, 0, 0)); + ASSERT_TRUE(rect.is_empty()); + + rect = DesktopRect::MakeXYWH(1, 1, 0, 0); + rect.UnionWith(DesktopRect::MakeXYWH(-1, -1, 0, 0)); + ASSERT_TRUE(rect.is_empty()); +} + +TEST(DesktopRectTest, Scale) { + DesktopRect rect = DesktopRect::MakeXYWH(100, 100, 100, 100); + rect.Scale(1.1, 1.1); + ASSERT_EQ(rect.top(), 100); + ASSERT_EQ(rect.left(), 100); + ASSERT_EQ(rect.width(), 110); + ASSERT_EQ(rect.height(), 110); + + rect = DesktopRect::MakeXYWH(100, 100, 100, 100); + rect.Scale(0.01, 0.01); + ASSERT_EQ(rect.top(), 100); + ASSERT_EQ(rect.left(), 100); + ASSERT_EQ(rect.width(), 1); + ASSERT_EQ(rect.height(), 1); + + rect = DesktopRect::MakeXYWH(100, 100, 100, 100); + rect.Scale(1.1, 0.9); + ASSERT_EQ(rect.top(), 100); + ASSERT_EQ(rect.left(), 100); + ASSERT_EQ(rect.width(), 110); + ASSERT_EQ(rect.height(), 90); + + rect = DesktopRect::MakeXYWH(0, 0, 100, 100); + rect.Scale(1.1, 1.1); + ASSERT_EQ(rect.top(), 0); + ASSERT_EQ(rect.left(), 0); + ASSERT_EQ(rect.width(), 110); + ASSERT_EQ(rect.height(), 110); + + rect = DesktopRect::MakeXYWH(0, 100, 100, 100); + rect.Scale(1.1, 1.1); + ASSERT_EQ(rect.top(), 100); + ASSERT_EQ(rect.left(), 0); + ASSERT_EQ(rect.width(), 110); + ASSERT_EQ(rect.height(), 110); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_region.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_region.cc new file mode 100644 index 0000000000..2c87c11eb3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_region.cc @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_region.h" + +#include <algorithm> +#include <utility> + +#include "rtc_base/checks.h" + +namespace webrtc { + +DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right) + : left(left), right(right) {} + +DesktopRegion::Row::Row(const Row&) = default; +DesktopRegion::Row::Row(Row&&) = default; + +DesktopRegion::Row::Row(int32_t top, int32_t bottom) + : top(top), bottom(bottom) {} + +DesktopRegion::Row::~Row() {} + +DesktopRegion::DesktopRegion() {} + +DesktopRegion::DesktopRegion(const DesktopRect& rect) { + AddRect(rect); +} + +DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) { + AddRects(rects, count); +} + +DesktopRegion::DesktopRegion(const DesktopRegion& other) { + *this = other; +} + +DesktopRegion::~DesktopRegion() { + Clear(); +} + +DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) { + Clear(); + rows_ = other.rows_; + for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { + // Copy each row. + Row* row = it->second; + it->second = new Row(*row); + } + return *this; +} + +bool DesktopRegion::Equals(const DesktopRegion& region) const { + // Iterate over rows of the tow regions and compare each row. + Rows::const_iterator it1 = rows_.begin(); + Rows::const_iterator it2 = region.rows_.begin(); + while (it1 != rows_.end()) { + if (it2 == region.rows_.end() || it1->first != it2->first || + it1->second->top != it2->second->top || + it1->second->bottom != it2->second->bottom || + it1->second->spans != it2->second->spans) { + return false; + } + ++it1; + ++it2; + } + return it2 == region.rows_.end(); +} + +void DesktopRegion::Clear() { + for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) { + delete row->second; + } + rows_.clear(); +} + +void DesktopRegion::SetRect(const DesktopRect& rect) { + Clear(); + AddRect(rect); +} + +void DesktopRegion::AddRect(const DesktopRect& rect) { + if (rect.is_empty()) + return; + + // Top of the part of the `rect` that hasn't been inserted yet. Increased as + // we iterate over the rows until it reaches `rect.bottom()`. + int top = rect.top(); + + // Iterate over all rows that may intersect with `rect` and add new rows when + // necessary. + Rows::iterator row = rows_.upper_bound(top); + while (top < rect.bottom()) { + if (row == rows_.end() || top < row->second->top) { + // If `top` is above the top of the current `row` then add a new row above + // the current one. + int32_t bottom = rect.bottom(); + if (row != rows_.end() && row->second->top < bottom) + bottom = row->second->top; + row = rows_.insert(row, Rows::value_type(bottom, new Row(top, bottom))); + } else if (top > row->second->top) { + // If the `top` falls in the middle of the `row` then split `row` into + // two, at `top`, and leave `row` referring to the lower of the two, + // ready to insert a new span into. + RTC_DCHECK_LE(top, row->second->bottom); + Rows::iterator new_row = rows_.insert( + row, Rows::value_type(top, new Row(row->second->top, top))); + row->second->top = top; + new_row->second->spans = row->second->spans; + } + + if (rect.bottom() < row->second->bottom) { + // If the bottom of the `rect` falls in the middle of the `row` split + // `row` into two, at `top`, and leave `row` referring to the upper of + // the two, ready to insert a new span into. + Rows::iterator new_row = rows_.insert( + row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom()))); + row->second->top = rect.bottom(); + new_row->second->spans = row->second->spans; + row = new_row; + } + + // Add a new span to the current row. + AddSpanToRow(row->second, rect.left(), rect.right()); + top = row->second->bottom; + + MergeWithPrecedingRow(row); + + // Move to the next row. + ++row; + } + + if (row != rows_.end()) + MergeWithPrecedingRow(row); +} + +void DesktopRegion::AddRects(const DesktopRect* rects, int count) { + for (int i = 0; i < count; ++i) { + AddRect(rects[i]); + } +} + +void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) { + RTC_DCHECK(row != rows_.end()); + + if (row != rows_.begin()) { + Rows::iterator previous_row = row; + previous_row--; + + // If `row` and `previous_row` are next to each other and contain the same + // set of spans then they can be merged. + if (previous_row->second->bottom == row->second->top && + previous_row->second->spans == row->second->spans) { + row->second->top = previous_row->second->top; + delete previous_row->second; + rows_.erase(previous_row); + } + } +} + +void DesktopRegion::AddRegion(const DesktopRegion& region) { + // TODO(sergeyu): This function is not optimized - potentially it can iterate + // over rows of the two regions similar to how it works in Intersect(). + for (Iterator it(region); !it.IsAtEnd(); it.Advance()) { + AddRect(it.rect()); + } +} + +void DesktopRegion::Intersect(const DesktopRegion& region1, + const DesktopRegion& region2) { + Clear(); + + Rows::const_iterator it1 = region1.rows_.begin(); + Rows::const_iterator end1 = region1.rows_.end(); + Rows::const_iterator it2 = region2.rows_.begin(); + Rows::const_iterator end2 = region2.rows_.end(); + if (it1 == end1 || it2 == end2) + return; + + while (it1 != end1 && it2 != end2) { + // Arrange for `it1` to always be the top-most of the rows. + if (it2->second->top < it1->second->top) { + std::swap(it1, it2); + std::swap(end1, end2); + } + + // Skip `it1` if it doesn't intersect `it2` at all. + if (it1->second->bottom <= it2->second->top) { + ++it1; + continue; + } + + // Top of the `it1` row is above the top of `it2`, so top of the + // intersection is always the top of `it2`. + int32_t top = it2->second->top; + int32_t bottom = std::min(it1->second->bottom, it2->second->bottom); + + Rows::iterator new_row = rows_.insert( + rows_.end(), Rows::value_type(bottom, new Row(top, bottom))); + IntersectRows(it1->second->spans, it2->second->spans, + &new_row->second->spans); + if (new_row->second->spans.empty()) { + delete new_row->second; + rows_.erase(new_row); + } else { + MergeWithPrecedingRow(new_row); + } + + // If `it1` was completely consumed, move to the next one. + if (it1->second->bottom == bottom) + ++it1; + // If `it2` was completely consumed, move to the next one. + if (it2->second->bottom == bottom) + ++it2; + } +} + +// static +void DesktopRegion::IntersectRows(const RowSpanSet& set1, + const RowSpanSet& set2, + RowSpanSet* output) { + RowSpanSet::const_iterator it1 = set1.begin(); + RowSpanSet::const_iterator end1 = set1.end(); + RowSpanSet::const_iterator it2 = set2.begin(); + RowSpanSet::const_iterator end2 = set2.end(); + RTC_DCHECK(it1 != end1 && it2 != end2); + + do { + // Arrange for `it1` to always be the left-most of the spans. + if (it2->left < it1->left) { + std::swap(it1, it2); + std::swap(end1, end2); + } + + // Skip `it1` if it doesn't intersect `it2` at all. + if (it1->right <= it2->left) { + ++it1; + continue; + } + + int32_t left = it2->left; + int32_t right = std::min(it1->right, it2->right); + RTC_DCHECK_LT(left, right); + + output->push_back(RowSpan(left, right)); + + // If `it1` was completely consumed, move to the next one. + if (it1->right == right) + ++it1; + // If `it2` was completely consumed, move to the next one. + if (it2->right == right) + ++it2; + } while (it1 != end1 && it2 != end2); +} + +void DesktopRegion::IntersectWith(const DesktopRegion& region) { + DesktopRegion old_region; + Swap(&old_region); + Intersect(old_region, region); +} + +void DesktopRegion::IntersectWith(const DesktopRect& rect) { + DesktopRegion region; + region.AddRect(rect); + IntersectWith(region); +} + +void DesktopRegion::Subtract(const DesktopRegion& region) { + if (region.rows_.empty()) + return; + + // `row_b` refers to the current row being subtracted. + Rows::const_iterator row_b = region.rows_.begin(); + + // Current vertical position at which subtraction is happening. + int top = row_b->second->top; + + // `row_a` refers to the current row we are subtracting from. Skip all rows + // above `top`. + Rows::iterator row_a = rows_.upper_bound(top); + + // Step through rows of the both regions subtracting content of `row_b` from + // `row_a`. + while (row_a != rows_.end() && row_b != region.rows_.end()) { + // Skip `row_a` if it doesn't intersect with the `row_b`. + if (row_a->second->bottom <= top) { + // Each output row is merged with previously-processed rows before further + // rows are processed. + MergeWithPrecedingRow(row_a); + ++row_a; + continue; + } + + if (top > row_a->second->top) { + // If `top` falls in the middle of `row_a` then split `row_a` into two, at + // `top`, and leave `row_a` referring to the lower of the two, ready to + // subtract spans from. + RTC_DCHECK_LE(top, row_a->second->bottom); + Rows::iterator new_row = rows_.insert( + row_a, Rows::value_type(top, new Row(row_a->second->top, top))); + row_a->second->top = top; + new_row->second->spans = row_a->second->spans; + } else if (top < row_a->second->top) { + // If the `top` is above `row_a` then skip the range between `top` and + // top of `row_a` because it's empty. + top = row_a->second->top; + if (top >= row_b->second->bottom) { + ++row_b; + if (row_b != region.rows_.end()) + top = row_b->second->top; + continue; + } + } + + if (row_b->second->bottom < row_a->second->bottom) { + // If the bottom of `row_b` falls in the middle of the `row_a` split + // `row_a` into two, at `top`, and leave `row_a` referring to the upper of + // the two, ready to subtract spans from. + int bottom = row_b->second->bottom; + Rows::iterator new_row = + rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom))); + row_a->second->top = bottom; + new_row->second->spans = row_a->second->spans; + row_a = new_row; + } + + // At this point the vertical range covered by `row_a` lays within the + // range covered by `row_b`. Subtract `row_b` spans from `row_a`. + RowSpanSet new_spans; + SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans); + new_spans.swap(row_a->second->spans); + top = row_a->second->bottom; + + if (top >= row_b->second->bottom) { + ++row_b; + if (row_b != region.rows_.end()) + top = row_b->second->top; + } + + // Check if the row is empty after subtraction and delete it. Otherwise move + // to the next one. + if (row_a->second->spans.empty()) { + Rows::iterator row_to_delete = row_a; + ++row_a; + delete row_to_delete->second; + rows_.erase(row_to_delete); + } else { + MergeWithPrecedingRow(row_a); + ++row_a; + } + } + + if (row_a != rows_.end()) + MergeWithPrecedingRow(row_a); +} + +void DesktopRegion::Subtract(const DesktopRect& rect) { + DesktopRegion region; + region.AddRect(rect); + Subtract(region); +} + +void DesktopRegion::Translate(int32_t dx, int32_t dy) { + Rows new_rows; + + for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { + Row* row = it->second; + + row->top += dy; + row->bottom += dy; + + if (dx != 0) { + // Translate each span. + for (RowSpanSet::iterator span = row->spans.begin(); + span != row->spans.end(); ++span) { + span->left += dx; + span->right += dx; + } + } + + if (dy != 0) + new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row)); + } + + if (dy != 0) + new_rows.swap(rows_); +} + +void DesktopRegion::Swap(DesktopRegion* region) { + rows_.swap(region->rows_); +} + +// static +bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) { + return r.right < value; +} + +// static +bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) { + return r.left < value; +} + +// static +void DesktopRegion::AddSpanToRow(Row* row, int left, int right) { + // First check if the new span is located to the right of all existing spans. + // This is an optimization to avoid binary search in the case when rectangles + // are inserted sequentially from left to right. + if (row->spans.empty() || left > row->spans.back().right) { + row->spans.push_back(RowSpan(left, right)); + return; + } + + // Find the first span that ends at or after `left`. + RowSpanSet::iterator start = std::lower_bound( + row->spans.begin(), row->spans.end(), left, CompareSpanRight); + RTC_DCHECK(start < row->spans.end()); + + // Find the first span that starts after `right`. + RowSpanSet::iterator end = + std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft); + if (end == row->spans.begin()) { + // There are no overlaps. Just insert the new span at the beginning. + row->spans.insert(row->spans.begin(), RowSpan(left, right)); + return; + } + + // Move end to the left, so that it points the last span that ends at or + // before `right`. + end--; + + // At this point [start, end] is the range of spans that intersect with the + // new one. + if (end < start) { + // There are no overlaps. Just insert the new span at the correct position. + row->spans.insert(start, RowSpan(left, right)); + return; + } + + left = std::min(left, start->left); + right = std::max(right, end->right); + + // Replace range [start, end] with the new span. + *start = RowSpan(left, right); + ++start; + ++end; + if (start < end) + row->spans.erase(start, end); +} + +// static +bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) { + // Find the first span that starts at or after `span.left` and then check if + // it's the same span. + RowSpanSet::const_iterator it = std::lower_bound( + row.spans.begin(), row.spans.end(), span.left, CompareSpanLeft); + return it != row.spans.end() && *it == span; +} + +// static +void DesktopRegion::SubtractRows(const RowSpanSet& set_a, + const RowSpanSet& set_b, + RowSpanSet* output) { + RTC_DCHECK(!set_a.empty() && !set_b.empty()); + + RowSpanSet::const_iterator it_b = set_b.begin(); + + // Iterate over all spans in `set_a` adding parts of it that do not intersect + // with `set_b` to the `output`. + for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end(); + ++it_a) { + // If there is no intersection then append the current span and continue. + if (it_b == set_b.end() || it_a->right < it_b->left) { + output->push_back(*it_a); + continue; + } + + // Iterate over `set_b` spans that may intersect with `it_a`. + int pos = it_a->left; + while (it_b != set_b.end() && it_b->left < it_a->right) { + if (it_b->left > pos) + output->push_back(RowSpan(pos, it_b->left)); + if (it_b->right > pos) { + pos = it_b->right; + if (pos >= it_a->right) + break; + } + ++it_b; + } + if (pos < it_a->right) + output->push_back(RowSpan(pos, it_a->right)); + } +} + +DesktopRegion::Iterator::Iterator(const DesktopRegion& region) + : region_(region), + row_(region.rows_.begin()), + previous_row_(region.rows_.end()) { + if (!IsAtEnd()) { + RTC_DCHECK_GT(row_->second->spans.size(), 0); + row_span_ = row_->second->spans.begin(); + UpdateCurrentRect(); + } +} + +DesktopRegion::Iterator::~Iterator() {} + +bool DesktopRegion::Iterator::IsAtEnd() const { + return row_ == region_.rows_.end(); +} + +void DesktopRegion::Iterator::Advance() { + RTC_DCHECK(!IsAtEnd()); + + while (true) { + ++row_span_; + if (row_span_ == row_->second->spans.end()) { + previous_row_ = row_; + ++row_; + if (row_ != region_.rows_.end()) { + RTC_DCHECK_GT(row_->second->spans.size(), 0); + row_span_ = row_->second->spans.begin(); + } + } + + if (IsAtEnd()) + return; + + // If the same span exists on the previous row then skip it, as we've + // already returned this span merged into the previous one, via + // UpdateCurrentRect(). + if (previous_row_ != region_.rows_.end() && + previous_row_->second->bottom == row_->second->top && + IsSpanInRow(*previous_row_->second, *row_span_)) { + continue; + } + + break; + } + + RTC_DCHECK(!IsAtEnd()); + UpdateCurrentRect(); +} + +void DesktopRegion::Iterator::UpdateCurrentRect() { + // Merge the current rectangle with the matching spans from later rows. + int bottom; + Rows::const_iterator bottom_row = row_; + Rows::const_iterator previous; + do { + bottom = bottom_row->second->bottom; + previous = bottom_row; + ++bottom_row; + } while (bottom_row != region_.rows_.end() && + previous->second->bottom == bottom_row->second->top && + IsSpanInRow(*bottom_row->second, *row_span_)); + rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top, + row_span_->right, bottom); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_region.h b/third_party/libwebrtc/modules/desktop_capture/desktop_region.h new file mode 100644 index 0000000000..ae9d8a0ba9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_region.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ +#define MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ + +#include <stdint.h> + +#include <map> +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// DesktopRegion represents a region of the screen or window. +// +// Internally each region is stored as a set of rows where each row contains one +// or more rectangles aligned vertically. +class RTC_EXPORT DesktopRegion { + private: + // The following private types need to be declared first because they are used + // in the public Iterator. + + // RowSpan represents a horizontal span withing a single row. + struct RowSpan { + RowSpan(int32_t left, int32_t right); + + // Used by std::vector<>. + bool operator==(const RowSpan& that) const { + return left == that.left && right == that.right; + } + + int32_t left; + int32_t right; + }; + + typedef std::vector<RowSpan> RowSpanSet; + + // Row represents a single row of a region. A row is set of rectangles that + // have the same vertical position. + struct Row { + Row(const Row&); + Row(Row&&); + Row(int32_t top, int32_t bottom); + ~Row(); + + int32_t top; + int32_t bottom; + + RowSpanSet spans; + }; + + // Type used to store list of rows in the region. The bottom position of row + // is used as the key so that rows are always ordered by their position. The + // map stores pointers to make Translate() more efficient. + typedef std::map<int, Row*> Rows; + + public: + // Iterator that can be used to iterate over rectangles of a DesktopRegion. + // The region must not be mutated while the iterator is used. + class RTC_EXPORT Iterator { + public: + explicit Iterator(const DesktopRegion& target); + ~Iterator(); + + bool IsAtEnd() const; + void Advance(); + + const DesktopRect& rect() const { return rect_; } + + private: + const DesktopRegion& region_; + + // Updates `rect_` based on the current `row_` and `row_span_`. If + // `row_span_` matches spans on consecutive rows then they are also merged + // into `rect_`, to generate more efficient output. + void UpdateCurrentRect(); + + Rows::const_iterator row_; + Rows::const_iterator previous_row_; + RowSpanSet::const_iterator row_span_; + DesktopRect rect_; + }; + + DesktopRegion(); + explicit DesktopRegion(const DesktopRect& rect); + DesktopRegion(const DesktopRect* rects, int count); + DesktopRegion(const DesktopRegion& other); + ~DesktopRegion(); + + DesktopRegion& operator=(const DesktopRegion& other); + + bool is_empty() const { return rows_.empty(); } + + bool Equals(const DesktopRegion& region) const; + + // Reset the region to be empty. + void Clear(); + + // Reset region to contain just `rect`. + void SetRect(const DesktopRect& rect); + + // Adds specified rect(s) or region to the region. + void AddRect(const DesktopRect& rect); + void AddRects(const DesktopRect* rects, int count); + void AddRegion(const DesktopRegion& region); + + // Finds intersection of two regions and stores them in the current region. + void Intersect(const DesktopRegion& region1, const DesktopRegion& region2); + + // Same as above but intersects content of the current region with `region`. + void IntersectWith(const DesktopRegion& region); + + // Clips the region by the `rect`. + void IntersectWith(const DesktopRect& rect); + + // Subtracts `region` from the current content of the region. + void Subtract(const DesktopRegion& region); + + // Subtracts `rect` from the current content of the region. + void Subtract(const DesktopRect& rect); + + // Adds (dx, dy) to the position of the region. + void Translate(int32_t dx, int32_t dy); + + void Swap(DesktopRegion* region); + + private: + // Comparison functions used for std::lower_bound(). Compare left or right + // edges withs a given `value`. + static bool CompareSpanLeft(const RowSpan& r, int32_t value); + static bool CompareSpanRight(const RowSpan& r, int32_t value); + + // Adds a new span to the row, coalescing spans if necessary. + static void AddSpanToRow(Row* row, int32_t left, int32_t right); + + // Returns true if the `span` exists in the given `row`. + static bool IsSpanInRow(const Row& row, const RowSpan& rect); + + // Calculates the intersection of two sets of spans. + static void IntersectRows(const RowSpanSet& set1, + const RowSpanSet& set2, + RowSpanSet* output); + + static void SubtractRows(const RowSpanSet& set_a, + const RowSpanSet& set_b, + RowSpanSet* output); + + // Merges `row` with the row above it if they contain the same spans. Doesn't + // do anything if called with `row` set to rows_.begin() (i.e. first row of + // the region). If the rows were merged `row` remains a valid iterator to the + // merged row. + void MergeWithPrecedingRow(Rows::iterator row); + + Rows rows_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_region_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_region_unittest.cc new file mode 100644 index 0000000000..b8bd78e990 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/desktop_region_unittest.cc @@ -0,0 +1,834 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_region.h" + +#include <stdlib.h> + +#include <algorithm> +#include <cstdint> + +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +int RadmonInt(int max) { + return (rand() / 256) % max; +} + +void CompareRegion(const DesktopRegion& region, + const DesktopRect rects[], + int rects_size) { + DesktopRegion::Iterator it(region); + for (int i = 0; i < rects_size; ++i) { + SCOPED_TRACE(i); + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_TRUE(it.rect().equals(rects[i])) + << it.rect().left() << "-" << it.rect().right() << "." + << it.rect().top() << "-" << it.rect().bottom() << " " + << rects[i].left() << "-" << rects[i].right() << "." << rects[i].top() + << "-" << rects[i].bottom(); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +} // namespace + +// Verify that regions are empty when created. +TEST(DesktopRegionTest, Empty) { + DesktopRegion r; + CompareRegion(r, NULL, 0); +} + +// Verify that empty rectangles are ignored. +TEST(DesktopRegionTest, AddEmpty) { + DesktopRegion r; + DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 0, 0); + r.AddRect(rect); + CompareRegion(r, NULL, 0); +} + +// Verify that regions with a single rectangles are handled properly. +TEST(DesktopRegionTest, SingleRect) { + DesktopRegion r; + DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 3, 4); + r.AddRect(rect); + CompareRegion(r, &rect, 1); +} + +// Verify that non-overlapping rectangles are not merged. +TEST(DesktopRegionTest, NonOverlappingRects) { + struct Case { + int count; + DesktopRect rects[4]; + } cases[] = { + {1, {DesktopRect::MakeXYWH(10, 10, 10, 10)}}, + {2, + {DesktopRect::MakeXYWH(10, 10, 10, 10), + DesktopRect::MakeXYWH(30, 10, 10, 15)}}, + {2, + {DesktopRect::MakeXYWH(10, 10, 10, 10), + DesktopRect::MakeXYWH(10, 30, 10, 5)}}, + {3, + {DesktopRect::MakeXYWH(10, 10, 10, 9), + DesktopRect::MakeXYWH(30, 10, 15, 10), + DesktopRect::MakeXYWH(10, 30, 8, 10)}}, + {4, + {DesktopRect::MakeXYWH(0, 0, 30, 10), + DesktopRect::MakeXYWH(40, 0, 10, 30), + DesktopRect::MakeXYWH(0, 20, 10, 30), + DesktopRect::MakeXYWH(20, 40, 30, 10)}}, + {4, + {DesktopRect::MakeXYWH(0, 0, 10, 100), + DesktopRect::MakeXYWH(20, 10, 30, 10), + DesktopRect::MakeXYWH(20, 30, 30, 10), + DesktopRect::MakeXYWH(20, 50, 30, 10)}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + + for (int j = 0; j < cases[i].count; ++j) { + r.AddRect(cases[i].rects[j]); + } + CompareRegion(r, cases[i].rects, cases[i].count); + + SCOPED_TRACE("Reverse"); + + // Try inserting rects in reverse order. + r.Clear(); + for (int j = cases[i].count - 1; j >= 0; --j) { + r.AddRect(cases[i].rects[j]); + } + CompareRegion(r, cases[i].rects, cases[i].count); + } +} + +TEST(DesktopRegionTest, TwoRects) { + struct Case { + DesktopRect input_rect1; + DesktopRect input_rect2; + int expected_count; + DesktopRect expected_rects[3]; + } cases[] = { + // Touching rectangles that merge into one. + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 100, 100, 200), + 1, + {DesktopRect::MakeLTRB(0, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(100, 0, 200, 100), + 1, + {DesktopRect::MakeLTRB(100, 0, 200, 200)}}, + + // Rectangles touching on the vertical edge. + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 150, 100, 250), + 3, + {DesktopRect::MakeLTRB(100, 100, 200, 150), + DesktopRect::MakeLTRB(0, 150, 200, 200), + DesktopRect::MakeLTRB(0, 200, 100, 250)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 50, 100, 150), + 3, + {DesktopRect::MakeLTRB(0, 50, 100, 100), + DesktopRect::MakeLTRB(0, 100, 200, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 120, 100, 180), + 3, + {DesktopRect::MakeLTRB(100, 100, 200, 120), + DesktopRect::MakeLTRB(0, 120, 200, 180), + DesktopRect::MakeLTRB(100, 180, 200, 200)}}, + + // Rectangles touching on the horizontal edge. + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 0, 250, 100), + 2, + {DesktopRect::MakeLTRB(150, 0, 250, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 0, 150, 100), + 2, + {DesktopRect::MakeLTRB(50, 0, 150, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(120, 0, 180, 100), + 2, + {DesktopRect::MakeLTRB(120, 0, 180, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + + // Overlapping rectangles. + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + 3, + {DesktopRect::MakeLTRB(50, 50, 150, 100), + DesktopRect::MakeLTRB(50, 100, 200, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 50, 250, 150), + 3, + {DesktopRect::MakeLTRB(150, 50, 250, 100), + DesktopRect::MakeLTRB(100, 100, 250, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 120, 150, 180), + 3, + {DesktopRect::MakeLTRB(100, 100, 200, 120), + DesktopRect::MakeLTRB(0, 120, 200, 180), + DesktopRect::MakeLTRB(100, 180, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(120, 0, 180, 150), + 2, + {DesktopRect::MakeLTRB(120, 0, 180, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 0, 200, 300), + DesktopRect::MakeLTRB(0, 100, 300, 200), + 3, + {DesktopRect::MakeLTRB(100, 0, 200, 100), + DesktopRect::MakeLTRB(0, 100, 300, 200), + DesktopRect::MakeLTRB(100, 200, 200, 300)}}, + + // One rectangle enclosing another. + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 150, 180, 180), + 1, + {DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(100, 100, 180, 180), + 1, + {DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 150, 200, 200), + 1, + {DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + + r.AddRect(cases[i].input_rect1); + r.AddRect(cases[i].input_rect2); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + + SCOPED_TRACE("Reverse"); + + // Run the same test with rectangles inserted in reverse order. + r.Clear(); + r.AddRect(cases[i].input_rect2); + r.AddRect(cases[i].input_rect1); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +// Verify that DesktopRegion::AddRectToRow() works correctly by creating a row +// of not overlapping rectangles and insert an overlapping rectangle into the +// row at different positions. Result is verified by building a map of the +// region in an array and comparing it with the expected values. +TEST(DesktopRegionTest, SameRow) { + const int kMapWidth = 50; + const int kLastRectSizes[] = {3, 27}; + + DesktopRegion base_region; + bool base_map[kMapWidth] = { + false, + }; + + base_region.AddRect(DesktopRect::MakeXYWH(5, 0, 5, 1)); + std::fill_n(base_map + 5, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(15, 0, 5, 1)); + std::fill_n(base_map + 15, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(25, 0, 5, 1)); + std::fill_n(base_map + 25, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(35, 0, 5, 1)); + std::fill_n(base_map + 35, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(45, 0, 5, 1)); + std::fill_n(base_map + 45, 5, true); + + for (size_t i = 0; i < sizeof(kLastRectSizes) / sizeof(kLastRectSizes[0]); + i++) { + int last_rect_size = kLastRectSizes[i]; + for (int x = 0; x < kMapWidth - last_rect_size; x++) { + SCOPED_TRACE(x); + + DesktopRegion r = base_region; + r.AddRect(DesktopRect::MakeXYWH(x, 0, last_rect_size, 1)); + + bool expected_map[kMapWidth]; + std::copy(base_map, base_map + kMapWidth, expected_map); + std::fill_n(expected_map + x, last_rect_size, true); + + bool map[kMapWidth] = { + false, + }; + + int pos = -1; + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + EXPECT_GT(it.rect().left(), pos); + pos = it.rect().right(); + std::fill_n(map + it.rect().left(), it.rect().width(), true); + } + + EXPECT_TRUE(std::equal(map, map + kMapWidth, expected_map)); + } + } +} + +TEST(DesktopRegionTest, ComplexRegions) { + struct Case { + int input_count; + DesktopRect input_rects[4]; + int expected_count; + DesktopRect expected_rects[6]; + } cases[] = { + {3, + { + DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 100, 100, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120), + }, + 2, + {DesktopRect::MakeLTRB(0, 100, 200, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120)}}, + {3, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175)}, + 4, + {DesktopRect::MakeLTRB(50, 50, 150, 100), + DesktopRect::MakeLTRB(50, 100, 200, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175), + DesktopRect::MakeLTRB(100, 150, 200, 200)}}, + {4, + {DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 0, 65, 15)}, + 6, + {DesktopRect::MakeLTRB(0, 0, 30, 10), + DesktopRect::MakeLTRB(50, 0, 65, 15), + DesktopRect::MakeLTRB(0, 10, 40, 20), + DesktopRect::MakeLTRB(0, 20, 50, 30), + DesktopRect::MakeLTRB(10, 30, 50, 40), + DesktopRect::MakeLTRB(20, 40, 50, 50)}}, + {3, + {DesktopRect::MakeLTRB(10, 10, 40, 20), + DesktopRect::MakeLTRB(10, 30, 40, 40), + DesktopRect::MakeLTRB(10, 20, 40, 30)}, + 1, + {DesktopRect::MakeLTRB(10, 10, 40, 40)}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + r.AddRects(cases[i].input_rects, cases[i].input_count); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + + // Try inserting rectangles in reverse order. + r.Clear(); + for (int j = cases[i].input_count - 1; j >= 0; --j) { + r.AddRect(cases[i].input_rects[j]); + } + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, Equals) { + struct Region { + int count; + DesktopRect rects[4]; + int id; + } regions[] = { + // Same region with one of the rectangles 1 pixel wider/taller. + {2, + {DesktopRect::MakeLTRB(0, 100, 200, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120)}, + 0}, + {2, + {DesktopRect::MakeLTRB(0, 100, 201, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120)}, + 1}, + {2, + {DesktopRect::MakeLTRB(0, 100, 200, 201), + DesktopRect::MakeLTRB(310, 110, 320, 120)}, + 2}, + + // Same region with one of the rectangles shifted horizontally and + // vertically. + {4, + {DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 0, 65, 15)}, + 3}, + {4, + {DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 1, 65, 16)}, + 4}, + {4, + {DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(51, 0, 66, 15)}, + 5}, + + // Same region defined by a different set of rectangles - one of the + // rectangle is split horizontally into two. + {3, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175)}, + 6}, + {4, + {DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 100, 150), + DesktopRect::MakeLTRB(100, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175)}, + 6}, + + // Rectangle region defined by a set of rectangles that merge into one. + {3, + {DesktopRect::MakeLTRB(10, 10, 40, 20), + DesktopRect::MakeLTRB(10, 30, 40, 40), + DesktopRect::MakeLTRB(10, 20, 40, 30)}, + 7}, + {1, {DesktopRect::MakeLTRB(10, 10, 40, 40)}, 7}, + }; + int kTotalRegions = sizeof(regions) / sizeof(Region); + + for (int i = 0; i < kTotalRegions; ++i) { + SCOPED_TRACE(i); + + DesktopRegion r1(regions[i].rects, regions[i].count); + for (int j = 0; j < kTotalRegions; ++j) { + SCOPED_TRACE(j); + + DesktopRegion r2(regions[j].rects, regions[j].count); + EXPECT_EQ(regions[i].id == regions[j].id, r1.Equals(r2)); + } + } +} + +TEST(DesktopRegionTest, Translate) { + struct Case { + int input_count; + DesktopRect input_rects[4]; + int dx; + int dy; + int expected_count; + DesktopRect expected_rects[5]; + } cases[] = { + {3, + {DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50)}, + 3, + 5, + 5, + {DesktopRect::MakeLTRB(3, 5, 33, 15), + DesktopRect::MakeLTRB(3, 15, 43, 25), + DesktopRect::MakeLTRB(3, 25, 53, 35), + DesktopRect::MakeLTRB(13, 35, 53, 45), + DesktopRect::MakeLTRB(23, 45, 53, 55)}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r(cases[i].input_rects, cases[i].input_count); + r.Translate(cases[i].dx, cases[i].dy); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, Intersect) { + struct Case { + int input1_count; + DesktopRect input1_rects[4]; + int input2_count; + DesktopRect input2_rects[4]; + int expected_count; + DesktopRect expected_rects[5]; + } cases[] = { + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(50, 50, 150, 150)}, + 1, + {DesktopRect::MakeLTRB(50, 50, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(100, 0, 200, 300)}, + 1, + {DesktopRect::MakeLTRB(0, 100, 300, 200)}, + 1, + {DesktopRect::MakeLTRB(100, 100, 200, 200)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 2, + {DesktopRect::MakeLTRB(50, 10, 150, 30), + DesktopRect::MakeLTRB(50, 30, 160, 50)}, + 1, + {DesktopRect::MakeLTRB(50, 10, 100, 50)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 2, + {DesktopRect::MakeLTRB(50, 10, 150, 30), + DesktopRect::MakeLTRB(50, 30, 90, 50)}, + 2, + {DesktopRect::MakeLTRB(50, 10, 100, 30), + DesktopRect::MakeLTRB(50, 30, 90, 50)}}, + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(100, 50, 200, 200)}, + 0, + {}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r1(cases[i].input1_rects, cases[i].input1_count); + DesktopRegion r2(cases[i].input2_rects, cases[i].input2_count); + + DesktopRegion r; + r.Intersect(r1, r2); + + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, Subtract) { + struct Case { + int input1_count; + DesktopRect input1_rects[4]; + int input2_count; + DesktopRect input2_rects[4]; + int expected_count; + DesktopRect expected_rects[5]; + } cases[] = { + // Subtract one rect from another. + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(50, 50, 150, 150)}, + 2, + {DesktopRect::MakeLTRB(0, 0, 100, 50), + DesktopRect::MakeLTRB(0, 50, 50, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(-50, -50, 50, 50)}, + 2, + {DesktopRect::MakeLTRB(50, 0, 100, 50), + DesktopRect::MakeLTRB(0, 50, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(-50, 50, 50, 150)}, + 2, + {DesktopRect::MakeLTRB(0, 0, 100, 50), + DesktopRect::MakeLTRB(50, 50, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(50, 50, 150, 70)}, + 3, + {DesktopRect::MakeLTRB(0, 0, 100, 50), + DesktopRect::MakeLTRB(0, 50, 50, 70), + DesktopRect::MakeLTRB(0, 70, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(50, 50, 70, 70)}, + 4, + {DesktopRect::MakeLTRB(0, 0, 100, 50), + DesktopRect::MakeLTRB(0, 50, 50, 70), + DesktopRect::MakeLTRB(70, 50, 100, 70), + DesktopRect::MakeLTRB(0, 70, 100, 100)}}, + + // Empty result. + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 0, + {}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(-10, -10, 110, 110)}, + 0, + {}}, + + {2, + {DesktopRect::MakeLTRB(0, 0, 100, 100), + DesktopRect::MakeLTRB(50, 50, 150, 150)}, + 2, + {DesktopRect::MakeLTRB(0, 0, 100, 100), + DesktopRect::MakeLTRB(50, 50, 150, 150)}, + 0, + {}}, + + // One rect out of disjoint set. + {3, + {DesktopRect::MakeLTRB(0, 0, 10, 10), + DesktopRect::MakeLTRB(20, 20, 30, 30), + DesktopRect::MakeLTRB(40, 0, 50, 10)}, + 1, + {DesktopRect::MakeLTRB(20, 20, 30, 30)}, + 2, + {DesktopRect::MakeLTRB(0, 0, 10, 10), + DesktopRect::MakeLTRB(40, 0, 50, 10)}}, + + // Row merging. + {3, + {DesktopRect::MakeLTRB(0, 0, 100, 50), + DesktopRect::MakeLTRB(0, 50, 150, 70), + DesktopRect::MakeLTRB(0, 70, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(100, 50, 150, 70)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}}, + + // No-op subtraction. + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(100, 0, 200, 100)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(-100, 0, 0, 100)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(0, 100, 0, 200)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}}, + + {1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}, + 1, + {DesktopRect::MakeLTRB(0, -100, 100, 0)}, + 1, + {DesktopRect::MakeLTRB(0, 0, 100, 100)}}, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r1(cases[i].input1_rects, cases[i].input1_count); + DesktopRegion r2(cases[i].input2_rects, cases[i].input2_count); + + r1.Subtract(r2); + + CompareRegion(r1, cases[i].expected_rects, cases[i].expected_count); + } +} + +// Verify that DesktopRegion::SubtractRows() works correctly by creating a row +// of not overlapping rectangles and subtracting a set of rectangle. Result +// is verified by building a map of the region in an array and comparing it with +// the expected values. +TEST(DesktopRegionTest, SubtractRectOnSameRow) { + const int kMapWidth = 50; + + struct SpanSet { + int count; + struct Range { + int start; + int end; + } spans[3]; + } span_sets[] = { + {1, {{0, 3}}}, + {1, {{0, 5}}}, + {1, {{0, 7}}}, + {1, {{0, 12}}}, + {2, {{0, 3}, {4, 5}, {6, 16}}}, + }; + + DesktopRegion base_region; + bool base_map[kMapWidth] = { + false, + }; + + base_region.AddRect(DesktopRect::MakeXYWH(5, 0, 5, 1)); + std::fill_n(base_map + 5, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(15, 0, 5, 1)); + std::fill_n(base_map + 15, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(25, 0, 5, 1)); + std::fill_n(base_map + 25, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(35, 0, 5, 1)); + std::fill_n(base_map + 35, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(45, 0, 5, 1)); + std::fill_n(base_map + 45, 5, true); + + for (size_t i = 0; i < sizeof(span_sets) / sizeof(span_sets[0]); i++) { + SCOPED_TRACE(i); + SpanSet& span_set = span_sets[i]; + int span_set_end = span_set.spans[span_set.count - 1].end; + for (int x = 0; x < kMapWidth - span_set_end; ++x) { + SCOPED_TRACE(x); + + DesktopRegion r = base_region; + + bool expected_map[kMapWidth]; + std::copy(base_map, base_map + kMapWidth, expected_map); + + DesktopRegion region2; + for (int span = 0; span < span_set.count; span++) { + std::fill_n(x + expected_map + span_set.spans[span].start, + span_set.spans[span].end - span_set.spans[span].start, + false); + region2.AddRect(DesktopRect::MakeLTRB(x + span_set.spans[span].start, 0, + x + span_set.spans[span].end, 1)); + } + r.Subtract(region2); + + bool map[kMapWidth] = { + false, + }; + + int pos = -1; + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + EXPECT_GT(it.rect().left(), pos); + pos = it.rect().right(); + std::fill_n(map + it.rect().left(), it.rect().width(), true); + } + + EXPECT_TRUE(std::equal(map, map + kMapWidth, expected_map)); + } + } +} + +// Verify that DesktopRegion::Subtract() works correctly by creating a column of +// not overlapping rectangles and subtracting a set of rectangle on the same +// column. Result is verified by building a map of the region in an array and +// comparing it with the expected values. +TEST(DesktopRegionTest, SubtractRectOnSameCol) { + const int kMapHeight = 50; + + struct SpanSet { + int count; + struct Range { + int start; + int end; + } spans[3]; + } span_sets[] = { + {1, {{0, 3}}}, + {1, {{0, 5}}}, + {1, {{0, 7}}}, + {1, {{0, 12}}}, + {2, {{0, 3}, {4, 5}, {6, 16}}}, + }; + + DesktopRegion base_region; + bool base_map[kMapHeight] = { + false, + }; + + base_region.AddRect(DesktopRect::MakeXYWH(0, 5, 1, 5)); + std::fill_n(base_map + 5, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(0, 15, 1, 5)); + std::fill_n(base_map + 15, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(0, 25, 1, 5)); + std::fill_n(base_map + 25, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(0, 35, 1, 5)); + std::fill_n(base_map + 35, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(0, 45, 1, 5)); + std::fill_n(base_map + 45, 5, true); + + for (size_t i = 0; i < sizeof(span_sets) / sizeof(span_sets[0]); i++) { + SCOPED_TRACE(i); + SpanSet& span_set = span_sets[i]; + int span_set_end = span_set.spans[span_set.count - 1].end; + for (int y = 0; y < kMapHeight - span_set_end; ++y) { + SCOPED_TRACE(y); + + DesktopRegion r = base_region; + + bool expected_map[kMapHeight]; + std::copy(base_map, base_map + kMapHeight, expected_map); + + DesktopRegion region2; + for (int span = 0; span < span_set.count; span++) { + std::fill_n(y + expected_map + span_set.spans[span].start, + span_set.spans[span].end - span_set.spans[span].start, + false); + region2.AddRect(DesktopRect::MakeLTRB(0, y + span_set.spans[span].start, + 1, y + span_set.spans[span].end)); + } + r.Subtract(region2); + + bool map[kMapHeight] = { + false, + }; + + int pos = -1; + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + EXPECT_GT(it.rect().top(), pos); + pos = it.rect().bottom(); + std::fill_n(map + it.rect().top(), it.rect().height(), true); + } + + for (int j = 0; j < kMapHeight; j++) { + EXPECT_EQ(expected_map[j], map[j]) << "j = " << j; + } + } + } +} + +TEST(DesktopRegionTest, DISABLED_Performance) { + for (int c = 0; c < 1000; ++c) { + DesktopRegion r; + for (int i = 0; i < 10; ++i) { + r.AddRect( + DesktopRect::MakeXYWH(RadmonInt(1000), RadmonInt(1000), 200, 200)); + } + + for (int i = 0; i < 1000; ++i) { + r.AddRect(DesktopRect::MakeXYWH(RadmonInt(1000), RadmonInt(1000), + 5 + RadmonInt(10) * 5, + 5 + RadmonInt(10) * 5)); + } + + // Iterate over the rectangles. + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/differ_block.cc b/third_party/libwebrtc/modules/desktop_capture/differ_block.cc new file mode 100644 index 0000000000..54ee0829ea --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/differ_block.cc @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/differ_block.h" + +#include <string.h> + +#include "modules/desktop_capture/differ_vector_sse2.h" +#include "rtc_base/system/arch.h" +#include "system_wrappers/include/cpu_features_wrapper.h" + +namespace webrtc { + +namespace { + +bool VectorDifference_C(const uint8_t* image1, const uint8_t* image2) { + return memcmp(image1, image2, kBlockSize * kBytesPerPixel) != 0; +} + +} // namespace + +bool VectorDifference(const uint8_t* image1, const uint8_t* image2) { + static bool (*diff_proc)(const uint8_t*, const uint8_t*) = nullptr; + + if (!diff_proc) { +#if defined(WEBRTC_ARCH_X86_FAMILY) + bool have_sse2 = GetCPUInfo(kSSE2) != 0; + // For x86 processors, check if SSE2 is supported. + if (have_sse2 && kBlockSize == 32) { + diff_proc = &VectorDifference_SSE2_W32; + } else if (have_sse2 && kBlockSize == 16) { + diff_proc = &VectorDifference_SSE2_W16; + } else { + diff_proc = &VectorDifference_C; + } +#else + // For other processors, always use C version. + // TODO(hclam): Implement a NEON version. + diff_proc = &VectorDifference_C; +#endif + } + + return diff_proc(image1, image2); +} + +bool BlockDifference(const uint8_t* image1, + const uint8_t* image2, + int height, + int stride) { + for (int i = 0; i < height; i++) { + if (VectorDifference(image1, image2)) { + return true; + } + image1 += stride; + image2 += stride; + } + return false; +} + +bool BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride) { + return BlockDifference(image1, image2, kBlockSize, stride); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/differ_block.h b/third_party/libwebrtc/modules/desktop_capture/differ_block.h new file mode 100644 index 0000000000..6c71e214e9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/differ_block.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ +#define MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ + +#include <stdint.h> + +namespace webrtc { + +// Size (in pixels) of each square block used for diffing. This must be a +// multiple of sizeof(uint64)/8. +const int kBlockSize = 32; + +// Format: BGRA 32 bit. +const int kBytesPerPixel = 4; + +// Low level function to compare 2 vectors of pixels of size kBlockSize. Returns +// whether the blocks differ. +bool VectorDifference(const uint8_t* image1, const uint8_t* image2); + +// Low level function to compare 2 blocks of pixels of size +// (kBlockSize, `height`). Returns whether the blocks differ. +bool BlockDifference(const uint8_t* image1, + const uint8_t* image2, + int height, + int stride); + +// Low level function to compare 2 blocks of pixels of size +// (kBlockSize, kBlockSize). Returns whether the blocks differ. +bool BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/differ_block_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/differ_block_unittest.cc new file mode 100644 index 0000000000..aa454c872d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/differ_block_unittest.cc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/differ_block.h" + +#include <string.h> + +#include "test/gtest.h" + +namespace webrtc { + +// Run 900 times to mimic 1280x720. +// TODO(fbarchard): Remove benchmark once performance is non-issue. +static const int kTimesToRun = 900; + +static void GenerateData(uint8_t* data, int size) { + for (int i = 0; i < size; ++i) { + data[i] = i; + } +} + +// Memory buffer large enough for 2 blocks aligned to 16 bytes. +static const int kSizeOfBlock = kBlockSize * kBlockSize * kBytesPerPixel; +uint8_t block_buffer[kSizeOfBlock * 2 + 16]; + +void PrepareBuffers(uint8_t*& block1, uint8_t*& block2) { + block1 = reinterpret_cast<uint8_t*>( + (reinterpret_cast<uintptr_t>(&block_buffer[0]) + 15) & ~15); + GenerateData(block1, kSizeOfBlock); + block2 = block1 + kSizeOfBlock; + memcpy(block2, block1, kSizeOfBlock); +} + +TEST(BlockDifferenceTestSame, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + + // These blocks should match. + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(0, result); + } +} + +TEST(BlockDifferenceTestLast, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[kSizeOfBlock - 2] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +TEST(BlockDifferenceTestMid, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[kSizeOfBlock / 2 + 1] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +TEST(BlockDifferenceTestFirst, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[0] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.cc b/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.cc new file mode 100644 index 0000000000..1c8b602d71 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.cc @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/differ_vector_sse2.h" + +#if defined(_MSC_VER) +#include <intrin.h> +#else +#include <emmintrin.h> +#include <mmintrin.h> +#endif + +namespace webrtc { + +extern bool VectorDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + const __m128i* i1 = reinterpret_cast<const __m128i*>(image1); + const __m128i* i2 = reinterpret_cast<const __m128i*>(image2); + v0 = _mm_loadu_si128(i1); + v1 = _mm_loadu_si128(i2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 1); + v1 = _mm_loadu_si128(i2 + 1); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 2); + v1 = _mm_loadu_si128(i2 + 2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 3); + v1 = _mm_loadu_si128(i2 + 3); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + + // This essential means sad = acc >> 64. We only care about the lower 16 + // bits. + sad = _mm_shuffle_epi32(acc, 0xEE); + sad = _mm_adds_epu16(sad, acc); + return _mm_cvtsi128_si32(sad) != 0; +} + +extern bool VectorDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + const __m128i* i1 = reinterpret_cast<const __m128i*>(image1); + const __m128i* i2 = reinterpret_cast<const __m128i*>(image2); + v0 = _mm_loadu_si128(i1); + v1 = _mm_loadu_si128(i2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 1); + v1 = _mm_loadu_si128(i2 + 1); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 2); + v1 = _mm_loadu_si128(i2 + 2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 3); + v1 = _mm_loadu_si128(i2 + 3); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 4); + v1 = _mm_loadu_si128(i2 + 4); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 5); + v1 = _mm_loadu_si128(i2 + 5); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 6); + v1 = _mm_loadu_si128(i2 + 6); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 7); + v1 = _mm_loadu_si128(i2 + 7); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + + // This essential means sad = acc >> 64. We only care about the lower 16 + // bits. + sad = _mm_shuffle_epi32(acc, 0xEE); + sad = _mm_adds_epu16(sad, acc); + return _mm_cvtsi128_si32(sad) != 0; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.h b/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.h new file mode 100644 index 0000000000..a3c297eb9d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/differ_vector_sse2.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This header file is used only differ_block.h. It defines the SSE2 rountines +// for finding vector difference. + +#ifndef MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_ +#define MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_ + +#include <stdint.h> + +namespace webrtc { + +// Find vector difference of dimension 16. +extern bool VectorDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2); + +// Find vector difference of dimension 32. +extern bool VectorDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.cc b/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.cc new file mode 100644 index 0000000000..67149bfcb9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.cc @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/fake_desktop_capturer.h" + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +FakeDesktopCapturer::FakeDesktopCapturer() = default; +FakeDesktopCapturer::~FakeDesktopCapturer() = default; + +void FakeDesktopCapturer::set_result(DesktopCapturer::Result result) { + result_ = result; +} + +int FakeDesktopCapturer::num_frames_captured() const { + return num_frames_captured_; +} + +int FakeDesktopCapturer::num_capture_attempts() const { + return num_capture_attempts_; +} + +// Uses the `generator` provided as DesktopFrameGenerator, FakeDesktopCapturer +// does +// not take the ownership of `generator`. +void FakeDesktopCapturer::set_frame_generator( + DesktopFrameGenerator* generator) { + generator_ = generator; +} + +void FakeDesktopCapturer::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; +} + +void FakeDesktopCapturer::CaptureFrame() { + num_capture_attempts_++; + if (generator_) { + if (result_ != DesktopCapturer::Result::SUCCESS) { + callback_->OnCaptureResult(result_, nullptr); + return; + } + + std::unique_ptr<DesktopFrame> frame( + generator_->GetNextFrame(shared_memory_factory_.get())); + if (frame) { + num_frames_captured_++; + callback_->OnCaptureResult(result_, std::move(frame)); + } else { + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY, + nullptr); + } + return; + } + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, nullptr); +} + +void FakeDesktopCapturer::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); +} + +bool FakeDesktopCapturer::GetSourceList(DesktopCapturer::SourceList* sources) { + sources->push_back({kWindowId, 1, "A-Fake-DesktopCapturer-Window"}); + sources->push_back({kScreenId, 1}); + return true; +} + +bool FakeDesktopCapturer::SelectSource(DesktopCapturer::SourceId id) { + return id == kWindowId || id == kScreenId || id == kFullDesktopScreenId; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.h b/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.h new file mode 100644 index 0000000000..086e6df0e2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/fake_desktop_capturer.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_ +#define MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame_generator.h" +#include "modules/desktop_capture/shared_memory.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// A fake implementation of DesktopCapturer or its derived interfaces to +// generate DesktopFrame for testing purpose. +// +// Consumers can provide a FrameGenerator instance to generate instances of +// DesktopFrame to return for each Capture() function call. +// If no FrameGenerator provided, FakeDesktopCapturer will always return a +// nullptr DesktopFrame. +// +// Double buffering is guaranteed by the FrameGenerator. FrameGenerator +// implements in desktop_frame_generator.h guarantee double buffering, they +// creates a new instance of DesktopFrame each time. +class RTC_EXPORT FakeDesktopCapturer : public DesktopCapturer { + public: + FakeDesktopCapturer(); + ~FakeDesktopCapturer() override; + + // Decides the result which will be returned in next Capture() callback. + void set_result(DesktopCapturer::Result result); + + // Uses the `generator` provided as DesktopFrameGenerator, FakeDesktopCapturer + // does not take the ownership of `generator`. + void set_frame_generator(DesktopFrameGenerator* generator); + + // Count of DesktopFrame(s) have been returned by this instance. This field + // would never be negative. + int num_frames_captured() const; + + // Count of CaptureFrame() calls have been made. This field would never be + // negative. + int num_capture_attempts() const; + + // DesktopCapturer interface + void Start(DesktopCapturer::Callback* callback) override; + void CaptureFrame() override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + bool GetSourceList(DesktopCapturer::SourceList* sources) override; + bool SelectSource(DesktopCapturer::SourceId id) override; + + private: + static constexpr DesktopCapturer::SourceId kWindowId = 1378277495; + static constexpr DesktopCapturer::SourceId kScreenId = 1378277496; + + DesktopCapturer::Callback* callback_ = nullptr; + std::unique_ptr<SharedMemoryFactory> shared_memory_factory_; + DesktopCapturer::Result result_ = Result::SUCCESS; + DesktopFrameGenerator* generator_ = nullptr; + int num_frames_captured_ = 0; + int num_capture_attempts_ = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc new file mode 100644 index 0000000000..61fdb416f2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" + +#include <stddef.h> + +#include <utility> + +#include "api/sequence_checker.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { + +// Implementation to share a SharedMemoryFactory between DesktopCapturer +// instances. This class is designed for synchronized DesktopCapturer +// implementations only. +class SharedMemoryFactoryProxy : public SharedMemoryFactory { + public: + // Users should maintain the lifetime of `factory` to ensure it overlives + // current instance. + static std::unique_ptr<SharedMemoryFactory> Create( + SharedMemoryFactory* factory); + ~SharedMemoryFactoryProxy() override; + + // Forwards CreateSharedMemory() calls to `factory_`. Users should always call + // this function in one thread. Users should not call this function after the + // SharedMemoryFactory which current instance created from has been destroyed. + std::unique_ptr<SharedMemory> CreateSharedMemory(size_t size) override; + + private: + explicit SharedMemoryFactoryProxy(SharedMemoryFactory* factory); + + SharedMemoryFactory* factory_ = nullptr; + SequenceChecker thread_checker_; +}; + +} // namespace + +SharedMemoryFactoryProxy::SharedMemoryFactoryProxy( + SharedMemoryFactory* factory) { + RTC_DCHECK(factory); + factory_ = factory; +} + +// static +std::unique_ptr<SharedMemoryFactory> SharedMemoryFactoryProxy::Create( + SharedMemoryFactory* factory) { + return std::unique_ptr<SharedMemoryFactory>( + new SharedMemoryFactoryProxy(factory)); +} + +SharedMemoryFactoryProxy::~SharedMemoryFactoryProxy() = default; + +std::unique_ptr<SharedMemory> SharedMemoryFactoryProxy::CreateSharedMemory( + size_t size) { + RTC_DCHECK(thread_checker_.IsCurrent()); + return factory_->CreateSharedMemory(size); +} + +FallbackDesktopCapturerWrapper::FallbackDesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer> main_capturer, + std::unique_ptr<DesktopCapturer> secondary_capturer) + : main_capturer_(std::move(main_capturer)), + secondary_capturer_(std::move(secondary_capturer)) { + RTC_DCHECK(main_capturer_); + RTC_DCHECK(secondary_capturer_); +} + +FallbackDesktopCapturerWrapper::~FallbackDesktopCapturerWrapper() = default; + +void FallbackDesktopCapturerWrapper::Start( + DesktopCapturer::Callback* callback) { + callback_ = callback; + // FallbackDesktopCapturerWrapper catchs the callback of the main capturer, + // and checks its return value to decide whether the secondary capturer should + // be involved. + main_capturer_->Start(this); + // For the secondary capturer, we do not have a backup plan anymore, so + // FallbackDesktopCapturerWrapper won't check its return value any more. It + // will directly return to the input `callback`. + secondary_capturer_->Start(callback); +} + +void FallbackDesktopCapturerWrapper::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); + if (shared_memory_factory_) { + main_capturer_->SetSharedMemoryFactory( + SharedMemoryFactoryProxy::Create(shared_memory_factory_.get())); + secondary_capturer_->SetSharedMemoryFactory( + SharedMemoryFactoryProxy::Create(shared_memory_factory_.get())); + } else { + main_capturer_->SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory>()); + secondary_capturer_->SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory>()); + } +} + +void FallbackDesktopCapturerWrapper::CaptureFrame() { + RTC_DCHECK(callback_); + if (main_capturer_permanent_error_) { + secondary_capturer_->CaptureFrame(); + } else { + main_capturer_->CaptureFrame(); + } +} + +void FallbackDesktopCapturerWrapper::SetExcludedWindow(WindowId window) { + main_capturer_->SetExcludedWindow(window); + secondary_capturer_->SetExcludedWindow(window); +} + +bool FallbackDesktopCapturerWrapper::GetSourceList(SourceList* sources) { + if (main_capturer_permanent_error_) { + return secondary_capturer_->GetSourceList(sources); + } + return main_capturer_->GetSourceList(sources); +} + +bool FallbackDesktopCapturerWrapper::SelectSource(SourceId id) { + if (main_capturer_permanent_error_) { + return secondary_capturer_->SelectSource(id); + } + const bool main_capturer_result = main_capturer_->SelectSource(id); + RTC_HISTOGRAM_BOOLEAN( + "WebRTC.DesktopCapture.PrimaryCapturerSelectSourceError", + main_capturer_result); + if (!main_capturer_result) { + main_capturer_permanent_error_ = true; + } + + return secondary_capturer_->SelectSource(id); +} + +bool FallbackDesktopCapturerWrapper::FocusOnSelectedSource() { + if (main_capturer_permanent_error_) { + return secondary_capturer_->FocusOnSelectedSource(); + } + return main_capturer_->FocusOnSelectedSource() || + secondary_capturer_->FocusOnSelectedSource(); +} + +bool FallbackDesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) { + // Returns true if either capturer returns true. + if (main_capturer_permanent_error_) { + return secondary_capturer_->IsOccluded(pos); + } + return main_capturer_->IsOccluded(pos) || + secondary_capturer_->IsOccluded(pos); +} + +void FallbackDesktopCapturerWrapper::OnCaptureResult( + Result result, + std::unique_ptr<DesktopFrame> frame) { + RTC_DCHECK(callback_); + RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.PrimaryCapturerError", + result != Result::SUCCESS); + RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.PrimaryCapturerPermanentError", + result == Result::ERROR_PERMANENT); + if (result == Result::SUCCESS) { + callback_->OnCaptureResult(result, std::move(frame)); + return; + } + + if (result == Result::ERROR_PERMANENT) { + main_capturer_permanent_error_ = true; + } + secondary_capturer_->CaptureFrame(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h new file mode 100644 index 0000000000..2855eae7ee --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ +#define MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +// A DesktopCapturer wrapper owns two DesktopCapturer implementations. If the +// main DesktopCapturer fails, it uses the secondary one instead. Two capturers +// are expected to return same SourceList, and the meaning of each SourceId is +// identical, otherwise FallbackDesktopCapturerWrapper may return frames from +// different sources. Using asynchronized DesktopCapturer implementations with +// SharedMemoryFactory is not supported, and may result crash or assertion +// failure. +class FallbackDesktopCapturerWrapper final : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + FallbackDesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer> main_capturer, + std::unique_ptr<DesktopCapturer> secondary_capturer); + ~FallbackDesktopCapturerWrapper() override; + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + private: + // DesktopCapturer::Callback interface. + void OnCaptureResult(Result result, + std::unique_ptr<DesktopFrame> frame) override; + + const std::unique_ptr<DesktopCapturer> main_capturer_; + const std::unique_ptr<DesktopCapturer> secondary_capturer_; + std::unique_ptr<SharedMemoryFactory> shared_memory_factory_; + bool main_capturer_permanent_error_ = false; + DesktopCapturer::Callback* callback_ = nullptr; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc new file mode 100644 index 0000000000..de66386434 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" + +#include <stddef.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame_generator.h" +#include "modules/desktop_capture/fake_desktop_capturer.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +std::unique_ptr<DesktopCapturer> CreateDesktopCapturer( + PainterDesktopFrameGenerator* frame_generator) { + std::unique_ptr<FakeDesktopCapturer> capturer(new FakeDesktopCapturer()); + capturer->set_frame_generator(frame_generator); + return std::move(capturer); +} + +class FakeSharedMemory : public SharedMemory { + public: + explicit FakeSharedMemory(size_t size); + ~FakeSharedMemory() override; + + private: + static int next_id_; +}; + +// static +int FakeSharedMemory::next_id_ = 0; + +FakeSharedMemory::FakeSharedMemory(size_t size) + : SharedMemory(new char[size], size, 0, next_id_++) {} + +FakeSharedMemory::~FakeSharedMemory() { + delete[] static_cast<char*>(data_); +} + +class FakeSharedMemoryFactory : public SharedMemoryFactory { + public: + FakeSharedMemoryFactory() = default; + ~FakeSharedMemoryFactory() override = default; + + std::unique_ptr<SharedMemory> CreateSharedMemory(size_t size) override; +}; + +std::unique_ptr<SharedMemory> FakeSharedMemoryFactory::CreateSharedMemory( + size_t size) { + return std::unique_ptr<SharedMemory>(new FakeSharedMemory(size)); +} + +} // namespace + +class FallbackDesktopCapturerWrapperTest : public ::testing::Test, + public DesktopCapturer::Callback { + public: + FallbackDesktopCapturerWrapperTest(); + ~FallbackDesktopCapturerWrapperTest() override = default; + + protected: + std::vector<std::pair<DesktopCapturer::Result, bool>> results_; + FakeDesktopCapturer* main_capturer_ = nullptr; + FakeDesktopCapturer* secondary_capturer_ = nullptr; + std::unique_ptr<FallbackDesktopCapturerWrapper> wrapper_; + + private: + // DesktopCapturer::Callback interface + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override; + PainterDesktopFrameGenerator frame_generator_; +}; + +FallbackDesktopCapturerWrapperTest::FallbackDesktopCapturerWrapperTest() { + frame_generator_.size()->set(1024, 768); + std::unique_ptr<DesktopCapturer> main_capturer = + CreateDesktopCapturer(&frame_generator_); + std::unique_ptr<DesktopCapturer> secondary_capturer = + CreateDesktopCapturer(&frame_generator_); + main_capturer_ = static_cast<FakeDesktopCapturer*>(main_capturer.get()); + secondary_capturer_ = + static_cast<FakeDesktopCapturer*>(secondary_capturer.get()); + wrapper_.reset(new FallbackDesktopCapturerWrapper( + std::move(main_capturer), std::move(secondary_capturer))); + wrapper_->Start(this); +} + +void FallbackDesktopCapturerWrapperTest::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) { + results_.emplace_back(result, !!frame); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainNeverFailed) { + wrapper_->CaptureFrame(); + ASSERT_EQ(main_capturer_->num_capture_attempts(), 1); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 0); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 0); + ASSERT_EQ(results_.size(), 1U); + ASSERT_EQ(results_[0], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainFailedTemporarily) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 3); + ASSERT_EQ(main_capturer_->num_frames_captured(), 2); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 1); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 1); + ASSERT_EQ(results_.size(), 3U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainFailedPermanently) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 2); + ASSERT_EQ(results_.size(), 3U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +TEST_F(FallbackDesktopCapturerWrapperTest, BothFailed) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + secondary_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + secondary_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 5); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 2); + ASSERT_EQ(results_.size(), 6U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } + ASSERT_EQ(results_[3], + std::make_pair(DesktopCapturer::Result::ERROR_TEMPORARY, false)); + ASSERT_EQ(results_[4], + std::make_pair(DesktopCapturer::Result::ERROR_PERMANENT, false)); + ASSERT_EQ(results_[5], + std::make_pair(DesktopCapturer::Result::ERROR_PERMANENT, false)); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, WithSharedMemory) { + wrapper_->SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory>(new FakeSharedMemoryFactory())); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 4); + ASSERT_EQ(main_capturer_->num_frames_captured(), 2); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 3); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 3); + ASSERT_EQ(results_.size(), 5U); + for (int i = 0; i < 5; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.cc b/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.cc new file mode 100644 index 0000000000..e0975570ba --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.cc @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/full_screen_application_handler.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +FullScreenApplicationHandler::FullScreenApplicationHandler( + DesktopCapturer::SourceId sourceId) + : source_id_(sourceId) {} + +DesktopCapturer::SourceId FullScreenApplicationHandler::FindFullScreenWindow( + const DesktopCapturer::SourceList&, + int64_t) const { + return 0; +} + +DesktopCapturer::SourceId FullScreenApplicationHandler::GetSourceId() const { + return source_id_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.h b/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.h new file mode 100644 index 0000000000..b7e097a474 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/full_screen_application_handler.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_ +#define MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capturer.h" + +namespace webrtc { + +// Base class for application specific handler to check criteria for switch to +// full-screen mode and find if possible the full-screen window to share. +// Supposed to be created and owned by platform specific +// FullScreenWindowDetector. +class FullScreenApplicationHandler { + public: + virtual ~FullScreenApplicationHandler() {} + + FullScreenApplicationHandler(const FullScreenApplicationHandler&) = delete; + FullScreenApplicationHandler& operator=(const FullScreenApplicationHandler&) = + delete; + + explicit FullScreenApplicationHandler(DesktopCapturer::SourceId sourceId); + + // Returns the full-screen window in place of the original window if all the + // criteria are met, or 0 if no such window found. + virtual DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& window_list, + int64_t timestamp) const; + + // Returns source id of original window associated with + // FullScreenApplicationHandler + DesktopCapturer::SourceId GetSourceId() const; + + private: + const DesktopCapturer::SourceId source_id_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.cc b/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.cc new file mode 100644 index 0000000000..d0bc9c7ca6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.cc @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/full_screen_window_detector.h" +#include "modules/desktop_capture/full_screen_application_handler.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +FullScreenWindowDetector::FullScreenWindowDetector( + ApplicationHandlerFactory application_handler_factory) + : application_handler_factory_(application_handler_factory), + last_update_time_ms_(0), + previous_source_id_(0), + no_handler_source_id_(0) {} + +DesktopCapturer::SourceId FullScreenWindowDetector::FindFullScreenWindow( + DesktopCapturer::SourceId original_source_id) { + if (app_handler_ == nullptr || + app_handler_->GetSourceId() != original_source_id) { + return 0; + } + return app_handler_->FindFullScreenWindow(window_list_, last_update_time_ms_); +} + +void FullScreenWindowDetector::UpdateWindowListIfNeeded( + DesktopCapturer::SourceId original_source_id, + rtc::FunctionView<bool(DesktopCapturer::SourceList*)> get_sources) { + const bool skip_update = previous_source_id_ != original_source_id; + previous_source_id_ = original_source_id; + + // Here is an attempt to avoid redundant creating application handler in case + // when an instance of WindowCapturer is used to generate a thumbnail to show + // in picker by calling SelectSource and CaptureFrame for every available + // source. + if (skip_update) { + return; + } + + CreateApplicationHandlerIfNeeded(original_source_id); + if (app_handler_ == nullptr) { + // There is no FullScreenApplicationHandler specific for + // current application + return; + } + + constexpr int64_t kUpdateIntervalMs = 500; + + if ((rtc::TimeMillis() - last_update_time_ms_) <= kUpdateIntervalMs) { + return; + } + + DesktopCapturer::SourceList window_list; + if (get_sources(&window_list)) { + last_update_time_ms_ = rtc::TimeMillis(); + window_list_.swap(window_list); + } +} + +void FullScreenWindowDetector::CreateApplicationHandlerIfNeeded( + DesktopCapturer::SourceId source_id) { + if (no_handler_source_id_ == source_id) { + return; + } + + if (app_handler_ == nullptr || app_handler_->GetSourceId() != source_id) { + app_handler_ = application_handler_factory_ + ? application_handler_factory_(source_id) + : nullptr; + } + + if (app_handler_ == nullptr) { + no_handler_source_id_ = source_id; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.h b/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.h new file mode 100644 index 0000000000..998b720d90 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/full_screen_window_detector.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_ +#define MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_ + +#include <memory> + +#include "api/function_view.h" +#include "api/ref_counted_base.h" +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/full_screen_application_handler.h" + +namespace webrtc { + +// This is a way to handle switch to full-screen mode for application in some +// specific cases: +// - Chrome on MacOS creates a new window in full-screen mode to +// show a tab full-screen and minimizes the old window. +// - PowerPoint creates new windows in full-screen mode when user goes to +// presentation mode (Slide Show Window, Presentation Window). +// +// To continue capturing in these cases, we try to find the new full-screen +// window using criteria provided by application specific +// FullScreenApplicationHandler. + +class FullScreenWindowDetector + : public rtc::RefCountedNonVirtual<FullScreenWindowDetector> { + public: + using ApplicationHandlerFactory = + std::function<std::unique_ptr<FullScreenApplicationHandler>( + DesktopCapturer::SourceId sourceId)>; + + FullScreenWindowDetector( + ApplicationHandlerFactory application_handler_factory); + + FullScreenWindowDetector(const FullScreenWindowDetector&) = delete; + FullScreenWindowDetector& operator=(const FullScreenWindowDetector&) = delete; + + // Returns the full-screen window in place of the original window if all the + // criteria provided by FullScreenApplicationHandler are met, or 0 if no such + // window found. + DesktopCapturer::SourceId FindFullScreenWindow( + DesktopCapturer::SourceId original_source_id); + + // The caller should call this function periodically, implementation will + // update internal state no often than twice per second + void UpdateWindowListIfNeeded( + DesktopCapturer::SourceId original_source_id, + rtc::FunctionView<bool(DesktopCapturer::SourceList*)> get_sources); + + static rtc::scoped_refptr<FullScreenWindowDetector> + CreateFullScreenWindowDetector(); + + protected: + std::unique_ptr<FullScreenApplicationHandler> app_handler_; + + private: + void CreateApplicationHandlerIfNeeded(DesktopCapturer::SourceId source_id); + + ApplicationHandlerFactory application_handler_factory_; + + int64_t last_update_time_ms_; + DesktopCapturer::SourceId previous_source_id_; + + // Save the source id when we fail to create an instance of + // CreateApplicationHandlerIfNeeded to avoid redundant attempt to do it again. + DesktopCapturer::SourceId no_handler_source_id_; + + DesktopCapturer::SourceList window_list_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc new file mode 100644 index 0000000000..e9158bf0cc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc @@ -0,0 +1,158 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h" + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/linux/wayland/restore_token_manager.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/random.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { + +using xdg_portal::RequestResponse; +using xdg_portal::ScreenCapturePortalInterface; +using xdg_portal::SessionDetails; + +} // namespace + +BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options) + : BaseCapturerPipeWire( + options, + std::make_unique<ScreenCastPortal>( + ScreenCastPortal::CaptureSourceType::kAnyScreenContent, + this)) { + is_screencast_portal_ = true; +} + +BaseCapturerPipeWire::BaseCapturerPipeWire( + const DesktopCaptureOptions& options, + std::unique_ptr<ScreenCapturePortalInterface> portal) + : options_(options), + is_screencast_portal_(false), + portal_(std::move(portal)) { + Random random(rtc::TimeMicros()); + source_id_ = static_cast<SourceId>(random.Rand(1, INT_MAX)); +} + +BaseCapturerPipeWire::~BaseCapturerPipeWire() {} + +void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result, + uint32_t stream_node_id, + int fd) { + if (result != RequestResponse::kSuccess || + !options_.screencast_stream()->StartScreenCastStream( + stream_node_id, fd, options_.get_width(), options_.get_height())) { + capturer_failed_ = true; + RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: " + << static_cast<uint>(result); + } else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) { + if (!screencast_portal->RestoreToken().empty()) { + RestoreTokenManager::GetInstance().AddToken( + source_id_, screencast_portal->RestoreToken()); + } + } +} + +void BaseCapturerPipeWire::OnScreenCastSessionClosed() { + if (!capturer_failed_) { + options_.screencast_stream()->StopScreenCastStream(); + } +} + +void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) { + if (!capturer_failed_) { + options_.screencast_stream()->UpdateScreenCastStreamResolution(width, + height); + } +} + +void BaseCapturerPipeWire::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; + + if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) { + screencast_portal->SetPersistMode( + ScreenCastPortal::PersistMode::kTransient); + if (selected_source_id_) { + screencast_portal->SetRestoreToken( + RestoreTokenManager::GetInstance().TakeToken(selected_source_id_)); + } + } + + portal_->Start(); +} + +void BaseCapturerPipeWire::CaptureFrame() { + if (capturer_failed_) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + std::unique_ptr<DesktopFrame> frame = + options_.screencast_stream()->CaptureFrame(); + + if (!frame || !frame->data()) { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on + // the frame, see ScreenCapturerX11::CaptureFrame. + + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +// Keep in sync with defines at browser/actors/WebRTCParent.jsm +// With PipeWire we can't select which system resource is shared so +// we don't create a window/screen list. Instead we place these constants +// as window name/id so frontend code can identify PipeWire backend +// and does not try to create screen/window preview. + +#define PIPEWIRE_ID 0xaffffff +#define PIPEWIRE_NAME "####_PIPEWIRE_PORTAL_####" + +bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) { + RTC_DCHECK(sources->size() == 0); + // List of available screens is already presented by the xdg-desktop-portal, + // so we just need a (valid) source id for any callers to pass around, even + // though it doesn't mean anything to us. Until the user selects a source in + // xdg-desktop-portal we'll just end up returning empty frames. Note that "0" + // is often treated as a null/placeholder id, so we shouldn't use that. + // TODO(https://crbug.com/1297671): Reconsider type of ID when plumbing + // token that will enable stream re-use. + sources->push_back({source_id_}); + return true; +} + +bool BaseCapturerPipeWire::SelectSource(SourceId id) { + // Screen selection is handled by the xdg-desktop-portal. + selected_source_id_ = id; + return id == PIPEWIRE_ID; +} + +SessionDetails BaseCapturerPipeWire::GetSessionDetails() { + return portal_->GetSessionDetails(); +} + +ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() { + return is_screencast_portal_ ? static_cast<ScreenCastPortal*>(portal_.get()) + : nullptr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h new file mode 100644 index 0000000000..6e18f4d11b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h @@ -0,0 +1,74 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_ + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/linux/wayland/portal_request_response.h" +#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h" +#include "modules/desktop_capture/linux/wayland/screencast_portal.h" +#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" +#include "modules/desktop_capture/linux/wayland/xdg_session_details.h" + +namespace webrtc { + +class BaseCapturerPipeWire : public DesktopCapturer, + public ScreenCastPortal::PortalNotifier { + public: + explicit BaseCapturerPipeWire(const DesktopCaptureOptions& options); + BaseCapturerPipeWire( + const DesktopCaptureOptions& options, + std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal); + ~BaseCapturerPipeWire() override; + + BaseCapturerPipeWire(const BaseCapturerPipeWire&) = delete; + BaseCapturerPipeWire& operator=(const BaseCapturerPipeWire&) = delete; + + // DesktopCapturer interface. + void Start(Callback* delegate) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + // ScreenCastPortal::PortalNotifier interface. + void OnScreenCastRequestResult(xdg_portal::RequestResponse result, + uint32_t stream_node_id, + int fd) override; + void OnScreenCastSessionClosed() override; + void UpdateResolution(uint32_t width, uint32_t height) override; + + xdg_portal::SessionDetails GetSessionDetails(); + + private: + ScreenCastPortal* GetScreenCastPortal(); + + DesktopCaptureOptions options_ = {}; + Callback* callback_ = nullptr; + bool capturer_failed_ = false; + bool is_screencast_portal_ = false; + + // SourceId that is selected using SelectSource() and that we previously + // returned in GetSourceList(). This should be a SourceId that has a restore + // token associated with it and can be restored if we have required version + // of xdg-desktop-portal. + SourceId selected_source_id_ = 0; + // SourceID we randomly generate and that is returned in GetSourceList() as + // available source that will later get assigned to a restore token in order + // to be restored later using SelectSource(). + SourceId source_id_ = 0; + std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/drm.sigs b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/drm.sigs new file mode 100644 index 0000000000..226979fe16 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/drm.sigs @@ -0,0 +1,11 @@ +// Copyright 2021 The WebRTC project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//------------------------------------------------ +// Functions from DRM used in capturer code. +//-------- + +// xf86drm.h +int drmGetDevices2(uint32_t flags, drmDevicePtr devices[], int max_devices); +void drmFreeDevices(drmDevicePtr devices[], int count); diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc new file mode 100644 index 0000000000..5bbd5d7aba --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.cc @@ -0,0 +1,703 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h" + +#include <asm/ioctl.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <libdrm/drm_fourcc.h> +#include <linux/types.h> +#include <spa/param/video/format-utils.h> +#include <unistd.h> +#include <xf86drm.h> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" +#include "rtc_base/string_encode.h" + +namespace webrtc { + +// EGL +typedef EGLBoolean (*eglBindAPI_func)(EGLenum api); +typedef EGLContext (*eglCreateContext_func)(EGLDisplay dpy, + EGLConfig config, + EGLContext share_context, + const EGLint* attrib_list); +typedef EGLBoolean (*eglDestroyContext_func)(EGLDisplay display, + EGLContext context); +typedef EGLBoolean (*eglTerminate_func)(EGLDisplay display); +typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay dpy, + EGLContext ctx, + EGLenum target, + EGLClientBuffer buffer, + const EGLint* attrib_list); +typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay dpy, + EGLImageKHR image); +typedef EGLint (*eglGetError_func)(void); +typedef void* (*eglGetProcAddress_func)(const char*); +typedef EGLDisplay (*eglGetPlatformDisplayEXT_func)(EGLenum platform, + void* native_display, + const EGLint* attrib_list); +typedef EGLDisplay (*eglGetPlatformDisplay_func)(EGLenum platform, + void* native_display, + const EGLAttrib* attrib_list); + +typedef EGLBoolean (*eglInitialize_func)(EGLDisplay dpy, + EGLint* major, + EGLint* minor); +typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay dpy, + EGLSurface draw, + EGLSurface read, + EGLContext ctx); +typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy, + EGLint max_formats, + EGLint* formats, + EGLint* num_formats); +typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy, + EGLint format, + EGLint max_modifiers, + EGLuint64KHR* modifiers, + EGLBoolean* external_only, + EGLint* num_modifiers); +typedef const char* (*eglQueryString_func)(EGLDisplay dpy, EGLint name); +typedef void (*glEGLImageTargetTexture2DOES_func)(GLenum target, + GLeglImageOES image); + +// This doesn't follow naming conventions in WebRTC, where the naming +// should look like e.g. egl_bind_api instead of EglBindAPI, however +// we named them according to the exported functions they map to for +// consistency. +eglBindAPI_func EglBindAPI = nullptr; +eglCreateContext_func EglCreateContext = nullptr; +eglDestroyContext_func EglDestroyContext = nullptr; +eglTerminate_func EglTerminate = nullptr; +eglCreateImageKHR_func EglCreateImageKHR = nullptr; +eglDestroyImageKHR_func EglDestroyImageKHR = nullptr; +eglGetError_func EglGetError = nullptr; +eglGetProcAddress_func EglGetProcAddress = nullptr; +eglGetPlatformDisplayEXT_func EglGetPlatformDisplayEXT = nullptr; +eglGetPlatformDisplay_func EglGetPlatformDisplay = nullptr; +eglInitialize_func EglInitialize = nullptr; +eglMakeCurrent_func EglMakeCurrent = nullptr; +eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr; +eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr; +eglQueryString_func EglQueryString = nullptr; +glEGLImageTargetTexture2DOES_func GlEGLImageTargetTexture2DOES = nullptr; + +// GL +typedef void (*glBindTexture_func)(GLenum target, GLuint texture); +typedef void (*glDeleteTextures_func)(GLsizei n, const GLuint* textures); +typedef void (*glGenTextures_func)(GLsizei n, GLuint* textures); +typedef GLenum (*glGetError_func)(void); +typedef const GLubyte* (*glGetString_func)(GLenum name); +typedef void (*glGetTexImage_func)(GLenum target, + GLint level, + GLenum format, + GLenum type, + void* pixels); +typedef void (*glTexParameteri_func)(GLenum target, GLenum pname, GLint param); +typedef void* (*glXGetProcAddressARB_func)(const char*); + +// This doesn't follow naming conventions in WebRTC, where the naming +// should look like e.g. egl_bind_api instead of EglBindAPI, however +// we named them according to the exported functions they map to for +// consistency. +glBindTexture_func GlBindTexture = nullptr; +glDeleteTextures_func GlDeleteTextures = nullptr; +glGenTextures_func GlGenTextures = nullptr; +glGetError_func GlGetError = nullptr; +glGetString_func GlGetString = nullptr; +glGetTexImage_func GlGetTexImage = nullptr; +glTexParameteri_func GlTexParameteri = nullptr; +glXGetProcAddressARB_func GlXGetProcAddressARB = nullptr; + +static const std::string FormatGLError(GLenum err) { + switch (err) { + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_STACK_OVERFLOW: + return "GL_STACK_OVERFLOW"; + case GL_STACK_UNDERFLOW: + return "GL_STACK_UNDERFLOW"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + default: + return "GL error code: " + std::to_string(err); + } +} + +static const std::string FormatEGLError(EGLint err) { + switch (err) { + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return "EGL error code: " + std::to_string(err); + } +} + +static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) { + switch (spa_format) { + case SPA_VIDEO_FORMAT_RGBA: + return DRM_FORMAT_ABGR8888; + case SPA_VIDEO_FORMAT_RGBx: + return DRM_FORMAT_XBGR8888; + case SPA_VIDEO_FORMAT_BGRA: + return DRM_FORMAT_ARGB8888; + case SPA_VIDEO_FORMAT_BGRx: + return DRM_FORMAT_XRGB8888; + default: + return DRM_FORMAT_INVALID; + } +} + +static void CloseLibrary(void* library) { + if (library) { + dlclose(library); + library = nullptr; + } +} + +static void* g_lib_egl = nullptr; + +RTC_NO_SANITIZE("cfi-icall") +static bool OpenEGL() { + g_lib_egl = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL); + if (g_lib_egl) { + EglGetProcAddress = + (eglGetProcAddress_func)dlsym(g_lib_egl, "eglGetProcAddress"); + return EglGetProcAddress; + } + + return false; +} + +RTC_NO_SANITIZE("cfi-icall") +static bool LoadEGL() { + if (OpenEGL()) { + EglBindAPI = (eglBindAPI_func)EglGetProcAddress("eglBindAPI"); + EglCreateContext = + (eglCreateContext_func)EglGetProcAddress("eglCreateContext"); + EglDestroyContext = + (eglDestroyContext_func)EglGetProcAddress("eglDestroyContext"); + EglTerminate = (eglTerminate_func)EglGetProcAddress("eglTerminate"); + EglCreateImageKHR = + (eglCreateImageKHR_func)EglGetProcAddress("eglCreateImageKHR"); + EglDestroyImageKHR = + (eglDestroyImageKHR_func)EglGetProcAddress("eglDestroyImageKHR"); + EglGetError = (eglGetError_func)EglGetProcAddress("eglGetError"); + EglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_func)EglGetProcAddress( + "eglGetPlatformDisplayEXT"); + EglGetPlatformDisplay = + (eglGetPlatformDisplay_func)EglGetProcAddress("eglGetPlatformDisplay"); + EglInitialize = (eglInitialize_func)EglGetProcAddress("eglInitialize"); + EglMakeCurrent = (eglMakeCurrent_func)EglGetProcAddress("eglMakeCurrent"); + EglQueryString = (eglQueryString_func)EglGetProcAddress("eglQueryString"); + GlEGLImageTargetTexture2DOES = + (glEGLImageTargetTexture2DOES_func)EglGetProcAddress( + "glEGLImageTargetTexture2DOES"); + + return EglBindAPI && EglCreateContext && EglCreateImageKHR && + EglTerminate && EglDestroyContext && EglDestroyImageKHR && + EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay && + EglInitialize && EglMakeCurrent && EglQueryString && + GlEGLImageTargetTexture2DOES; + } + + return false; +} + +static void* g_lib_gl = nullptr; + +RTC_NO_SANITIZE("cfi-icall") +static bool OpenGL() { + std::vector<std::string> names = {"libGL.so.1", "libGL.so"}; + for (const std::string& name : names) { + g_lib_gl = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (g_lib_gl) { + GlXGetProcAddressARB = + (glXGetProcAddressARB_func)dlsym(g_lib_gl, "glXGetProcAddressARB"); + return GlXGetProcAddressARB; + } + } + + return false; +} + +RTC_NO_SANITIZE("cfi-icall") +static bool LoadGL() { + if (OpenGL()) { + GlGetString = (glGetString_func)GlXGetProcAddressARB("glGetString"); + if (!GlGetString) { + return false; + } + + GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture"); + GlDeleteTextures = + (glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures"); + GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures"); + GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError"); + GlGetTexImage = (glGetTexImage_func)GlXGetProcAddressARB("glGetTexImage"); + GlTexParameteri = + (glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri"); + + return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError && + GlGetTexImage && GlTexParameteri; + } + + return false; +} + +RTC_NO_SANITIZE("cfi-icall") +EglDmaBuf::EglDmaBuf() { + if (!LoadEGL()) { + RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions."; + CloseLibrary(g_lib_egl); + return; + } + + if (!LoadGL()) { + RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions."; + CloseLibrary(g_lib_gl); + return; + } + + if (!GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS)) { + return; + } + + bool has_platform_base_ext = false; + bool has_platform_gbm_ext = false; + bool has_khr_platform_gbm_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_platform_base") { + has_platform_base_ext = true; + continue; + } else if (extension == "EGL_MESA_platform_gbm") { + has_platform_gbm_ext = true; + continue; + } else if (extension == "EGL_KHR_platform_gbm") { + has_khr_platform_gbm_ext = true; + continue; + } + } + + if (!has_platform_base_ext || !has_platform_gbm_ext || + !has_khr_platform_gbm_ext) { + RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing"; + return; + } + + egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, + (void*)EGL_DEFAULT_DISPLAY, nullptr); + + if (egl_.display == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: " + << FormatEGLError(EglGetError()) << "\n" + << "Defaulting to using first available render node"; + absl::optional<std::string> render_node = GetRenderNode(); + if (!render_node) { + return; + } + + drm_fd_ = open(render_node->c_str(), O_RDWR); + + if (drm_fd_ < 0) { + RTC_LOG(LS_ERROR) << "Failed to open drm render node: " + << strerror(errno); + return; + } + + gbm_device_ = gbm_create_device(drm_fd_); + + if (!gbm_device_) { + RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno); + close(drm_fd_); + return; + } + + // Use eglGetPlatformDisplayEXT() to get the display pointer + // if the implementation supports it. + egl_.display = + EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device_, nullptr); + } + + if (egl_.display == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: " + << FormatEGLError(EglGetError()); + return; + } + + EGLint major, minor; + if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Error during eglInitialize: " + << FormatEGLError(EglGetError()); + return; + } + + if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "bind OpenGL API failed"; + return; + } + + egl_.context = + EglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr); + + if (egl_.context == EGL_NO_CONTEXT) { + RTC_LOG(LS_ERROR) << "Couldn't create EGL context: " + << FormatGLError(EglGetError()); + return; + } + + if (!GetClientExtensions(egl_.display, EGL_EXTENSIONS)) { + return; + } + + bool has_image_dma_buf_import_modifiers_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_image_dma_buf_import") { + has_image_dma_buf_import_ext_ = true; + continue; + } else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") { + has_image_dma_buf_import_modifiers_ext = true; + continue; + } + } + + if (has_image_dma_buf_import_ext_ && has_image_dma_buf_import_modifiers_ext) { + EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)EglGetProcAddress( + "eglQueryDmaBufFormatsEXT"); + EglQueryDmaBufModifiersEXT = + (eglQueryDmaBufModifiersEXT_func)EglGetProcAddress( + "eglQueryDmaBufModifiersEXT"); + } + + RTC_LOG(LS_INFO) << "Egl initialization succeeded"; + egl_initialized_ = true; +} + +RTC_NO_SANITIZE("cfi-icall") +EglDmaBuf::~EglDmaBuf() { + if (gbm_device_) { + gbm_device_destroy(gbm_device_); + close(drm_fd_); + } + + if (egl_.context != EGL_NO_CONTEXT) { + EglDestroyContext(egl_.display, egl_.context); + } + + if (egl_.display != EGL_NO_DISPLAY) { + EglTerminate(egl_.display); + } + + // BUG: crbug.com/1290566 + // Closing libEGL.so.1 when using NVidia drivers causes a crash + // when EglGetPlatformDisplayEXT() is used, at least this one is enough + // to be called to make it crash. + // It also looks that libepoxy and glad don't dlclose it either + // CloseLibrary(g_lib_egl); + // CloseLibrary(g_lib_gl); +} + +RTC_NO_SANITIZE("cfi-icall") +bool EglDmaBuf::GetClientExtensions(EGLDisplay dpy, EGLint name) { + // Get the list of client extensions + const char* client_extensions_cstring = EglQueryString(dpy, name); + if (!client_extensions_cstring) { + // If eglQueryString() returned NULL, the implementation doesn't support + // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. + RTC_LOG(LS_ERROR) << "No client extensions defined! " + << FormatEGLError(EglGetError()); + return false; + } + + std::vector<absl::string_view> client_extensions = + rtc::split(client_extensions_cstring, ' '); + for (const auto& extension : client_extensions) { + egl_.extensions.push_back(std::string(extension)); + } + + return true; +} + +RTC_NO_SANITIZE("cfi-icall") +std::unique_ptr<uint8_t[]> EglDmaBuf::ImageFromDmaBuf( + const DesktopSize& size, + uint32_t format, + const std::vector<PlaneData>& plane_datas, + uint64_t modifier) { + std::unique_ptr<uint8_t[]> src; + + if (!egl_initialized_) { + return src; + } + + if (plane_datas.size() <= 0) { + RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes"; + return src; + } + + EGLint attribs[47]; + int atti = 0; + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = static_cast<EGLint>(size.width()); + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = static_cast<EGLint>(size.height()); + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = SpaPixelFormatToDrmFormat(format); + + if (plane_datas.size() > 0) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[atti++] = plane_datas[0].fd; + attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[atti++] = plane_datas[0].offset; + attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[atti++] = plane_datas[0].stride; + + if (modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[atti++] = modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[atti++] = modifier >> 32; + } + } + + if (plane_datas.size() > 1) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT; + attribs[atti++] = plane_datas[1].fd; + attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; + attribs[atti++] = plane_datas[1].offset; + attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; + attribs[atti++] = plane_datas[1].stride; + + if (modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; + attribs[atti++] = modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; + attribs[atti++] = modifier >> 32; + } + } + + if (plane_datas.size() > 2) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT; + attribs[atti++] = plane_datas[2].fd; + attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; + attribs[atti++] = plane_datas[2].offset; + attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; + attribs[atti++] = plane_datas[2].stride; + + if (modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; + attribs[atti++] = modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; + attribs[atti++] = modifier >> 32; + } + } + + if (plane_datas.size() > 3) { + attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT; + attribs[atti++] = plane_datas[3].fd; + attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT; + attribs[atti++] = plane_datas[3].offset; + attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; + attribs[atti++] = plane_datas[3].stride; + + if (modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; + attribs[atti++] = modifier & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; + attribs[atti++] = modifier >> 32; + } + } + + attribs[atti++] = EGL_NONE; + + // bind context to render thread + EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context); + + // create EGL image from attribute list + EGLImageKHR image = EglCreateImageKHR( + egl_.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs); + + if (image == EGL_NO_IMAGE) { + RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImage - " + << FormatEGLError(EglGetError()); + return src; + } + + // create GL 2D texture for framebuffer + GLuint texture; + GlGenTextures(1, &texture); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + GlBindTexture(GL_TEXTURE_2D, texture); + GlEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + + src = std::make_unique<uint8_t[]>(plane_datas[0].stride * size.height()); + + GLenum gl_format = GL_BGRA; + switch (format) { + case SPA_VIDEO_FORMAT_RGBx: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_RGBA: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_BGRx: + gl_format = GL_BGRA; + break; + default: + gl_format = GL_BGRA; + break; + } + GlGetTexImage(GL_TEXTURE_2D, 0, gl_format, GL_UNSIGNED_BYTE, src.get()); + + if (GlGetError()) { + RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer."; + return src; + } + + GlDeleteTextures(1, &texture); + EglDestroyImageKHR(egl_.display, image); + + return src; +} + +RTC_NO_SANITIZE("cfi-icall") +std::vector<uint64_t> EglDmaBuf::QueryDmaBufModifiers(uint32_t format) { + if (!egl_initialized_) { + return {}; + } + + // Explicit modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we + // can still use modifier-less DMA-BUFs if we have required extension + if (EglQueryDmaBufFormatsEXT == nullptr || + EglQueryDmaBufModifiersEXT == nullptr) { + return has_image_dma_buf_import_ext_ + ? std::vector<uint64_t>{DRM_FORMAT_MOD_INVALID} + : std::vector<uint64_t>{}; + } + + uint32_t drm_format = SpaPixelFormatToDrmFormat(format); + // Should never happen as it's us who controls the list of supported formats + RTC_DCHECK(drm_format != DRM_FORMAT_INVALID); + + EGLint count = 0; + EGLBoolean success = + EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_WARNING) << "Cannot query the number of formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector<uint32_t> formats(count); + if (!EglQueryDmaBufFormatsEXT(egl_.display, count, + reinterpret_cast<EGLint*>(formats.data()), + &count)) { + RTC_LOG(LS_WARNING) << "Cannot query a list of formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) { + RTC_LOG(LS_WARNING) << "Format " << drm_format + << " not supported for modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr, + nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_WARNING) << "Cannot query the number of modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector<uint64_t> modifiers(count); + if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count, + modifiers.data(), nullptr, &count)) { + RTC_LOG(LS_WARNING) << "Cannot query a list of modifiers."; + } + + // Support modifier-less buffers + modifiers.push_back(DRM_FORMAT_MOD_INVALID); + return modifiers; +} + +absl::optional<std::string> EglDmaBuf::GetRenderNode() { + int max_devices = drmGetDevices2(0, nullptr, 0); + if (max_devices <= 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno=" + << -max_devices << ")"; + return absl::nullopt; + } + + std::vector<drmDevicePtr> devices(max_devices); + int ret = drmGetDevices2(0, devices.data(), max_devices); + if (ret < 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret; + return absl::nullopt; + } + + std::string render_node; + + for (const drmDevicePtr& device : devices) { + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + render_node = device->nodes[DRM_NODE_RENDER]; + break; + } + } + + drmFreeDevices(devices.data(), ret); + return render_node; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h new file mode 100644 index 0000000000..f1d96b2f80 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/egl_dmabuf.h @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_ + +#include <epoxy/egl.h> +#include <epoxy/gl.h> +#include <gbm.h> + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class EglDmaBuf { + public: + struct EGLStruct { + std::vector<std::string> extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; + }; + + struct PlaneData { + int32_t fd; + uint32_t stride; + uint32_t offset; + }; + + EglDmaBuf(); + ~EglDmaBuf(); + + std::unique_ptr<uint8_t[]> ImageFromDmaBuf( + const DesktopSize& size, + uint32_t format, + const std::vector<PlaneData>& plane_datas, + uint64_t modifiers); + std::vector<uint64_t> QueryDmaBufModifiers(uint32_t format); + + bool IsEglInitialized() const { return egl_initialized_; } + + private: + bool GetClientExtensions(EGLDisplay dpy, EGLint name); + + bool egl_initialized_ = false; + bool has_image_dma_buf_import_ext_ = false; + int32_t drm_fd_ = -1; // for GBM buffer mmap + gbm_device* gbm_device_ = nullptr; // for passed GBM buffer retrieval + + EGLStruct egl_; + + absl::optional<std::string> GetRenderNode(); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc new file mode 100644 index 0000000000..3d33b0fbb8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h" + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +MouseCursorMonitorPipeWire::MouseCursorMonitorPipeWire( + const DesktopCaptureOptions& options) + : options_(options) { + sequence_checker_.Detach(); +} + +MouseCursorMonitorPipeWire::~MouseCursorMonitorPipeWire() {} + +void MouseCursorMonitorPipeWire::Init(Callback* callback, Mode mode) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; + mode_ = mode; +} + +void MouseCursorMonitorPipeWire::Capture() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK(callback_); + + std::unique_ptr<MouseCursor> mouse_cursor = + options_.screencast_stream()->CaptureCursor(); + + if (mouse_cursor && mouse_cursor->image()->data()) { + callback_->OnMouseCursor(mouse_cursor.release()); + } + + if (mode_ == SHAPE_AND_POSITION) { + absl::optional<DesktopVector> mouse_cursor_position = + options_.screencast_stream()->CaptureCursorPosition(); + if (mouse_cursor_position) { + callback_->OnMouseCursorPosition(mouse_cursor_position.value()); + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h new file mode 100644 index 0000000000..da670bece9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h @@ -0,0 +1,44 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_ + +#include <memory> + +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" +#include "rtc_base/system/no_unique_address.h" + +namespace webrtc { + +class MouseCursorMonitorPipeWire : public MouseCursorMonitor { + public: + explicit MouseCursorMonitorPipeWire(const DesktopCaptureOptions& options); + ~MouseCursorMonitorPipeWire() override; + + // MouseCursorMonitor: + void Init(Callback* callback, Mode mode) override; + void Capture() override; + + DesktopCaptureOptions options_ RTC_GUARDED_BY(sequence_checker_); + Callback* callback_ RTC_GUARDED_BY(sequence_checker_) = nullptr; + Mode mode_ RTC_GUARDED_BY(sequence_checker_) = SHAPE_AND_POSITION; + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc new file mode 100644 index 0000000000..9bd7cec7ff --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc @@ -0,0 +1,1081 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h" + +#include <gio/gunixfdlist.h> +#include <glib-object.h> +#include <spa/param/format-utils.h> +#include <spa/param/props.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/syscall.h> + +#include <cstring> +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +#if defined(WEBRTC_DLOPEN_PIPEWIRE) +#include "modules/desktop_capture/linux/pipewire_stubs.h" +using modules_desktop_capture_linux::InitializeStubs; +using modules_desktop_capture_linux::kModulePipewire03; +using modules_desktop_capture_linux::StubPathMap; +#endif // defined(WEBRTC_DLOPEN_PIPEWIRE) + +namespace webrtc { + +const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; +const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; +const char kDesktopRequestObjectPath[] = + "/org/freedesktop/portal/desktop/request"; +const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; +const char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; +const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; + +const int kBytesPerPixel = 4; + +#if defined(WEBRTC_DLOPEN_PIPEWIRE) +const char kPipeWireLib[] = "libpipewire-0.3.so.0"; +#endif + +// static +struct dma_buf_sync { + uint64_t flags; +}; +#define DMA_BUF_SYNC_READ (1 << 0) +#define DMA_BUF_SYNC_START (0 << 2) +#define DMA_BUF_SYNC_END (1 << 2) +#define DMA_BUF_BASE 'b' +#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) + +static void SyncDmaBuf(int fd, uint64_t start_or_end) { + struct dma_buf_sync sync = {0}; + + sync.flags = start_or_end | DMA_BUF_SYNC_READ; + + while (true) { + int ret; + ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); + if (ret == -1 && errno == EINTR) { + continue; + } else if (ret == -1) { + RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " + << g_strerror(errno); + break; + } else { + break; + } + } +} + +class ScopedBuf { + public: + ScopedBuf() {} + ScopedBuf(unsigned char* map, int map_size, bool is_dma_buf, int fd) + : map_(map), map_size_(map_size), is_dma_buf_(is_dma_buf), fd_(fd) {} + ~ScopedBuf() { + if (map_ != MAP_FAILED) { + if (is_dma_buf_) { + SyncDmaBuf(fd_, DMA_BUF_SYNC_END); + } + munmap(map_, map_size_); + } + } + + operator bool() { return map_ != MAP_FAILED; } + + void initialize(unsigned char* map, int map_size, bool is_dma_buf, int fd) { + map_ = map; + map_size_ = map_size; + is_dma_buf_ = is_dma_buf; + fd_ = fd; + } + + unsigned char* get() { return map_; } + + protected: + unsigned char* map_ = nullptr; + int map_size_; + bool is_dma_buf_; + int fd_; +}; + +template <class T> +class Scoped { + public: + Scoped() {} + explicit Scoped(T* val) { ptr_ = val; } + ~Scoped() { RTC_DCHECK_NOTREACHED(); } + + T* operator->() { return ptr_; } + + bool operator!() { return ptr_ == nullptr; } + + T* get() { return ptr_; } + + T** receive() { + RTC_CHECK(!ptr_); + return &ptr_; + } + + Scoped& operator=(T* val) { + ptr_ = val; + return *this; + } + + protected: + T* ptr_ = nullptr; +}; + +template <> +Scoped<GError>::~Scoped() { + if (ptr_) { + g_error_free(ptr_); + } +} + +template <> +Scoped<gchar>::~Scoped() { + if (ptr_) { + g_free(ptr_); + } +} + +template <> +Scoped<GVariant>::~Scoped() { + if (ptr_) { + g_variant_unref(ptr_); + } +} + +template <> +Scoped<GVariantIter>::~Scoped() { + if (ptr_) { + g_variant_iter_free(ptr_); + } +} + +template <> +Scoped<GDBusMessage>::~Scoped() { + if (ptr_) { + g_object_unref(ptr_); + } +} + +template <> +Scoped<GUnixFDList>::~Scoped() { + if (ptr_) { + g_object_unref(ptr_); + } +} + +void BaseCapturerPipeWire::OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); + RTC_DCHECK(that); + + RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message; +} + +// static +void BaseCapturerPipeWire::OnStreamStateChanged(void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); + RTC_DCHECK(that); + + switch (state) { + case PW_STREAM_STATE_ERROR: + RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message; + break; + case PW_STREAM_STATE_PAUSED: + case PW_STREAM_STATE_STREAMING: + case PW_STREAM_STATE_UNCONNECTED: + case PW_STREAM_STATE_CONNECTING: + break; + } +} + +// static +void BaseCapturerPipeWire::OnStreamParamChanged(void* data, + uint32_t id, + const struct spa_pod* format) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "PipeWire stream format changed."; + + if (!format || id != SPA_PARAM_Format) { + return; + } + + spa_format_video_raw_parse(format, &that->spa_video_format_); + + auto width = that->spa_video_format_.size.width; + auto height = that->spa_video_format_.size.height; + // In order to be able to build in the non unified environment kBytesPerPixel + // must be fully qualified, see Bug 1725145 + auto stride = SPA_ROUND_UP_N(width * BasicDesktopFrame::kBytesPerPixel, 4); + auto size = height * stride; + + that->desktop_size_ = DesktopSize(width, height); + + uint8_t buffer[1024] = {}; + auto builder = spa_pod_builder{buffer, sizeof(buffer)}; + + // Setup buffers and meta header for new format. + const struct spa_pod* params[3]; + params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemPtr) | + (1<<SPA_DATA_MemFd)), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, + SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers, + SPA_POD_CHOICE_RANGE_Int(8, 1, 32))); + params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_header)))); + params[2] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_region)))); + pw_stream_update_params(that->pw_stream_, params, 3); +} + +// static +void BaseCapturerPipeWire::OnStreamProcess(void* data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data); + RTC_DCHECK(that); + + struct pw_buffer* next_buffer; + struct pw_buffer* buffer = nullptr; + + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); + while (next_buffer) { + buffer = next_buffer; + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); + + if (next_buffer) { + pw_stream_queue_buffer(that->pw_stream_, buffer); + } + } + + if (!buffer) { + return; + } + + that->HandleBuffer(buffer); + + pw_stream_queue_buffer(that->pw_stream_, buffer); +} + +BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type) + : capture_source_type_(source_type) {} + +BaseCapturerPipeWire::~BaseCapturerPipeWire() { + if (pw_main_loop_) { + pw_thread_loop_stop(pw_main_loop_); + } + + if (pw_stream_) { + pw_stream_destroy(pw_stream_); + } + + if (pw_core_) { + pw_core_disconnect(pw_core_); + } + + if (pw_context_) { + pw_context_destroy(pw_context_); + } + + if (pw_main_loop_) { + pw_thread_loop_destroy(pw_main_loop_); + } + + if (start_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); + } + if (sources_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + sources_request_signal_id_); + } + if (session_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + session_request_signal_id_); + } + + if (session_handle_) { + Scoped<GDBusMessage> message(g_dbus_message_new_method_call( + kDesktopBusName, session_handle_, kSessionInterfaceName, "Close")); + if (message.get()) { + Scoped<GError> error; + g_dbus_connection_send_message(connection_, message.get(), + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + /*out_serial=*/nullptr, error.receive()); + if (error.get()) { + RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message; + } + } + } + + g_free(start_handle_); + g_free(sources_handle_); + g_free(session_handle_); + g_free(portal_handle_); + + if (cancellable_) { + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + cancellable_ = nullptr; + } + + if (proxy_) { + g_object_unref(proxy_); + proxy_ = nullptr; + } + + if (pw_fd_ != -1) { + close(pw_fd_); + } +} + +void BaseCapturerPipeWire::InitPortal() { + cancellable_ = g_cancellable_new(); + g_dbus_proxy_new_for_bus( + G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr, + kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName, + cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this); +} + +void BaseCapturerPipeWire::InitPipeWire() { +#if defined(WEBRTC_DLOPEN_PIPEWIRE) + StubPathMap paths; + + // Check if the PipeWire library is available. + paths[kModulePipewire03].push_back(kPipeWireLib); + if (!InitializeStubs(paths)) { + RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols."; + portal_init_failed_ = true; + return; + } +#endif // defined(WEBRTC_DLOPEN_PIPEWIRE) + + pw_init(/*argc=*/nullptr, /*argc=*/nullptr); + + pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); + + pw_thread_loop_lock(pw_main_loop_); + + pw_context_ = + pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0); + if (!pw_context_) { + RTC_LOG(LS_ERROR) << "Failed to create PipeWire context"; + return; + } + + pw_core_ = pw_context_connect_fd(pw_context_, pw_fd_, nullptr, 0); + if (!pw_core_) { + RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context"; + return; + } + + // Initialize event handlers, remote end and stream-related. + pw_core_events_.version = PW_VERSION_CORE_EVENTS; + pw_core_events_.error = &OnCoreError; + + pw_stream_events_.version = PW_VERSION_STREAM_EVENTS; + pw_stream_events_.state_changed = &OnStreamStateChanged; + pw_stream_events_.param_changed = &OnStreamParamChanged; + pw_stream_events_.process = &OnStreamProcess; + + pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this); + + pw_stream_ = CreateReceivingStream(); + if (!pw_stream_) { + RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream"; + return; + } + + if (pw_thread_loop_start(pw_main_loop_) < 0) { + RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; + portal_init_failed_ = true; + } + + pw_thread_loop_unlock(pw_main_loop_); + + RTC_LOG(LS_INFO) << "PipeWire remote opened."; +} + +pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { + spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1}; + spa_rectangle pwMaxScreenBounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; + + pw_properties* reuseProps = + pw_properties_new_string("pipewire.client.reuse=1"); + auto stream = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps); + + uint8_t buffer[1024] = {}; + const spa_pod* params[1]; + spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; + + params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, + SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, + SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, + SPA_VIDEO_FORMAT_BGRA), + SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds, &pwMinScreenBounds, + &pwMaxScreenBounds), + 0)); + + pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, + this); + if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_, + PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) { + RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; + portal_init_failed_ = true; + return nullptr; + } + + return stream; +} + +void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { + spa_buffer* spaBuffer = buffer->buffer; + ScopedBuf map; + uint8_t* src = nullptr; + + if (spaBuffer->datas[0].chunk->size == 0) { + RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size."; + return; + } + + if (spaBuffer->datas[0].type == SPA_DATA_MemFd || + spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { + map.initialize( + static_cast<uint8_t*>( + mmap(nullptr, + spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, + PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)), + spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, + spaBuffer->datas[0].type == SPA_DATA_DmaBuf, + spaBuffer->datas[0].fd); + + if (!map) { + RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " + << std::strerror(errno); + return; + } + + if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { + SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START); + } + + src = SPA_MEMBER(map.get(), spaBuffer->datas[0].mapoffset, uint8_t); + } else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) { + src = static_cast<uint8_t*>(spaBuffer->datas[0].data); + } + + if (!src) { + return; + } + + struct spa_meta_region* video_metadata = + static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data( + spaBuffer, SPA_META_VideoCrop, sizeof(*video_metadata))); + + // Video size from metadata is bigger than an actual video stream size. + // The metadata are wrong or we should up-scale the video...in both cases + // just quit now. + if (video_metadata && (video_metadata->region.size.width > + static_cast<uint32_t>(desktop_size_.width()) || + video_metadata->region.size.height > + static_cast<uint32_t>(desktop_size_.height()))) { + RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!"; + return; + } + + // Use video metadata when video size from metadata is set and smaller than + // video stream size, so we need to adjust it. + bool video_metadata_use = false; + + const struct spa_rectangle* video_metadata_size = + video_metadata ? &video_metadata->region.size : nullptr; + + if (video_metadata_size && video_metadata_size->width != 0 && + video_metadata_size->height != 0 && + (static_cast<int>(video_metadata_size->width) < desktop_size_.width() || + static_cast<int>(video_metadata_size->height) < + desktop_size_.height())) { + video_metadata_use = true; + } + + DesktopSize video_size_prev = video_size_; + if (video_metadata_use) { + video_size_ = + DesktopSize(video_metadata_size->width, video_metadata_size->height); + } else { + video_size_ = desktop_size_; + } + + webrtc::MutexLock lock(¤t_frame_lock_); + if (!current_frame_ || !video_size_.equals(video_size_prev)) { + current_frame_ = std::make_unique<uint8_t[]>( + video_size_.width() * video_size_.height() * BasicDesktopFrame::kBytesPerPixel); + } + + const int32_t dst_stride = video_size_.width() * BasicDesktopFrame::kBytesPerPixel; + const int32_t src_stride = spaBuffer->datas[0].chunk->stride; + + if (src_stride != (desktop_size_.width() * BasicDesktopFrame::kBytesPerPixel)) { + RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: " + << src_stride + << " != " << (desktop_size_.width() * BasicDesktopFrame::kBytesPerPixel); + portal_init_failed_ = true; + + return; + } + + // Adjust source content based on metadata video position + if (video_metadata_use && + (video_metadata->region.position.y + video_size_.height() <= + desktop_size_.height())) { + src += src_stride * video_metadata->region.position.y; + } + const int x_offset = + video_metadata_use && + (video_metadata->region.position.x + video_size_.width() <= + desktop_size_.width()) + ? video_metadata->region.position.x * BasicDesktopFrame::kBytesPerPixel + : 0; + + uint8_t* dst = current_frame_.get(); + for (int i = 0; i < video_size_.height(); ++i) { + // Adjust source content based on crop video position if needed + src += x_offset; + std::memcpy(dst, src, dst_stride); + // If both sides decided to go with the RGBx format we need to convert it to + // BGRx to match color format expected by WebRTC. + if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx || + spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) { + ConvertRGBxToBGRx(dst, dst_stride); + } + src += src_stride - x_offset; + dst += dst_stride; + } +} + +void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) { + // Change color format for KDE KWin which uses RGBx and not BGRx + for (uint32_t i = 0; i < size; i += 4) { + uint8_t tempR = frame[i]; + uint8_t tempB = frame[i + 2]; + frame[i] = tempB; + frame[i + 2] = tempR; + } +} + +guint BaseCapturerPipeWire::SetupRequestResponseSignal( + const gchar* object_path, + GDBusSignalCallback callback) { + return g_dbus_connection_signal_subscribe( + connection_, kDesktopBusName, kRequestInterfaceName, "Response", + object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + callback, this, /*user_data_free_func=*/nullptr); +} + +// static +void BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/, + GAsyncResult* result, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); + if (!proxy) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: " + << error->message; + that->portal_init_failed_ = true; + return; + } + that->proxy_ = proxy; + that->connection_ = g_dbus_proxy_get_connection(that->proxy_); + + RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal."; + that->SessionRequest(); +} + +// static +gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection, + const gchar* token) { + Scoped<gchar> sender( + g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); + for (int i = 0; sender.get()[i]; i++) { + if (sender.get()[i] == '.') { + sender.get()[i] = '_'; + } + } + + gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), "/", + token, /*end of varargs*/ nullptr); + + return handle; +} + +void BaseCapturerPipeWire::SessionRequest() { + GVariantBuilder builder; + Scoped<gchar> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = + g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "session_handle_token", + g_variant_new_string(variant_string.get())); + variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + portal_handle_ = PrepareSignalHandle(connection_, variant_string.get()); + session_request_signal_id_ = SetupRequestResponseSignal( + portal_handle_, OnSessionRequestResponseSignal); + + RTC_LOG(LS_INFO) << "Screen cast session requested."; + g_dbus_proxy_call( + proxy_, "CreateSession", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnSessionRequested), this); +} + +// static +void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: " + << error->message; + that->portal_init_failed_ = true; + return; + } + RTC_LOG(LS_INFO) << "Initializing the screen cast session."; + + Scoped<gchar> handle; + g_variant_get_child(variant.get(), 0, "o", &handle); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; + if (that->session_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->session_request_signal_id_); + that->session_request_signal_id_ = 0; + } + that->portal_init_failed_ = true; + return; + } + + RTC_LOG(LS_INFO) << "Subscribing to the screen cast session."; +} + +// static +void BaseCapturerPipeWire::OnSessionRequestResponseSignal( + GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) + << "Received response for the screen cast session subscription."; + + guint32 portal_response; + Scoped<GVariant> response_data; + g_variant_get(parameters, "(u@a{sv})", &portal_response, + response_data.receive()); + g_variant_lookup(response_data.get(), "session_handle", "s", + &that->session_handle_); + + if (!that->session_handle_ || portal_response) { + RTC_LOG(LS_ERROR) + << "Failed to request the screen cast session subscription."; + that->portal_init_failed_ = true; + return; + } + + that->SourcesRequest(); +} + +void BaseCapturerPipeWire::SourcesRequest() { + GVariantBuilder builder; + Scoped<gchar> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + // We want to record monitor content. + g_variant_builder_add( + &builder, "{sv}", "types", + g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_))); + // We don't want to allow selection of multiple sources. + g_variant_builder_add(&builder, "{sv}", "multiple", + g_variant_new_boolean(false)); + + Scoped<GVariant> variant( + g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes")); + if (variant.get()) { + uint32_t modes = 0; + g_variant_get(variant.get(), "u", &modes); + // Request mouse cursor to be embedded as part of the stream, otherwise it + // is hidden by default. Make request only if this mode is advertised by + // the portal implementation. + if (modes & + static_cast<uint32_t>(BaseCapturerPipeWire::CursorMode::kEmbedded)) { + g_variant_builder_add(&builder, "{sv}", "cursor_mode", + g_variant_new_uint32(static_cast<uint32_t>( + BaseCapturerPipeWire::CursorMode::kEmbedded))); + } + } + + variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + sources_handle_ = PrepareSignalHandle(connection_, variant_string.get()); + sources_request_signal_id_ = SetupRequestResponseSignal( + sources_handle_, OnSourcesRequestResponseSignal); + + RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session."; + g_dbus_proxy_call( + proxy_, "SelectSources", + g_variant_new("(oa{sv})", session_handle_, &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this); +} + +// static +void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message; + that->portal_init_failed_ = true; + return; + } + + RTC_LOG(LS_INFO) << "Sources requested from the screen cast session."; + + Scoped<gchar> handle; + g_variant_get_child(variant.get(), 0, "o", handle.receive()); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; + if (that->sources_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->sources_request_signal_id_); + that->sources_request_signal_id_ = 0; + } + that->portal_init_failed_ = true; + return; + } + + RTC_LOG(LS_INFO) << "Subscribed to sources signal."; +} + +// static +void BaseCapturerPipeWire::OnSourcesRequestResponseSignal( + GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "Received sources signal from session."; + + guint32 portal_response; + g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); + if (portal_response) { + RTC_LOG(LS_ERROR) + << "Failed to select sources for the screen cast session."; + that->portal_init_failed_ = true; + return; + } + + that->StartRequest(); +} + +void BaseCapturerPipeWire::StartRequest() { + GVariantBuilder builder; + Scoped<gchar> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + start_handle_ = PrepareSignalHandle(connection_, variant_string.get()); + start_request_signal_id_ = + SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal); + + // "Identifier for the application window", this is Wayland, so not "x11:...". + const gchar parent_window[] = ""; + + RTC_LOG(LS_INFO) << "Starting the screen cast session."; + g_dbus_proxy_call( + proxy_, "Start", + g_variant_new("(osa{sv})", session_handle_, parent_window, &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), this); +} + +// static +void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: " + << error->message; + that->portal_init_failed_ = true; + return; + } + + RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session."; + + Scoped<gchar> handle; + g_variant_get_child(variant.get(), 0, "o", handle.receive()); + if (!handle) { + RTC_LOG(LS_ERROR) + << "Failed to initialize the start of the screen cast session."; + if (that->start_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->start_request_signal_id_); + that->start_request_signal_id_ = 0; + } + that->portal_init_failed_ = true; + return; + } + + RTC_LOG(LS_INFO) << "Subscribed to the start signal."; +} + +// static +void BaseCapturerPipeWire::OnStartRequestResponseSignal( + GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "Start signal received."; + guint32 portal_response; + Scoped<GVariant> response_data; + Scoped<GVariantIter> iter; + g_variant_get(parameters, "(u@a{sv})", &portal_response, + response_data.receive()); + if (portal_response || !response_data) { + RTC_LOG(LS_ERROR) << "Failed to start the screen cast session."; + that->portal_init_failed_ = true; + return; + } + + // Array of PipeWire streams. See + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + // documentation for <method name="Start">. + if (g_variant_lookup(response_data.get(), "streams", "a(ua{sv})", + iter.receive())) { + Scoped<GVariant> variant; + + while (g_variant_iter_next(iter.get(), "@(ua{sv})", variant.receive())) { + guint32 stream_id; + guint32 type; + Scoped<GVariant> options; + + g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive()); + RTC_DCHECK(options.get()); + + if (g_variant_lookup(options.get(), "source_type", "u", &type)) { + that->capture_source_type_ = + static_cast<BaseCapturerPipeWire::CaptureSourceType>(type); + } + + that->pw_stream_node_id_ = stream_id; + + break; + } + } + + that->OpenPipeWireRemote(); +} + +void BaseCapturerPipeWire::OpenPipeWireRemote() { + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + RTC_LOG(LS_INFO) << "Opening the PipeWire remote."; + + g_dbus_proxy_call_with_unix_fd_list( + proxy_, "OpenPipeWireRemote", + g_variant_new("(oa{sv})", session_handle_, &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, + cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested), + this); +} + +// static +void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested( + GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data) { + BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GUnixFDList> outlist; + Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish( + proxy, outlist.receive(), result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: " + << error->message; + that->portal_init_failed_ = true; + return; + } + + gint32 index; + g_variant_get(variant.get(), "(h)", &index); + + if ((that->pw_fd_ = + g_unix_fd_list_get(outlist.get(), index, error.receive())) == -1) { + RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " + << error->message; + that->portal_init_failed_ = true; + return; + } + + that->InitPipeWire(); +} + +void BaseCapturerPipeWire::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + InitPortal(); + + callback_ = callback; +} + +void BaseCapturerPipeWire::CaptureFrame() { + if (portal_init_failed_) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + webrtc::MutexLock lock(¤t_frame_lock_); + if (!current_frame_) { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + DesktopSize frame_size = video_size_; + + std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(frame_size)); + result->CopyPixelsFrom( + current_frame_.get(), (frame_size.width() * BasicDesktopFrame::kBytesPerPixel), + DesktopRect::MakeWH(frame_size.width(), frame_size.height())); + if (!result) { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on the + // frame, see ScreenCapturerX11::CaptureFrame. + + callback_->OnCaptureResult(Result::SUCCESS, std::move(result)); +} + +// Keep in sync with defines at browser/actors/WebRTCParent.jsm +// With PipeWire we can't select which system resource is shared so +// we don't create a window/screen list. Instead we place these constants +// as window name/id so frontend code can identify PipeWire backend +// and does not try to create screen/window preview. + +#define PIPEWIRE_ID 0xaffffff +#define PIPEWIRE_NAME "####_PIPEWIRE_PORTAL_####" + +bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) { + sources->push_back({PIPEWIRE_ID, 0, PIPEWIRE_NAME}); + return true; +} + +bool BaseCapturerPipeWire::SelectSource(SourceId id) { + // Screen selection is handled by the xdg-desktop-portal. + return id == PIPEWIRE_ID; +} + +// static +std::unique_ptr<DesktopCapturer> BaseCapturerPipeWire::CreateRawCapturer( + const DesktopCaptureOptions& options) { + return std::make_unique<BaseCapturerPipeWire>( + BaseCapturerPipeWire::CaptureSourceType::kAny); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h new file mode 100644 index 0000000000..a3d43336fd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h @@ -0,0 +1,182 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_BASE_CAPTURER_PIPEWIRE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_BASE_CAPTURER_PIPEWIRE_H_ +#include <gio/gio.h> +#define typeof __typeof__ +#include <pipewire/pipewire.h> +#include <spa/param/video/format-utils.h> +#include <spa/utils/result.h> + +#include "absl/types/optional.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +class BaseCapturerPipeWire : public DesktopCapturer { + public: + // Values are set based on source type property in + // xdg-desktop-portal/screencast + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + enum class CaptureSourceType : uint32_t { + kScreen = 0b01, + kWindow = 0b10, + kAny = 0b11 + }; + + enum class CursorMode : uint32_t { + kHidden = 0b01, + kEmbedded = 0b10, + kMetadata = 0b100 + }; + + explicit BaseCapturerPipeWire(CaptureSourceType source_type); + ~BaseCapturerPipeWire() override; + + BaseCapturerPipeWire(const BaseCapturerPipeWire&) = delete; + BaseCapturerPipeWire& operator=(const BaseCapturerPipeWire&) = delete; + + static std::unique_ptr<DesktopCapturer> CreateRawCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* delegate) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + // PipeWire types --> + struct pw_context* pw_context_ = nullptr; + struct pw_core* pw_core_ = nullptr; + struct pw_stream* pw_stream_ = nullptr; + struct pw_thread_loop* pw_main_loop_ = nullptr; + + spa_hook spa_core_listener_; + spa_hook spa_stream_listener_; + + // event handlers + pw_core_events pw_core_events_ = {}; + pw_stream_events pw_stream_events_ = {}; + + struct spa_video_info_raw spa_video_format_; + + guint32 pw_stream_node_id_ = 0; + gint32 pw_fd_ = -1; + + CaptureSourceType capture_source_type_ = + BaseCapturerPipeWire::CaptureSourceType::kScreen; + + // <-- end of PipeWire types + + GDBusConnection* connection_ = nullptr; + GDBusProxy* proxy_ = nullptr; + GCancellable *cancellable_ = nullptr; + gchar* portal_handle_ = nullptr; + gchar* session_handle_ = nullptr; + gchar* sources_handle_ = nullptr; + gchar* start_handle_ = nullptr; + guint session_request_signal_id_ = 0; + guint sources_request_signal_id_ = 0; + guint start_request_signal_id_ = 0; + + DesktopSize video_size_; + DesktopSize desktop_size_ = {}; + DesktopCaptureOptions options_ = {}; + + webrtc::Mutex current_frame_lock_; + std::unique_ptr<uint8_t[]> current_frame_; + Callback* callback_ = nullptr; + + bool portal_init_failed_ = false; + + void InitPortal(); + void InitPipeWire(); + void InitPipeWireTypes(); + + pw_stream* CreateReceivingStream(); + void HandleBuffer(pw_buffer* buffer); + + void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size); + + static void OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message); + static void OnStreamParamChanged(void* data, + uint32_t id, + const struct spa_pod* format); + static void OnStreamStateChanged(void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message); + + static void OnStreamProcess(void* data); + static void OnNewBuffer(void* data, uint32_t id); + + guint SetupRequestResponseSignal(const gchar* object_path, + GDBusSignalCallback callback); + + static void OnProxyRequested(GObject* object, + GAsyncResult* result, + gpointer user_data); + + static gchar* PrepareSignalHandle(GDBusConnection* connection, + const gchar* token); + + void SessionRequest(); + static void OnSessionRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data); + static void OnSessionRequestResponseSignal(GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data); + + void SourcesRequest(); + static void OnSourcesRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data); + static void OnSourcesRequestResponseSignal(GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data); + + void StartRequest(); + static void OnStartRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data); + static void OnStartRequestResponseSignal(GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data); + + void OpenPipeWireRemote(); + static void OnOpenPipeWireRemoteRequested(GDBusProxy *proxy, + GAsyncResult* result, + gpointer user_data); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_BASE_CAPTURER_PIPEWIRE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire.sigs b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire.sigs new file mode 100644 index 0000000000..06a97b8f29 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire.sigs @@ -0,0 +1,50 @@ +// Copyright 2018 The WebRTC project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//------------------------------------------------ +// Functions from PipeWire used in capturer code. +//------------------------------------------------ + +// core.h +int pw_core_disconnect(pw_core *core); + +// loop.h +void pw_loop_destroy(pw_loop *loop); +pw_loop * pw_loop_new(const spa_dict *props); + + +// pipewire.h +void pw_init(int *argc, char **argv[]); +const char* pw_get_library_version(); + +// properties.h +pw_properties * pw_properties_new_string(const char *args); + +// stream.h +void pw_stream_add_listener(pw_stream *stream, spa_hook *listener, const pw_stream_events *events, void *data); +int pw_stream_connect(pw_stream *stream, enum pw_direction direction, uint32_t target_id, enum pw_stream_flags flags, const spa_pod **params, uint32_t n_params); +int pw_stream_disconnect(pw_stream *stream); +pw_buffer *pw_stream_dequeue_buffer(pw_stream *stream); +void pw_stream_destroy(pw_stream *stream); +pw_stream * pw_stream_new(pw_core *core, const char *name, pw_properties *props); +int pw_stream_queue_buffer(pw_stream *stream, pw_buffer *buffer); +int pw_stream_set_active(pw_stream *stream, bool active); +int pw_stream_update_params(pw_stream *stream, const spa_pod **params, uint32_t n_params); + +// thread-loop.h +void pw_thread_loop_destroy(pw_thread_loop *loop); +pw_thread_loop * pw_thread_loop_new(const char *name, const spa_dict *props); +int pw_thread_loop_start(pw_thread_loop *loop); +void pw_thread_loop_stop(pw_thread_loop *loop); +void pw_thread_loop_lock(pw_thread_loop *loop); +void pw_thread_loop_unlock(pw_thread_loop *loop); +pw_loop * pw_thread_loop_get_loop(pw_thread_loop *loop); +void pw_thread_loop_signal(pw_thread_loop *loop, bool wait_for_accept); +void pw_thread_loop_wait(pw_thread_loop *loop); + +// context.h +void pw_context_destroy(pw_context *context); +pw_context *pw_context_new(pw_loop *main_loop, pw_properties *props, size_t user_data_size); +pw_core * pw_context_connect(pw_context *context, pw_properties *properties, size_t user_data_size); +pw_core * pw_context_connect_fd(pw_context *context, int fd, pw_properties *properties, size_t user_data_size); diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire_stub_header.fragment b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire_stub_header.fragment new file mode 100644 index 0000000000..06ae18dfd4 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/pipewire_stub_header.fragment @@ -0,0 +1,9 @@ +// The extra include header needed in the generated stub file for defining +// various PipeWire types. + +extern "C" { + +#include <pipewire/pipewire.h> + +#include <xf86drm.h> +} diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h new file mode 100644 index 0000000000..dde9ac5eff --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/portal_request_response.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_ + +namespace webrtc { +namespace xdg_portal { + +// Contains type of responses that can be observed when making a request to +// a desktop portal interface. +enum class RequestResponse { + // Unknown, the initialized status. + kUnknown, + // Success, the request is carried out. + kSuccess, + // The user cancelled the interaction. + kUserCancelled, + // The user interaction was ended in some other way. + kError, + + kMaxValue = kError, +}; + +} // namespace xdg_portal +} // namespace webrtc +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc new file mode 100644 index 0000000000..cc626d3065 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.cc @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/restore_token_manager.h" + +namespace webrtc { + +// static +RestoreTokenManager& RestoreTokenManager::GetInstance() { + static webrtc::RestoreTokenManager* manager = new RestoreTokenManager(); + return *manager; +} + +void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id, + const std::string& token) { + restore_tokens_.insert({id, token}); +} + +std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) { + std::string token = restore_tokens_[id]; + // Remove the token as it cannot be used anymore + restore_tokens_.erase(id); + return token; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h new file mode 100644 index 0000000000..37c9a39cac --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h @@ -0,0 +1,41 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_ + +#include <mutex> +#include <string> +#include <unordered_map> + +#include "modules/desktop_capture/desktop_capturer.h" + +namespace webrtc { + +class RestoreTokenManager { + public: + RestoreTokenManager(const RestoreTokenManager& manager) = delete; + RestoreTokenManager& operator=(const RestoreTokenManager& manager) = delete; + + static RestoreTokenManager& GetInstance(); + + void AddToken(DesktopCapturer::SourceId id, const std::string& token); + std::string TakeToken(DesktopCapturer::SourceId id); + + private: + RestoreTokenManager() = default; + ~RestoreTokenManager() = default; + + std::unordered_map<DesktopCapturer::SourceId, std::string> restore_tokens_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.cc new file mode 100644 index 0000000000..0d9a87d7fd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" + +namespace webrtc { + +template <> +Scoped<GError>::~Scoped() { + if (ptr_) { + g_error_free(ptr_); + } +} + +template <> +Scoped<char>::~Scoped() { + if (ptr_) { + g_free(ptr_); + } +} + +template <> +Scoped<GVariant>::~Scoped() { + if (ptr_) { + g_variant_unref(ptr_); + } +} + +template <> +Scoped<GVariantIter>::~Scoped() { + if (ptr_) { + g_variant_iter_free(ptr_); + } +} + +template <> +Scoped<GDBusMessage>::~Scoped() { + if (ptr_) { + g_object_unref(ptr_); + } +} + +template <> +Scoped<GUnixFDList>::~Scoped() { + if (ptr_) { + g_object_unref(ptr_); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h new file mode 100644 index 0000000000..908bd6f77d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/scoped_glib.h @@ -0,0 +1,65 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_ + +#include <gio/gio.h> + +#include "rtc_base/checks.h" + +namespace webrtc { + +template <class T> +class Scoped { + public: + Scoped() {} + explicit Scoped(T* val) { ptr_ = val; } + ~Scoped() { RTC_DCHECK_NOTREACHED(); } + + T* operator->() const { return ptr_; } + + explicit operator bool() const { return ptr_ != nullptr; } + + bool operator!() const { return ptr_ == nullptr; } + + T* get() const { return ptr_; } + + T** receive() { + RTC_CHECK(!ptr_); + return &ptr_; + } + + Scoped& operator=(T* val) { + RTC_DCHECK(val); + ptr_ = val; + return *this; + } + + protected: + T* ptr_ = nullptr; +}; + +template <> +Scoped<GError>::~Scoped(); +template <> +Scoped<char>::~Scoped(); +template <> +Scoped<GVariant>::~Scoped(); +template <> +Scoped<GVariantIter>::~Scoped(); +template <> +Scoped<GDBusMessage>::~Scoped(); +template <> +Scoped<GUnixFDList>::~Scoped(); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc new file mode 100644 index 0000000000..02d9d2e806 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.cc @@ -0,0 +1,127 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" + +#include <string> + +#include "rtc_base/logging.h" + +namespace webrtc { +namespace xdg_portal { + +void ScreenCapturePortalInterface::RequestSessionUsingProxy( + GAsyncResult* result) { + Scoped<GError> error; + GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); + if (!proxy) { + // Ignore the error caused by user cancelling the request via `cancellable_` + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: " + << error->message; + OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Successfully created proxy for the portal."; + RequestSession(proxy); +} + +void ScreenCapturePortalInterface::OnSessionRequestResult( + GDBusProxy* proxy, + GAsyncResult* result) { + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + // Ignore the error caused by user cancelling the request via `cancellable_` + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to request session: " << error->message; + OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Initializing the session."; + + Scoped<char> handle; + g_variant_get_child(variant.get(), /*index=*/0, /*format_string=*/"o", + &handle); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the session."; + OnPortalDone(RequestResponse::kError); + return; + } +} + +void ScreenCapturePortalInterface::RegisterSessionClosedSignalHandler( + const SessionClosedSignalHandler session_close_signal_handler, + GVariant* parameters, + GDBusConnection* connection, + std::string& session_handle, + guint& session_closed_signal_id) { + uint32_t portal_response = 2; + Scoped<GVariant> response_data; + g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response, + response_data.receive()); + + if (RequestResponseFromPortalResponse(portal_response) != + RequestResponse::kSuccess) { + RTC_LOG(LS_ERROR) << "Failed to request the session subscription."; + OnPortalDone(RequestResponse::kError); + return; + } + + Scoped<GVariant> g_session_handle( + g_variant_lookup_value(response_data.get(), /*key=*/"session_handle", + /*expected_type=*/nullptr)); + session_handle = g_variant_get_string( + /*value=*/g_session_handle.get(), /*length=*/nullptr); + + if (session_handle.empty()) { + RTC_LOG(LS_ERROR) << "Could not get session handle despite valid response"; + OnPortalDone(RequestResponse::kError); + return; + } + + session_closed_signal_id = g_dbus_connection_signal_subscribe( + connection, kDesktopBusName, kSessionInterfaceName, /*member=*/"Closed", + session_handle.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE, + session_close_signal_handler, this, /*user_data_free_func=*/nullptr); +} + +void ScreenCapturePortalInterface::OnStartRequestResult(GDBusProxy* proxy, + GAsyncResult* result) { + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to start the portal session: " + << error->message; + OnPortalDone(RequestResponse::kError); + return; + } + + Scoped<char> handle; + g_variant_get_child(variant.get(), 0, "o", handle.receive()); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the start portal session."; + OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Subscribed to the start signal."; +} + +} // namespace xdg_portal +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h new file mode 100644 index 0000000000..775ed1facc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_ + +#include <gio/gio.h> + +#include <string> + +#include "modules/desktop_capture/linux/wayland/portal_request_response.h" +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" +#include "modules/desktop_capture/linux/wayland/xdg_session_details.h" + +namespace webrtc { +namespace xdg_portal { + +using SessionClosedSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); + +// A base class for XDG desktop portals that can capture desktop/screen. +// Note: downstream clients inherit from this class so it is advisable to +// provide a default implementation of any new virtual methods that may be added +// to this class. +class ScreenCapturePortalInterface { + public: + virtual ~ScreenCapturePortalInterface() {} + // Gets details about the session such as session handle. + virtual xdg_portal::SessionDetails GetSessionDetails() { return {}; } + // Starts the portal setup. + virtual void Start() {} + // Notifies observers about the success/fail state of the portal + // request/response. + virtual void OnPortalDone(xdg_portal::RequestResponse result) {} + // Sends a create session request to the portal. + virtual void RequestSession(GDBusProxy* proxy) {} + + // Following methods should not be made virtual as they share a common + // implementation between portals. + + // Requests portal session using the proxy object. + void RequestSessionUsingProxy(GAsyncResult* result); + // Handles the session request result. + void OnSessionRequestResult(GDBusProxy* proxy, GAsyncResult* result); + // Subscribes to session close signal and sets up a handler for it. + void RegisterSessionClosedSignalHandler( + const SessionClosedSignalHandler session_close_signal_handler, + GVariant* parameters, + GDBusConnection* connection, + std::string& session_handle, + guint& session_closed_signal_id); + // Handles the result of session start request. + void OnStartRequestResult(GDBusProxy* proxy, GAsyncResult* result); +}; + +} // namespace xdg_portal +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc new file mode 100644 index 0000000000..8e45af7e24 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc @@ -0,0 +1,455 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/screencast_portal.h" + +#include <gio/gunixfdlist.h> +#include <glib-object.h> + +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +using xdg_portal::kScreenCastInterfaceName; +using xdg_portal::PrepareSignalHandle; +using xdg_portal::RequestResponse; +using xdg_portal::RequestSessionProxy; +using xdg_portal::SetupRequestResponseSignal; +using xdg_portal::SetupSessionRequestHandlers; +using xdg_portal::StartSessionRequest; +using xdg_portal::TearDownSession; +using xdg_portal::RequestResponseFromPortalResponse; + +} // namespace + +ScreenCastPortal::ScreenCastPortal( + ScreenCastPortal::CaptureSourceType source_type, + PortalNotifier* notifier) + : ScreenCastPortal(source_type, + notifier, + OnProxyRequested, + OnSourcesRequestResponseSignal, + this) {} + +ScreenCastPortal::ScreenCastPortal( + CaptureSourceType source_type, + PortalNotifier* notifier, + ProxyRequestResponseHandler proxy_request_response_handler, + SourcesRequestResponseSignalHandler sources_request_response_signal_handler, + gpointer user_data) + : notifier_(notifier), + capture_source_type_(source_type), + proxy_request_response_handler_(proxy_request_response_handler), + sources_request_response_signal_handler_( + sources_request_response_signal_handler), + user_data_(user_data) {} + +ScreenCastPortal::~ScreenCastPortal() { + Cleanup(); +} + +void ScreenCastPortal::Cleanup() { + UnsubscribeSignalHandlers(); + TearDownSession(std::move(session_handle_), proxy_, cancellable_, + connection_); + session_handle_ = ""; + cancellable_ = nullptr; + proxy_ = nullptr; + + if (pw_fd_ != -1) { + close(pw_fd_); + } +} + +void ScreenCastPortal::UnsubscribeSignalHandlers() { + if (start_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); + start_request_signal_id_ = 0; + } + + if (sources_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + sources_request_signal_id_); + sources_request_signal_id_ = 0; + } + + if (session_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + session_request_signal_id_); + session_request_signal_id_ = 0; + } +} + +void ScreenCastPortal::SetSessionDetails( + const xdg_portal::SessionDetails& session_details) { + if (session_details.proxy) { + proxy_ = session_details.proxy; + connection_ = g_dbus_proxy_get_connection(proxy_); + } + if (session_details.cancellable) { + cancellable_ = session_details.cancellable; + } + if (!session_details.session_handle.empty()) { + session_handle_ = session_details.session_handle; + } + if (session_details.pipewire_stream_node_id) { + pw_stream_node_id_ = session_details.pipewire_stream_node_id; + } +} + +void ScreenCastPortal::Start() { + cancellable_ = g_cancellable_new(); + RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_, + cancellable_, this); +} + +xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() { + return {}; // No-op +} + +void ScreenCastPortal::OnPortalDone(RequestResponse result) { + notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_); + if (result != RequestResponse::kSuccess) { + Cleanup(); + } +} + +// static +void ScreenCastPortal::OnProxyRequested(GObject* gobject, + GAsyncResult* result, + gpointer user_data) { + static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result); +} + +void ScreenCastPortal::RequestSession(GDBusProxy* proxy) { + proxy_ = proxy; + connection_ = g_dbus_proxy_get_connection(proxy_); + SetupSessionRequestHandlers( + "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_, + proxy_, cancellable_, portal_handle_, session_request_signal_id_, this); +} + +// static +void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + static_cast<ScreenCastPortal*>(user_data)->OnSessionRequestResult(proxy, + result); +} + +// static +void ScreenCastPortal::OnSessionRequestResponseSignal( + GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + that->RegisterSessionClosedSignalHandler( + OnSessionClosedSignal, parameters, that->connection_, + that->session_handle_, that->session_closed_signal_id_); + + // Do not continue if we don't get session_handle back. The call above will + // already notify the capturer there is a failure, but we would still continue + // to make following request and crash on that. + if (!that->session_handle_.empty()) { + that->SourcesRequest(); + } +} + +// static +void ScreenCastPortal::OnSessionClosedSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "Received closed signal from session."; + + that->notifier_->OnScreenCastSessionClosed(); + + // Unsubscribe from the signal and free the session handle to avoid calling + // Session::Close from the destructor since it's already closed + g_dbus_connection_signal_unsubscribe(that->connection_, + that->session_closed_signal_id_); +} + +void ScreenCastPortal::SourcesRequest() { + GVariantBuilder builder; + Scoped<char> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + // We want to record monitor content. + g_variant_builder_add( + &builder, "{sv}", "types", + g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_))); + // We don't want to allow selection of multiple sources. + g_variant_builder_add(&builder, "{sv}", "multiple", + g_variant_new_boolean(false)); + + Scoped<GVariant> cursorModesVariant( + g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes")); + if (cursorModesVariant.get()) { + uint32_t modes = 0; + g_variant_get(cursorModesVariant.get(), "u", &modes); + // Make request only if this mode is advertised by the portal + // implementation. + if (modes & static_cast<uint32_t>(cursor_mode_)) { + g_variant_builder_add( + &builder, "{sv}", "cursor_mode", + g_variant_new_uint32(static_cast<uint32_t>(cursor_mode_))); + } + } + + Scoped<GVariant> versionVariant( + g_dbus_proxy_get_cached_property(proxy_, "version")); + if (versionVariant.get()) { + uint32_t version = 0; + g_variant_get(versionVariant.get(), "u", &version); + // Make request only if xdg-desktop-portal has required API version + if (version >= 4) { + g_variant_builder_add( + &builder, "{sv}", "persist_mode", + g_variant_new_uint32(static_cast<uint32_t>(persist_mode_))); + if (!restore_token_.empty()) { + g_variant_builder_add(&builder, "{sv}", "restore_token", + g_variant_new_string(restore_token_.c_str())); + } + } + } + + variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_); + sources_request_signal_id_ = SetupRequestResponseSignal( + sources_handle_.c_str(), sources_request_response_signal_handler_, + user_data_, connection_); + + RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session."; + g_dbus_proxy_call( + proxy_, "SelectSources", + g_variant_new("(oa{sv})", session_handle_.c_str(), &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this); +} + +// static +void ScreenCastPortal::OnSourcesRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GVariant> variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message; + that->OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Sources requested from the screen cast session."; + + Scoped<char> handle; + g_variant_get_child(variant.get(), 0, "o", handle.receive()); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; + if (that->sources_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(that->connection_, + that->sources_request_signal_id_); + that->sources_request_signal_id_ = 0; + } + that->OnPortalDone(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Subscribed to sources signal."; +} + +// static +void ScreenCastPortal::OnSourcesRequestResponseSignal( + GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "Received sources signal from session."; + + uint32_t portal_response; + g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); + if (portal_response) { + RTC_LOG(LS_ERROR) + << "Failed to select sources for the screen cast session."; + that->OnPortalDone(RequestResponse::kError); + return; + } + + that->StartRequest(); +} + +void ScreenCastPortal::StartRequest() { + StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal, + OnStartRequested, proxy_, connection_, cancellable_, + start_request_signal_id_, start_handle_, this); +} + +// static +void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + static_cast<ScreenCastPortal*>(user_data)->OnStartRequestResult(proxy, + result); +} + +// static +void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "Start signal received."; + uint32_t portal_response; + Scoped<GVariant> response_data; + Scoped<GVariantIter> iter; + Scoped<char> restore_token; + g_variant_get(parameters, "(u@a{sv})", &portal_response, + response_data.receive()); + if (portal_response || !response_data) { + RTC_LOG(LS_ERROR) << "Failed to start the screen cast session."; + that->OnPortalDone(RequestResponseFromPortalResponse(portal_response)); + return; + } + + // Array of PipeWire streams. See + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + // documentation for <method name="Start">. + if (g_variant_lookup(response_data.get(), "streams", "a(ua{sv})", + iter.receive())) { + Scoped<GVariant> variant; + + while (g_variant_iter_next(iter.get(), "@(ua{sv})", variant.receive())) { + uint32_t stream_id; + uint32_t type; + Scoped<GVariant> options; + + g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive()); + RTC_DCHECK(options.get()); + + if (g_variant_lookup(options.get(), "source_type", "u", &type)) { + that->capture_source_type_ = + static_cast<ScreenCastPortal::CaptureSourceType>(type); + } + + that->pw_stream_node_id_ = stream_id; + + break; + } + } + + if (g_variant_lookup(response_data.get(), "restore_token", "s", + restore_token.receive())) { + that->restore_token_ = restore_token.get(); + } + + that->OpenPipeWireRemote(); +} + +uint32_t ScreenCastPortal::pipewire_stream_node_id() { + return pw_stream_node_id_; +} + +void ScreenCastPortal::SetPersistMode(ScreenCastPortal::PersistMode mode) { + persist_mode_ = mode; +} + +void ScreenCastPortal::SetRestoreToken(const std::string& token) { + restore_token_ = token; +} + +std::string ScreenCastPortal::RestoreToken() const { + return restore_token_; +} + +void ScreenCastPortal::OpenPipeWireRemote() { + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + RTC_LOG(LS_INFO) << "Opening the PipeWire remote."; + + g_dbus_proxy_call_with_unix_fd_list( + proxy_, "OpenPipeWireRemote", + g_variant_new("(oa{sv})", session_handle_.c_str(), &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_, + reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested), + this); +} + +// static +void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); + RTC_DCHECK(that); + + Scoped<GError> error; + Scoped<GUnixFDList> outlist; + Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish( + proxy, outlist.receive(), result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: " + << error->message; + that->OnPortalDone(RequestResponse::kError); + return; + } + + int32_t index; + g_variant_get(variant.get(), "(h)", &index); + + that->pw_fd_ = g_unix_fd_list_get(outlist.get(), index, error.receive()); + + if (that->pw_fd_ == -1) { + RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " + << error->message; + that->OnPortalDone(RequestResponse::kError); + return; + } + + that->OnPortalDone(RequestResponse::kSuccess); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h new file mode 100644 index 0000000000..7970710c41 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_portal.h @@ -0,0 +1,213 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_ + +#include <gio/gio.h> + +#include <string> + +#include "modules/desktop_capture/linux/wayland/portal_request_response.h" +#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" +#include "modules/desktop_capture/linux/wayland/xdg_session_details.h" + +namespace webrtc { + +class ScreenCastPortal : public xdg_portal::ScreenCapturePortalInterface { + public: + using ProxyRequestResponseHandler = void (*)(GObject* object, + GAsyncResult* result, + gpointer user_data); + + using SourcesRequestResponseSignalHandler = + void (*)(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + + // Values are set based on source type property in + // xdg-desktop-portal/screencast + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + enum class CaptureSourceType : uint32_t { + kScreen = 0b01, + kWindow = 0b10, + kAnyScreenContent = kScreen | kWindow + }; + + // Values are set based on cursor mode property in + // xdg-desktop-portal/screencast + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + enum class CursorMode : uint32_t { + // Mouse cursor will not be included in any form + kHidden = 0b01, + // Mouse cursor will be part of the screen content + kEmbedded = 0b10, + // Mouse cursor information will be send separately in form of metadata + kMetadata = 0b100 + }; + + // Values are set based on persist mode property in + // xdg-desktop-portal/screencast + // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml + enum class PersistMode : uint32_t { + // Do not allow to restore stream + kDoNotPersist = 0b00, + // The restore token is valid as long as the application is alive. It's + // stored in memory and revoked when the application closes its DBus + // connection + kTransient = 0b01, + // The restore token is stored in disk and is valid until the user manually + // revokes it + kPersistent = 0b10 + }; + + // Interface that must be implemented by the ScreenCastPortal consumers. + class PortalNotifier { + public: + virtual void OnScreenCastRequestResult(xdg_portal::RequestResponse result, + uint32_t stream_node_id, + int fd) = 0; + virtual void OnScreenCastSessionClosed() = 0; + + protected: + PortalNotifier() = default; + virtual ~PortalNotifier() = default; + }; + + explicit ScreenCastPortal(ScreenCastPortal::CaptureSourceType source_type, + PortalNotifier* notifier); + explicit ScreenCastPortal( + CaptureSourceType source_type, + PortalNotifier* notifier, + ProxyRequestResponseHandler proxy_request_response_handler, + SourcesRequestResponseSignalHandler + sources_request_response_signal_handler, + gpointer user_data); + ~ScreenCastPortal(); + + // Initialize ScreenCastPortal with series of DBus calls where we try to + // obtain all the required information, like PipeWire file descriptor and + // PipeWire stream node ID. + // + // The observer will return whether the communication with xdg-desktop-portal + // was successful and only then you will be able to get all the required + // information in order to continue working with PipeWire. + void Start() override; + xdg_portal::SessionDetails GetSessionDetails() override; + + // Method to notify the reason for failure of a portal request. + void OnPortalDone(xdg_portal::RequestResponse result) override; + + // Sends a create session request to the portal. + void RequestSession(GDBusProxy* proxy) override; + void Cleanup(); + + // Set of methods leveraged by remote desktop portal to setup a common session + // with screen cast portal. + void SetSessionDetails(const xdg_portal::SessionDetails& session_details); + uint32_t pipewire_stream_node_id(); + void SourcesRequest(); + void OpenPipeWireRemote(); + + // ScreenCast specific methods for stream restoration + void SetPersistMode(ScreenCastPortal::PersistMode mode); + void SetRestoreToken(const std::string& token); + std::string RestoreToken() const; + + private: + PortalNotifier* notifier_; + + // A PipeWire stream ID of stream we will be connecting to + uint32_t pw_stream_node_id_ = 0; + // A file descriptor of PipeWire socket + int pw_fd_ = -1; + // Restore token that can be used to restore previous session + std::string restore_token_; + + CaptureSourceType capture_source_type_ = + ScreenCastPortal::CaptureSourceType::kScreen; + + CursorMode cursor_mode_ = ScreenCastPortal::CursorMode::kMetadata; + + PersistMode persist_mode_ = ScreenCastPortal::PersistMode::kDoNotPersist; + + ProxyRequestResponseHandler proxy_request_response_handler_; + SourcesRequestResponseSignalHandler sources_request_response_signal_handler_; + gpointer user_data_; + + GDBusConnection* connection_ = nullptr; + GDBusProxy* proxy_ = nullptr; + GCancellable* cancellable_ = nullptr; + std::string portal_handle_; + std::string session_handle_; + std::string sources_handle_; + std::string start_handle_; + guint session_request_signal_id_ = 0; + guint sources_request_signal_id_ = 0; + guint start_request_signal_id_ = 0; + guint session_closed_signal_id_ = 0; + + void UnsubscribeSignalHandlers(); + static void OnProxyRequested(GObject* object, + GAsyncResult* result, + gpointer user_data); + static void OnSessionRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); + static void OnSessionRequestResponseSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + static void OnSessionClosedSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + static void OnSourcesRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); + static void OnSourcesRequestResponseSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + + void StartRequest(); + static void OnStartRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); + static void OnStartRequestResponseSignal(GDBusConnection* connection, + const char* sender_name, + const char* object_path, + const char* interface_name, + const char* signal_name, + GVariant* parameters, + gpointer user_data); + + static void OnOpenPipeWireRemoteRequested(GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc new file mode 100644 index 0000000000..dc0784791d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.cc @@ -0,0 +1,132 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h" + +#include <libdrm/drm_fourcc.h> +#include <pipewire/pipewire.h> +#include <spa/param/video/format-utils.h> + +#include <string> + +#include "rtc_base/string_to_number.h" + +#if !PW_CHECK_VERSION(0, 3, 29) +#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3) +#endif +#if !PW_CHECK_VERSION(0, 3, 33) +#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4) +#endif + +namespace webrtc { + +PipeWireThreadLoopLock::PipeWireThreadLoopLock(pw_thread_loop* loop) + : loop_(loop) { + pw_thread_loop_lock(loop_); +} + +PipeWireThreadLoopLock::~PipeWireThreadLoopLock() { + pw_thread_loop_unlock(loop_); +} + +PipeWireVersion PipeWireVersion::Parse(const absl::string_view& version) { + std::vector<absl::string_view> parsed_version = rtc::split(version, '.'); + + if (parsed_version.size() != 3) { + return {}; + } + + absl::optional<int> major = rtc::StringToNumber<int>(parsed_version.at(0)); + absl::optional<int> minor = rtc::StringToNumber<int>(parsed_version.at(1)); + absl::optional<int> micro = rtc::StringToNumber<int>(parsed_version.at(2)); + + // Return invalid version if we failed to parse it + if (!major || !minor || !micro) { + return {}; + } + + return {major.value(), minor.value(), micro.value()}; +} + +bool PipeWireVersion::operator>=(const PipeWireVersion& other) { + if (!major && !minor && !micro) { + return false; + } + + return std::tie(major, minor, micro) >= + std::tie(other.major, other.minor, other.micro); +} + +bool PipeWireVersion::operator<=(const PipeWireVersion& other) { + if (!major && !minor && !micro) { + return false; + } + + return std::tie(major, minor, micro) <= + std::tie(other.major, other.minor, other.micro); +} + +spa_pod* BuildFormat(spa_pod_builder* builder, + uint32_t format, + const std::vector<uint64_t>& modifiers, + const struct spa_rectangle* resolution) { + spa_pod_frame frames[2]; + spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1}; + spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; + + spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format, + SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, SPA_FORMAT_mediaType, + SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, + SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + + if (modifiers.size()) { + if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(builder, modifiers[0]); + } else { + spa_pod_builder_prop( + builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0); + + // modifiers from the array + bool first = true; + for (int64_t val : modifiers) { + spa_pod_builder_long(builder, val); + // Add the first modifier twice as the very first value is the default + // option + if (first) { + spa_pod_builder_long(builder, val); + first = false; + } + } + spa_pod_builder_pop(builder, &frames[1]); + } + } + + if (resolution) { + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, + SPA_POD_Rectangle(resolution), 0); + } else { + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds, + &pw_min_screen_bounds, + &pw_max_screen_bounds), + 0); + } + + return static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frames[0])); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h new file mode 100644 index 0000000000..70262c2e39 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/screencast_stream_utils.h @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "rtc_base/string_encode.h" + +struct pw_thread_loop; +struct spa_pod; +struct spa_pod_builder; +struct spa_rectangle; + +namespace webrtc { + +// Locks pw_thread_loop in the current scope +class PipeWireThreadLoopLock { + public: + explicit PipeWireThreadLoopLock(pw_thread_loop* loop); + ~PipeWireThreadLoopLock(); + + private: + pw_thread_loop* const loop_; +}; + +struct PipeWireVersion { + static PipeWireVersion Parse(const absl::string_view& version); + + // Returns whether current version is newer or same as required version + bool operator>=(const PipeWireVersion& other); + // Returns whether current version is older or same as required version + bool operator<=(const PipeWireVersion& other); + + int major = 0; + int minor = 0; + int micro = 0; +}; + +// Returns a spa_pod used to build PipeWire stream format using given +// arguments. Modifiers are optional value and when present they will be +// used with SPA_POD_PROP_FLAG_MANDATORY and SPA_POD_PROP_FLAG_DONT_FIXATE +// flags. +spa_pod* BuildFormat(spa_pod_builder* builder, + uint32_t format, + const std::vector<uint64_t>& modifiers, + const struct spa_rectangle* resolution); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc new file mode 100644 index 0000000000..5cbeaee9bf --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc @@ -0,0 +1,870 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" + +#include <fcntl.h> +#include <libdrm/drm_fourcc.h> +#include <pipewire/pipewire.h> +#include <spa/param/video/format-utils.h> +#include <sys/mman.h> + +#include <vector> + +#include "absl/memory/memory.h" +#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h" +#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" +#include "rtc_base/synchronization/mutex.h" + +#if defined(WEBRTC_DLOPEN_PIPEWIRE) +#include "modules/desktop_capture/linux/wayland/pipewire_stubs.h" +using modules_desktop_capture_linux_wayland::InitializeStubs; +using modules_desktop_capture_linux_wayland::kModuleDrm; +using modules_desktop_capture_linux_wayland::kModulePipewire; +using modules_desktop_capture_linux_wayland::StubPathMap; +#endif // defined(WEBRTC_DLOPEN_PIPEWIRE) + +namespace webrtc { + +const int kBytesPerPixel = 4; + +#if defined(WEBRTC_DLOPEN_PIPEWIRE) +const char kPipeWireLib[] = "libpipewire-0.3.so.0"; +const char kDrmLib[] = "libdrm.so.2"; +#endif + +constexpr int kCursorBpp = 4; +constexpr int CursorMetaSize(int w, int h) { + return (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + + w * h * kCursorBpp); +} + +constexpr PipeWireVersion kDmaBufMinVersion = {0, 3, 24}; +constexpr PipeWireVersion kDmaBufModifierMinVersion = {0, 3, 33}; +constexpr PipeWireVersion kDropSingleModifierMinVersion = {0, 3, 40}; + +class ScopedBuf { + public: + ScopedBuf() {} + ScopedBuf(uint8_t* map, int map_size, int fd) + : map_(map), map_size_(map_size), fd_(fd) {} + ~ScopedBuf() { + if (map_ != MAP_FAILED) { + munmap(map_, map_size_); + } + } + + explicit operator bool() { return map_ != MAP_FAILED; } + + void initialize(uint8_t* map, int map_size, int fd) { + map_ = map; + map_size_ = map_size; + fd_ = fd; + } + + uint8_t* get() { return map_; } + + protected: + uint8_t* map_ = static_cast<uint8_t*>(MAP_FAILED); + int map_size_; + int fd_; +}; + +class SharedScreenCastStreamPrivate { + public: + SharedScreenCastStreamPrivate(); + ~SharedScreenCastStreamPrivate(); + + bool StartScreenCastStream(uint32_t stream_node_id, + int fd, + uint32_t width = 0, + uint32_t height = 0); + void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height); + void StopScreenCastStream(); + std::unique_ptr<DesktopFrame> CaptureFrame(); + std::unique_ptr<MouseCursor> CaptureCursor(); + DesktopVector CaptureCursorPosition(); + + private: + uint32_t pw_stream_node_id_ = 0; + + DesktopSize stream_size_ = {}; + DesktopSize frame_size_; + + webrtc::Mutex queue_lock_; + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_ + RTC_GUARDED_BY(&queue_lock_); + std::unique_ptr<MouseCursor> mouse_cursor_; + DesktopVector mouse_cursor_position_ = DesktopVector(-1, -1); + + int64_t modifier_; + std::unique_ptr<EglDmaBuf> egl_dmabuf_; + // List of modifiers we query as supported by the graphics card/driver + std::vector<uint64_t> modifiers_; + + // PipeWire types + struct pw_context* pw_context_ = nullptr; + struct pw_core* pw_core_ = nullptr; + struct pw_stream* pw_stream_ = nullptr; + struct pw_thread_loop* pw_main_loop_ = nullptr; + struct spa_source* renegotiate_ = nullptr; + + spa_hook spa_core_listener_; + spa_hook spa_stream_listener_; + + // A number used to verify all previous methods and the resulting + // events have been handled. + int server_version_sync_ = 0; + // Version of the running PipeWire server we communicate with + PipeWireVersion pw_server_version_; + // Version of the library used to run our code + PipeWireVersion pw_client_version_; + + // Resolution parameters. + uint32_t width_ = 0; + uint32_t height_ = 0; + webrtc::Mutex resolution_lock_; + // Resolution changes are processed during buffer processing. + bool pending_resolution_change_ RTC_GUARDED_BY(&resolution_lock_) = false; + + // event handlers + pw_core_events pw_core_events_ = {}; + pw_stream_events pw_stream_events_ = {}; + + struct spa_video_info_raw spa_video_format_; + + void ProcessBuffer(pw_buffer* buffer); + void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size); + + // PipeWire callbacks + static void OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message); + static void OnCoreDone(void* user_data, uint32_t id, int seq); + static void OnCoreInfo(void* user_data, const pw_core_info* info); + static void OnStreamParamChanged(void* data, + uint32_t id, + const struct spa_pod* format); + static void OnStreamStateChanged(void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message); + static void OnStreamProcess(void* data); + // This will be invoked in case we fail to process DMA-BUF PW buffer using + // negotiated stream parameters (modifier). We will drop the modifier we + // failed to use and try to use a different one or fallback to shared memory + // buffers. + static void OnRenegotiateFormat(void* data, uint64_t); +}; + +void SharedScreenCastStreamPrivate::OnCoreError(void* data, + uint32_t id, + int seq, + int res, + const char* message) { + SharedScreenCastStreamPrivate* that = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(that); + + RTC_LOG(LS_ERROR) << "PipeWire remote error: " << message; +} + +void SharedScreenCastStreamPrivate::OnCoreInfo(void* data, + const pw_core_info* info) { + SharedScreenCastStreamPrivate* stream = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(stream); + + stream->pw_server_version_ = PipeWireVersion::Parse(info->version); +} + +void SharedScreenCastStreamPrivate::OnCoreDone(void* data, + uint32_t id, + int seq) { + const SharedScreenCastStreamPrivate* stream = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(stream); + + if (id == PW_ID_CORE && stream->server_version_sync_ == seq) { + pw_thread_loop_signal(stream->pw_main_loop_, false); + } +} + +// static +void SharedScreenCastStreamPrivate::OnStreamStateChanged( + void* data, + pw_stream_state old_state, + pw_stream_state state, + const char* error_message) { + SharedScreenCastStreamPrivate* that = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(that); + + switch (state) { + case PW_STREAM_STATE_ERROR: + RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message; + break; + case PW_STREAM_STATE_PAUSED: + case PW_STREAM_STATE_STREAMING: + case PW_STREAM_STATE_UNCONNECTED: + case PW_STREAM_STATE_CONNECTING: + break; + } +} + +// static +void SharedScreenCastStreamPrivate::OnStreamParamChanged( + void* data, + uint32_t id, + const struct spa_pod* format) { + SharedScreenCastStreamPrivate* that = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(that); + + RTC_LOG(LS_INFO) << "PipeWire stream format changed."; + if (!format || id != SPA_PARAM_Format) { + return; + } + + spa_format_video_raw_parse(format, &that->spa_video_format_); + + auto width = that->spa_video_format_.size.width; + auto height = that->spa_video_format_.size.height; + auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4); + auto size = height * stride; + + that->stream_size_ = DesktopSize(width, height); + + uint8_t buffer[1024] = {}; + auto builder = spa_pod_builder{buffer, sizeof(buffer)}; + + // Setup buffers and meta header for new format. + + // When SPA_FORMAT_VIDEO_modifier is present we can use DMA-BUFs as + // the server announces support for it. + // See https://github.com/PipeWire/pipewire/blob/master/doc/dma-buf.dox + const bool has_modifier = + spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier); + that->modifier_ = + has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID; + std::vector<const spa_pod*> params; + const int buffer_types = + has_modifier || (that->pw_server_version_ >= kDmaBufMinVersion) + ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | + (1 << SPA_DATA_MemPtr) + : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); + + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, + SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers, + SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int(buffer_types)))); + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_header))))); + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_region))))); + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, + SPA_POD_CHOICE_RANGE_Int(CursorMetaSize(64, 64), CursorMetaSize(1, 1), + CursorMetaSize(384, 384))))); + params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object( + &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, + SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * 16, + sizeof(struct spa_meta_region) * 1, + sizeof(struct spa_meta_region) * 16)))); + + pw_stream_update_params(that->pw_stream_, params.data(), params.size()); +} + +// static +void SharedScreenCastStreamPrivate::OnStreamProcess(void* data) { + SharedScreenCastStreamPrivate* that = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(that); + + struct pw_buffer* next_buffer; + struct pw_buffer* buffer = nullptr; + + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); + while (next_buffer) { + buffer = next_buffer; + next_buffer = pw_stream_dequeue_buffer(that->pw_stream_); + + if (next_buffer) { + pw_stream_queue_buffer(that->pw_stream_, buffer); + } + } + + if (!buffer) { + return; + } + + that->ProcessBuffer(buffer); + + pw_stream_queue_buffer(that->pw_stream_, buffer); +} + +void SharedScreenCastStreamPrivate::OnRenegotiateFormat(void* data, uint64_t) { + SharedScreenCastStreamPrivate* that = + static_cast<SharedScreenCastStreamPrivate*>(data); + RTC_DCHECK(that); + + { + PipeWireThreadLoopLock thread_loop_lock(that->pw_main_loop_); + + uint8_t buffer[2048] = {}; + + spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; + + std::vector<const spa_pod*> params; + struct spa_rectangle resolution = + SPA_RECTANGLE(that->width_, that->height_); + + webrtc::MutexLock lock(&that->resolution_lock_); + for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { + if (!that->modifiers_.empty()) { + params.push_back(BuildFormat( + &builder, format, that->modifiers_, + that->pending_resolution_change_ ? &resolution : nullptr)); + } + params.push_back(BuildFormat( + &builder, format, /*modifiers=*/{}, + that->pending_resolution_change_ ? &resolution : nullptr)); + } + + pw_stream_update_params(that->pw_stream_, params.data(), params.size()); + that->pending_resolution_change_ = false; + } +} + +SharedScreenCastStreamPrivate::SharedScreenCastStreamPrivate() {} + +SharedScreenCastStreamPrivate::~SharedScreenCastStreamPrivate() { + if (pw_main_loop_) { + pw_thread_loop_stop(pw_main_loop_); + } + + if (pw_stream_) { + pw_stream_destroy(pw_stream_); + } + + if (pw_core_) { + pw_core_disconnect(pw_core_); + } + + if (pw_context_) { + pw_context_destroy(pw_context_); + } + + if (pw_main_loop_) { + pw_thread_loop_destroy(pw_main_loop_); + } +} + +RTC_NO_SANITIZE("cfi-icall") +bool SharedScreenCastStreamPrivate::StartScreenCastStream( + uint32_t stream_node_id, + int fd, + uint32_t width, + uint32_t height) { + width_ = width; + height_ = height; +#if defined(WEBRTC_DLOPEN_PIPEWIRE) + StubPathMap paths; + + // Check if the PipeWire and DRM libraries are available. + paths[kModulePipewire].push_back(kPipeWireLib); + paths[kModuleDrm].push_back(kDrmLib); + + if (!InitializeStubs(paths)) { + RTC_LOG(LS_ERROR) + << "One of following libraries is missing on your system:\n" + << " - PipeWire (" << kPipeWireLib << ")\n" + << " - drm (" << kDrmLib << ")"; + return false; + } +#endif // defined(WEBRTC_DLOPEN_PIPEWIRE) + egl_dmabuf_ = std::make_unique<EglDmaBuf>(); + + pw_stream_node_id_ = stream_node_id; + + pw_init(/*argc=*/nullptr, /*argc=*/nullptr); + + pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr); + + pw_context_ = + pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0); + if (!pw_context_) { + RTC_LOG(LS_ERROR) << "Failed to create PipeWire context"; + return false; + } + + if (pw_thread_loop_start(pw_main_loop_) < 0) { + RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; + return false; + } + + pw_client_version_ = PipeWireVersion::Parse(pw_get_library_version()); + + // Initialize event handlers, remote end and stream-related. + pw_core_events_.version = PW_VERSION_CORE_EVENTS; + pw_core_events_.info = &OnCoreInfo; + pw_core_events_.done = &OnCoreDone; + pw_core_events_.error = &OnCoreError; + + pw_stream_events_.version = PW_VERSION_STREAM_EVENTS; + pw_stream_events_.state_changed = &OnStreamStateChanged; + pw_stream_events_.param_changed = &OnStreamParamChanged; + pw_stream_events_.process = &OnStreamProcess; + + { + PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_); + + if (fd >= 0) { + pw_core_ = pw_context_connect_fd( + pw_context_, fcntl(fd, F_DUPFD_CLOEXEC), nullptr, 0); + } else { + pw_core_ = pw_context_connect(pw_context_, nullptr, 0); + } + + if (!pw_core_) { + RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context"; + return false; + } + + pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this); + + // Add an event that can be later invoked by pw_loop_signal_event() + renegotiate_ = pw_loop_add_event(pw_thread_loop_get_loop(pw_main_loop_), + OnRenegotiateFormat, this); + + server_version_sync_ = + pw_core_sync(pw_core_, PW_ID_CORE, server_version_sync_); + + pw_thread_loop_wait(pw_main_loop_); + + pw_properties* reuseProps = + pw_properties_new_string("pipewire.client.reuse=1"); + pw_stream_ = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps); + + if (!pw_stream_) { + RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream"; + return false; + } + + pw_stream_add_listener(pw_stream_, &spa_stream_listener_, + &pw_stream_events_, this); + uint8_t buffer[2048] = {}; + + spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; + + std::vector<const spa_pod*> params; + const bool has_required_pw_client_version = + pw_client_version_ >= kDmaBufModifierMinVersion; + const bool has_required_pw_server_version = + pw_server_version_ >= kDmaBufModifierMinVersion; + struct spa_rectangle resolution; + bool set_resolution = false; + if (width && height) { + resolution = SPA_RECTANGLE(width, height); + set_resolution = true; + } + for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { + // Modifiers can be used with PipeWire >= 0.3.33 + if (has_required_pw_client_version && has_required_pw_server_version) { + modifiers_ = egl_dmabuf_->QueryDmaBufModifiers(format); + + if (!modifiers_.empty()) { + params.push_back(BuildFormat(&builder, format, modifiers_, + set_resolution ? &resolution : nullptr)); + } + } + + params.push_back(BuildFormat(&builder, format, /*modifiers=*/{}, + set_resolution ? &resolution : nullptr)); + } + + if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, pw_stream_node_id_, + PW_STREAM_FLAG_AUTOCONNECT, params.data(), + params.size()) != 0) { + RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; + return false; + } + + RTC_LOG(LS_INFO) << "PipeWire remote opened."; + } + return true; +} + +RTC_NO_SANITIZE("cfi-icall") +void SharedScreenCastStreamPrivate::UpdateScreenCastStreamResolution( + uint32_t width, + uint32_t height) { + if (!width || !height) { + RTC_LOG(LS_WARNING) << "Bad resolution specified: " << width << "x" + << height; + return; + } + if (!pw_main_loop_) { + RTC_LOG(LS_WARNING) << "No main pipewire loop, ignoring resolution change"; + return; + } + if (!renegotiate_) { + RTC_LOG(LS_WARNING) << "Can not renegotiate stream params, ignoring " + << "resolution change"; + return; + } + if (width_ != width || height_ != height) { + width_ = width; + height_ = height; + { + webrtc::MutexLock lock(&resolution_lock_); + pending_resolution_change_ = true; + } + pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), renegotiate_); + } +} + +void SharedScreenCastStreamPrivate::StopScreenCastStream() { + if (pw_stream_) { + pw_stream_disconnect(pw_stream_); + } +} + +std::unique_ptr<DesktopFrame> SharedScreenCastStreamPrivate::CaptureFrame() { + webrtc::MutexLock lock(&queue_lock_); + + if (!queue_.current_frame()) { + return std::unique_ptr<DesktopFrame>{}; + } + + std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share(); + return std::move(frame); +} + +std::unique_ptr<MouseCursor> SharedScreenCastStreamPrivate::CaptureCursor() { + if (!mouse_cursor_) { + return nullptr; + } + + return std::move(mouse_cursor_); +} + +DesktopVector SharedScreenCastStreamPrivate::CaptureCursorPosition() { + return mouse_cursor_position_; +} + +RTC_NO_SANITIZE("cfi-icall") +void SharedScreenCastStreamPrivate::ProcessBuffer(pw_buffer* buffer) { + spa_buffer* spa_buffer = buffer->buffer; + ScopedBuf map; + std::unique_ptr<uint8_t[]> src_unique_ptr; + uint8_t* src = nullptr; + + // Try to update the mouse cursor first, because it can be the only + // information carried by the buffer + { + const struct spa_meta_cursor* cursor = + static_cast<struct spa_meta_cursor*>(spa_buffer_find_meta_data( + spa_buffer, SPA_META_Cursor, sizeof(*cursor))); + if (cursor && spa_meta_cursor_is_valid(cursor)) { + struct spa_meta_bitmap* bitmap = nullptr; + + if (cursor->bitmap_offset) + bitmap = + SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap); + + if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0) { + const uint8_t* bitmap_data = + SPA_MEMBER(bitmap, bitmap->offset, uint8_t); + BasicDesktopFrame* mouse_frame = new BasicDesktopFrame( + DesktopSize(bitmap->size.width, bitmap->size.height)); + mouse_frame->CopyPixelsFrom( + bitmap_data, bitmap->stride, + DesktopRect::MakeWH(bitmap->size.width, bitmap->size.height)); + mouse_cursor_ = std::make_unique<MouseCursor>( + mouse_frame, DesktopVector(cursor->hotspot.x, cursor->hotspot.y)); + } + mouse_cursor_position_.set(cursor->position.x, cursor->position.y); + } + } + + if (spa_buffer->datas[0].chunk->size == 0) { + return; + } + + if (spa_buffer->datas[0].type == SPA_DATA_MemFd) { + map.initialize( + static_cast<uint8_t*>( + mmap(nullptr, + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)), + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + spa_buffer->datas[0].fd); + + if (!map) { + RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " + << std::strerror(errno); + return; + } + + src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t); + } else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) { + const uint n_planes = spa_buffer->n_datas; + + if (!n_planes) { + return; + } + + std::vector<EglDmaBuf::PlaneData> plane_datas; + for (uint32_t i = 0; i < n_planes; ++i) { + EglDmaBuf::PlaneData data = { + static_cast<int32_t>(spa_buffer->datas[i].fd), + static_cast<uint32_t>(spa_buffer->datas[i].chunk->stride), + static_cast<uint32_t>(spa_buffer->datas[i].chunk->offset)}; + plane_datas.push_back(data); + } + + // When importing DMA-BUFs, we use the stride (number of bytes from one row + // of pixels in the buffer) provided by PipeWire. The stride from PipeWire + // is given by the graphics driver and some drivers might add some + // additional padding for memory layout optimizations so not everytime the + // stride is equal to BYTES_PER_PIXEL x WIDTH. This is fine, because during + // the import we will use OpenGL and same graphics driver so it will be able + // to work with the stride it provided, but later on when we work with + // images we get from DMA-BUFs we will need to update the stride to be equal + // to BYTES_PER_PIXEL x WIDTH as that's the size of the DesktopFrame we + // allocate for each captured frame. + src_unique_ptr = egl_dmabuf_->ImageFromDmaBuf( + stream_size_, spa_video_format_.format, plane_datas, modifier_); + if (src_unique_ptr) { + src = src_unique_ptr.get(); + } else { + RTC_LOG(LS_ERROR) << "Dropping DMA-BUF modifier: " << modifier_ + << " and trying to renegotiate stream parameters"; + + if (pw_server_version_ >= kDropSingleModifierMinVersion) { + modifiers_.erase( + std::remove(modifiers_.begin(), modifiers_.end(), modifier_), + modifiers_.end()); + } else { + modifiers_.clear(); + } + + pw_loop_signal_event(pw_thread_loop_get_loop(pw_main_loop_), + renegotiate_); + return; + } + } else if (spa_buffer->datas[0].type == SPA_DATA_MemPtr) { + src = static_cast<uint8_t*>(spa_buffer->datas[0].data); + } + + if (!src) { + return; + } + + // Use SPA_META_VideoCrop metadata to get the frame size. KDE and GNOME do + // handle screen/window sharing differently. KDE/KWin doesn't use + // SPA_META_VideoCrop metadata and when sharing a window, it always sets + // stream size to size of the window. With that we just allocate the + // DesktopFrame using the size of the stream itself. GNOME/Mutter + // always sets stream size to the size of the whole screen, even when sharing + // a window. To get the real window size we have to use SPA_META_VideoCrop + // metadata. This gives us the size we need in order to allocate the + // DesktopFrame. + + struct spa_meta_region* videocrop_metadata = + static_cast<struct spa_meta_region*>(spa_buffer_find_meta_data( + spa_buffer, SPA_META_VideoCrop, sizeof(*videocrop_metadata))); + + // Video size from metadata is bigger than an actual video stream size. + // The metadata are wrong or we should up-scale the video...in both cases + // just quit now. + if (videocrop_metadata && + (videocrop_metadata->region.size.width > + static_cast<uint32_t>(stream_size_.width()) || + videocrop_metadata->region.size.height > + static_cast<uint32_t>(stream_size_.height()))) { + RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!"; + return; + } + + // Use SPA_META_VideoCrop metadata to get the DesktopFrame size in case + // a windows is shared and it represents just a small portion of the + // stream itself. This will be for example used in case of GNOME (Mutter) + // where the stream will have the size of the screen itself, but we care + // only about smaller portion representing the window inside. + bool videocrop_metadata_use = false; + const struct spa_rectangle* videocrop_metadata_size = + videocrop_metadata ? &videocrop_metadata->region.size : nullptr; + + if (videocrop_metadata_size && videocrop_metadata_size->width != 0 && + videocrop_metadata_size->height != 0 && + (static_cast<int>(videocrop_metadata_size->width) < + stream_size_.width() || + static_cast<int>(videocrop_metadata_size->height) < + stream_size_.height())) { + videocrop_metadata_use = true; + } + + if (videocrop_metadata_use) { + frame_size_ = DesktopSize(videocrop_metadata_size->width, + videocrop_metadata_size->height); + } else { + frame_size_ = stream_size_; + } + + // Get the position of the video crop within the stream. Just double-check + // that the position doesn't exceed the size of the stream itself. NOTE: + // Currently it looks there is no implementation using this. + uint32_t y_offset = + videocrop_metadata_use && + (videocrop_metadata->region.position.y + frame_size_.height() <= + stream_size_.height()) + ? videocrop_metadata->region.position.y + : 0; + uint32_t x_offset = + videocrop_metadata_use && + (videocrop_metadata->region.position.x + frame_size_.width() <= + stream_size_.width()) + ? videocrop_metadata->region.position.x + : 0; + + const uint32_t stream_stride = kBytesPerPixel * stream_size_.width(); + uint32_t buffer_stride = spa_buffer->datas[0].chunk->stride; + uint32_t src_stride = buffer_stride; + + if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf && + buffer_stride > stream_stride) { + // When DMA-BUFs are used, sometimes spa_buffer->stride we get might + // contain additional padding, but after we import the buffer, the stride + // we used is no longer relevant and we should just calculate it based on + // the stream width. For more context see https://crbug.com/1333304. + src_stride = stream_stride; + } + + uint8_t* updated_src = + src + (src_stride * y_offset) + (kBytesPerPixel * x_offset); + + webrtc::MutexLock lock(&queue_lock_); + + // Move to the next frame if the current one is being used and shared + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_LOG(LS_WARNING) + << "Failed to process PipeWire buffer: no available frame"; + return; + } + } + + if (!queue_.current_frame() || + !queue_.current_frame()->size().equals(frame_size_)) { + std::unique_ptr<DesktopFrame> frame(new BasicDesktopFrame( + DesktopSize(frame_size_.width(), frame_size_.height()))); + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame))); + } + + queue_.current_frame()->CopyPixelsFrom( + updated_src, (src_stride - (kBytesPerPixel * x_offset)), + DesktopRect::MakeWH(frame_size_.width(), frame_size_.height())); + + if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx || + spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) { + uint8_t* tmp_src = queue_.current_frame()->data(); + for (int i = 0; i < frame_size_.height(); ++i) { + // If both sides decided to go with the RGBx format we need to convert + // it to BGRx to match color format expected by WebRTC. + ConvertRGBxToBGRx(tmp_src, queue_.current_frame()->stride()); + tmp_src += queue_.current_frame()->stride(); + } + } + + queue_.current_frame()->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(queue_.current_frame()->size())); +} + +void SharedScreenCastStreamPrivate::ConvertRGBxToBGRx(uint8_t* frame, + uint32_t size) { + for (uint32_t i = 0; i < size; i += 4) { + uint8_t tempR = frame[i]; + uint8_t tempB = frame[i + 2]; + frame[i] = tempB; + frame[i + 2] = tempR; + } +} + +SharedScreenCastStream::SharedScreenCastStream() + : private_(std::make_unique<SharedScreenCastStreamPrivate>()) {} + +SharedScreenCastStream::~SharedScreenCastStream() {} + +rtc::scoped_refptr<SharedScreenCastStream> +SharedScreenCastStream::CreateDefault() { + // Explicit new, to access non-public constructor. + return rtc::scoped_refptr(new SharedScreenCastStream()); +} + +bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id) { + return private_->StartScreenCastStream(stream_node_id, -1); +} + +bool SharedScreenCastStream::StartScreenCastStream(uint32_t stream_node_id, + int fd, + uint32_t width, + uint32_t height) { + return private_->StartScreenCastStream(stream_node_id, fd, width, height); +} + +void SharedScreenCastStream::UpdateScreenCastStreamResolution(uint32_t width, + uint32_t height) { + private_->UpdateScreenCastStreamResolution(width, height); +} + +void SharedScreenCastStream::StopScreenCastStream() { + private_->StopScreenCastStream(); +} + +std::unique_ptr<DesktopFrame> SharedScreenCastStream::CaptureFrame() { + return private_->CaptureFrame(); +} + +std::unique_ptr<MouseCursor> SharedScreenCastStream::CaptureCursor() { + return private_->CaptureCursor(); +} + +absl::optional<DesktopVector> SharedScreenCastStream::CaptureCursorPosition() { + DesktopVector position = private_->CaptureCursorPosition(); + + // Consider only (x >= 0 and y >= 0) a valid position + if (position.x() < 0 || position.y() < 0) { + return absl::nullopt; + } + + return position; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h new file mode 100644 index 0000000000..66a3f45bdb --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/shared_screencast_stream.h @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_ + +#include <memory> + +#include "absl/types/optional.h" +#include "api/ref_counted_base.h" +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class SharedScreenCastStreamPrivate; + +class RTC_EXPORT SharedScreenCastStream + : public rtc::RefCountedNonVirtual<SharedScreenCastStream> { + public: + static rtc::scoped_refptr<SharedScreenCastStream> CreateDefault(); + + bool StartScreenCastStream(uint32_t stream_node_id); + bool StartScreenCastStream(uint32_t stream_node_id, + int fd, + uint32_t width = 0, + uint32_t height = 0); + void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height); + void StopScreenCastStream(); + + // Below functions return the most recent information we get from a + // PipeWire buffer on each Process() callback. This assumes that we + // managed to successfuly connect to a PipeWire stream provided by the + // compositor (based on stream parameters). The cursor data are obtained + // from spa_meta_cursor stream metadata and therefore the cursor is not + // part of actual screen/window frame. + + // Returns the most recent screen/window frame we obtained from PipeWire + // buffer. Will return an empty frame in case we didn't manage to get a frame + // from PipeWire buffer. + std::unique_ptr<DesktopFrame> CaptureFrame(); + + // Returns the most recent mouse cursor image. Will return an nullptr cursor + // in case we didn't manage to get a cursor from PipeWire buffer. NOTE: the + // cursor image might not be updated on every cursor location change, but + // actually only when its shape changes. + std::unique_ptr<MouseCursor> CaptureCursor(); + + // Returns the most recent mouse cursor position. Will not return a value in + // case we didn't manage to get it from PipeWire buffer. + absl::optional<DesktopVector> CaptureCursorPosition(); + + ~SharedScreenCastStream(); + + protected: + SharedScreenCastStream(); + + private: + SharedScreenCastStream(const SharedScreenCastStream&) = delete; + SharedScreenCastStream& operator=(const SharedScreenCastStream&) = delete; + + std::unique_ptr<SharedScreenCastStreamPrivate> private_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc new file mode 100644 index 0000000000..75dbf2bdf3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc @@ -0,0 +1,195 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" + +#include <string> + +#include "absl/strings/string_view.h" +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace xdg_portal { + +std::string RequestResponseToString(RequestResponse request) { + switch (request) { + case RequestResponse::kUnknown: + return "kUnknown"; + case RequestResponse::kSuccess: + return "kSuccess"; + case RequestResponse::kUserCancelled: + return "kUserCancelled"; + case RequestResponse::kError: + return "kError"; + default: + return "Uknown"; + } +} + +RequestResponse RequestResponseFromPortalResponse(uint32_t portal_response) { + // See: + // https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-signal-org-freedesktop-portal-Request.Response + switch (portal_response) { + case 0: + return RequestResponse::kSuccess; + case 1: + return RequestResponse::kUserCancelled; + case 2: + return RequestResponse::kError; + default: + return RequestResponse::kUnknown; + } +} + +std::string PrepareSignalHandle(absl::string_view token, + GDBusConnection* connection) { + Scoped<char> sender( + g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); + for (int i = 0; sender.get()[i]; ++i) { + if (sender.get()[i] == '.') { + sender.get()[i] = '_'; + } + } + const char* handle = + g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), "/", + std::string(token).c_str(), /*end of varargs*/ nullptr); + return handle; +} + +uint32_t SetupRequestResponseSignal(absl::string_view object_path, + const GDBusSignalCallback callback, + gpointer user_data, + GDBusConnection* connection) { + return g_dbus_connection_signal_subscribe( + connection, kDesktopBusName, kRequestInterfaceName, "Response", + std::string(object_path).c_str(), /*arg0=*/nullptr, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, user_data, + /*user_data_free_func=*/nullptr); +} + +void RequestSessionProxy(absl::string_view interface_name, + const ProxyRequestCallback proxy_request_callback, + GCancellable* cancellable, + gpointer user_data) { + g_dbus_proxy_new_for_bus( + G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr, + kDesktopBusName, kDesktopObjectPath, std::string(interface_name).c_str(), + cancellable, + reinterpret_cast<GAsyncReadyCallback>(proxy_request_callback), user_data); +} + +void SetupSessionRequestHandlers( + absl::string_view portal_prefix, + const SessionRequestCallback session_request_callback, + const SessionRequestResponseSignalHandler request_response_signale_handler, + GDBusConnection* connection, + GDBusProxy* proxy, + GCancellable* cancellable, + std::string& portal_handle, + guint& session_request_signal_id, + gpointer user_data) { + GVariantBuilder builder; + Scoped<char> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = + g_strdup_printf("%.*s_session%d", static_cast<int>(portal_prefix.size()), + portal_prefix.data(), g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "session_handle_token", + g_variant_new_string(variant_string.get())); + + variant_string = + g_strdup_printf("%.*s_%d", static_cast<int>(portal_prefix.size()), + portal_prefix.data(), g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + portal_handle = PrepareSignalHandle(variant_string.get(), connection); + session_request_signal_id = SetupRequestResponseSignal( + portal_handle.c_str(), request_response_signale_handler, user_data, + connection); + + RTC_LOG(LS_INFO) << "Desktop session requested."; + g_dbus_proxy_call( + proxy, "CreateSession", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable, + reinterpret_cast<GAsyncReadyCallback>(session_request_callback), + user_data); +} + +void StartSessionRequest( + absl::string_view prefix, + absl::string_view session_handle, + const StartRequestResponseSignalHandler signal_handler, + const SessionStartRequestedHandler session_started_handler, + GDBusProxy* proxy, + GDBusConnection* connection, + GCancellable* cancellable, + guint& start_request_signal_id, + std::string& start_handle, + gpointer user_data) { + GVariantBuilder builder; + Scoped<char> variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = + g_strdup_printf("%.*s%d", static_cast<int>(prefix.size()), prefix.data(), + g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + start_handle = PrepareSignalHandle(variant_string.get(), connection); + start_request_signal_id = SetupRequestResponseSignal( + start_handle.c_str(), signal_handler, user_data, connection); + + // "Identifier for the application window", this is Wayland, so not "x11:...". + const char parent_window[] = ""; + + RTC_LOG(LS_INFO) << "Starting the portal session."; + g_dbus_proxy_call( + proxy, "Start", + g_variant_new("(osa{sv})", std::string(session_handle).c_str(), + parent_window, &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable, + reinterpret_cast<GAsyncReadyCallback>(session_started_handler), + user_data); +} + +void TearDownSession(absl::string_view session_handle, + GDBusProxy* proxy, + GCancellable* cancellable, + GDBusConnection* connection) { + if (!session_handle.empty()) { + Scoped<GDBusMessage> message(g_dbus_message_new_method_call( + kDesktopBusName, std::string(session_handle).c_str(), + kSessionInterfaceName, "Close")); + if (message.get()) { + Scoped<GError> error; + g_dbus_connection_send_message(connection, message.get(), + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + /*out_serial=*/nullptr, error.receive()); + if (error.get()) { + RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message; + } + } + } + + if (cancellable) { + g_cancellable_cancel(cancellable); + g_object_unref(cancellable); + } + + if (proxy) { + g_object_unref(proxy); + } +} + +} // namespace xdg_portal +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h new file mode 100644 index 0000000000..f6ac92b5d1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_ + +#include <gio/gio.h> +#include <stdint.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/desktop_capture/linux/wayland/portal_request_response.h" +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "modules/desktop_capture/linux/wayland/xdg_session_details.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace xdg_portal { + +constexpr char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; +constexpr char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; +constexpr char kDesktopRequestObjectPath[] = + "/org/freedesktop/portal/desktop/request"; +constexpr char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; +constexpr char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; +constexpr char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; + +using ProxyRequestCallback = void (*)(GObject*, GAsyncResult*, gpointer); +using SessionRequestCallback = void (*)(GDBusProxy*, GAsyncResult*, gpointer); +using SessionRequestResponseSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); +using StartRequestResponseSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); +using SessionStartRequestedHandler = void (*)(GDBusProxy*, + GAsyncResult*, + gpointer); + +std::string RequestResponseToString(RequestResponse request); + +RequestResponse RequestResponseFromPortalResponse(uint32_t portal_response); + +// Returns a string path for signal handle based on the provided connection and +// token. +std::string PrepareSignalHandle(absl::string_view token, + GDBusConnection* connection); + +// Sets up the callback to execute when a response signal is received for the +// given object. +uint32_t SetupRequestResponseSignal(absl::string_view object_path, + const GDBusSignalCallback callback, + gpointer user_data, + GDBusConnection* connection); + +void RequestSessionProxy(absl::string_view interface_name, + const ProxyRequestCallback proxy_request_callback, + GCancellable* cancellable, + gpointer user_data); + +void SetupSessionRequestHandlers( + absl::string_view portal_prefix, + const SessionRequestCallback session_request_callback, + const SessionRequestResponseSignalHandler request_response_signale_handler, + GDBusConnection* connection, + GDBusProxy* proxy, + GCancellable* cancellable, + std::string& portal_handle, + guint& session_request_signal_id, + gpointer user_data); + +void StartSessionRequest( + absl::string_view prefix, + absl::string_view session_handle, + const StartRequestResponseSignalHandler signal_handler, + const SessionStartRequestedHandler session_started_handler, + GDBusProxy* proxy, + GDBusConnection* connection, + GCancellable* cancellable, + guint& start_request_signal_id, + std::string& start_handle, + gpointer user_data); + +// Tears down the portal session and cleans up related objects. +void TearDownSession(absl::string_view session_handle, + GDBusProxy* proxy, + GCancellable* cancellable, + GDBusConnection* connection); + +} // namespace xdg_portal +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h new file mode 100644 index 0000000000..b70ac4aa59 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/wayland/xdg_session_details.h @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_ + +#include <gio/gio.h> + +#include <string> + +namespace webrtc { +namespace xdg_portal { + +// Details of the session associated with XDG desktop portal session. Portal API +// calls can be invoked by utilizing the information here. +struct SessionDetails { + GDBusProxy* proxy = nullptr; + GCancellable* cancellable = nullptr; + std::string session_handle; + uint32_t pipewire_stream_node_id = 0; +}; + +} // namespace xdg_portal +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc new file mode 100644 index 0000000000..12f554df79 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h" + +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/xfixeswire.h> +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/linux/x11/x_error_trap.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace { + +// WindowCapturer returns window IDs of X11 windows with WM_STATE attribute. +// These windows may not be immediate children of the root window, because +// window managers may re-parent them to add decorations. However, +// XQueryPointer() expects to be passed children of the root. This function +// searches up the list of the windows to find the root child that corresponds +// to `window`. +Window GetTopLevelWindow(Display* display, Window window) { + webrtc::XErrorTrap error_trap(display); + while (true) { + // If the window is in WithdrawnState then look at all of its children. + ::Window root, parent; + ::Window* children; + unsigned int num_children; + if (!XQueryTree(display, window, &root, &parent, &children, + &num_children)) { + RTC_LOG(LS_ERROR) << "Failed to query for child windows although window" + "does not have a valid WM_STATE."; + return None; + } + if (children) + XFree(children); + + if (parent == root) + break; + + window = parent; + } + + return window; +} + +} // namespace + +namespace webrtc { + +MouseCursorMonitorX11::MouseCursorMonitorX11( + const DesktopCaptureOptions& options, + Window window) + : x_display_(options.x_display()), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + window_(window), + have_xfixes_(false), + xfixes_event_base_(-1), + xfixes_error_base_(-1) { + // Set a default initial cursor shape in case XFixes is not present. + const int kSize = 5; + std::unique_ptr<DesktopFrame> default_cursor( + new BasicDesktopFrame(DesktopSize(kSize, kSize))); + const uint8_t pixels[kSize * kSize] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t* ptr = default_cursor->data(); + for (int y = 0; y < kSize; ++y) { + for (int x = 0; x < kSize; ++x) { + *ptr++ = pixels[kSize * y + x]; + *ptr++ = pixels[kSize * y + x]; + *ptr++ = pixels[kSize * y + x]; + *ptr++ = 0xff; + } + } + DesktopVector hotspot(2, 2); + cursor_shape_.reset(new MouseCursor(default_cursor.release(), hotspot)); +} + +MouseCursorMonitorX11::~MouseCursorMonitorX11() { + if (have_xfixes_) { + x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify, + this); + } +} + +void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) { + // Init can be called only if not started + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; + mode_ = mode; + + have_xfixes_ = + XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_); + + if (have_xfixes_) { + // Register for changes to the cursor shape. + XErrorTrap error_trap(display()); + XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask); + x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this); + + CaptureCursor(); + } else { + RTC_LOG(LS_INFO) << "X server does not support XFixes."; + } +} + +void MouseCursorMonitorX11::Capture() { + RTC_DCHECK(callback_); + + // Process X11 events in case XFixes has sent cursor notification. + x_display_->ProcessPendingXEvents(); + + // cursor_shape_| is set only if we were notified of a cursor shape change. + if (cursor_shape_.get()) + callback_->OnMouseCursor(cursor_shape_.release()); + + // Get cursor position if necessary. + if (mode_ == SHAPE_AND_POSITION) { + int root_x; + int root_y; + int win_x; + int win_y; + Window root_window; + Window child_window; + unsigned int mask; + + XErrorTrap error_trap(display()); + Bool result = XQueryPointer(display(), window_, &root_window, &child_window, + &root_x, &root_y, &win_x, &win_y, &mask); + CursorState state; + if (!result || error_trap.GetLastErrorAndDisable() != 0) { + state = OUTSIDE; + } else { + // In screen mode (window_ == root_window) the mouse is always inside. + // XQueryPointer() sets `child_window` to None if the cursor is outside + // `window_`. + state = + (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE; + } + + // As the comments to GetTopLevelWindow() above indicate, in window capture, + // the cursor position capture happens in `window_`, while the frame catpure + // happens in `child_window`. These two windows are not alwyas same, as + // window manager may add some decorations to the `window_`. So translate + // the coordinate in `window_` to the coordinate space of `child_window`. + if (window_ != root_window && state == INSIDE) { + int translated_x, translated_y; + Window unused; + if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y, + &translated_x, &translated_y, &unused)) { + win_x = translated_x; + win_y = translated_y; + } + } + + // X11 always starts the coordinate from (0, 0), so we do not need to + // translate here. + callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y)); + } +} + +bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) { + if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) { + const XFixesCursorNotifyEvent* cursor_event = + reinterpret_cast<const XFixesCursorNotifyEvent*>(&event); + if (cursor_event->subtype == XFixesDisplayCursorNotify) { + CaptureCursor(); + } + // Return false, even if the event has been handled, because there might be + // other listeners for cursor notifications. + } + return false; +} + +void MouseCursorMonitorX11::CaptureCursor() { + RTC_DCHECK(have_xfixes_); + + XFixesCursorImage* img; + { + XErrorTrap error_trap(display()); + img = XFixesGetCursorImage(display()); + if (!img || error_trap.GetLastErrorAndDisable() != 0) + return; + } + + std::unique_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(img->width, img->height))); + + // Xlib stores 32-bit data in longs, even if longs are 64-bits long. + unsigned long* src = img->pixels; // NOLINT(runtime/int) + uint32_t* dst = reinterpret_cast<uint32_t*>(image->data()); + uint32_t* dst_end = dst + (img->width * img->height); + while (dst < dst_end) { + *dst++ = static_cast<uint32_t>(*src++); + } + + DesktopVector hotspot(std::min(img->width, img->xhot), + std::min(img->height, img->yhot)); + + XFree(img); + + cursor_shape_.reset(new MouseCursor(image.release(), hotspot)); +} + +// static +MouseCursorMonitor* MouseCursorMonitorX11::CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window) { + if (!options.x_display()) + return NULL; + window = GetTopLevelWindow(options.x_display()->display(), window); + if (window == None) + return NULL; + return new MouseCursorMonitorX11(options, window); +} + +MouseCursorMonitor* MouseCursorMonitorX11::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + if (!options.x_display()) + return NULL; + WindowId window = DefaultRootWindow(options.x_display()->display()); + return new MouseCursorMonitorX11(options, window); +} + +std::unique_ptr<MouseCursorMonitor> MouseCursorMonitorX11::Create( + const DesktopCaptureOptions& options) { + return std::unique_ptr<MouseCursorMonitor>( + CreateForScreen(options, kFullDesktopScreenId)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h new file mode 100644 index 0000000000..980d254a0a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_ + +#include <X11/X.h> + +#include <memory> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" + +namespace webrtc { + +class MouseCursorMonitorX11 : public MouseCursorMonitor, + public SharedXDisplay::XEventHandler { + public: + MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window); + ~MouseCursorMonitorX11() override; + + static MouseCursorMonitor* CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window); + static MouseCursorMonitor* CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen); + static std::unique_ptr<MouseCursorMonitor> Create( + const DesktopCaptureOptions& options); + + void Init(Callback* callback, Mode mode) override; + void Capture() override; + + private: + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + Display* display() { return x_display_->display(); } + + // Captures current cursor shape and stores it in `cursor_shape_`. + void CaptureCursor(); + + rtc::scoped_refptr<SharedXDisplay> x_display_; + Callback* callback_; + Mode mode_; + Window window_; + + bool have_xfixes_; + int xfixes_event_base_; + int xfixes_error_base_; + + std::unique_ptr<MouseCursor> cursor_shape_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc new file mode 100644 index 0000000000..684838bee5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.cc @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/screen_capturer_x11.h" + +#include <X11/Xlib.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/damagewire.h> +#include <dlfcn.h> +#include <stdint.h> +#include <string.h> + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +ScreenCapturerX11::ScreenCapturerX11() { + helper_.SetLogGridSize(4); +} + +ScreenCapturerX11::~ScreenCapturerX11() { + options_.x_display()->RemoveEventHandler(ConfigureNotify, this); + if (use_damage_) { + options_.x_display()->RemoveEventHandler(damage_event_base_ + XDamageNotify, + this); + } + if (use_randr_) { + options_.x_display()->RemoveEventHandler( + randr_event_base_ + RRScreenChangeNotify, this); + } + DeinitXlib(); +} + +bool ScreenCapturerX11::Init(const DesktopCaptureOptions& options) { + TRACE_EVENT0("webrtc", "ScreenCapturerX11::Init"); + options_ = options; + + atom_cache_ = std::make_unique<XAtomCache>(display()); + + root_window_ = RootWindow(display(), DefaultScreen(display())); + if (root_window_ == BadValue) { + RTC_LOG(LS_ERROR) << "Unable to get the root window"; + DeinitXlib(); + return false; + } + + gc_ = XCreateGC(display(), root_window_, 0, NULL); + if (gc_ == NULL) { + RTC_LOG(LS_ERROR) << "Unable to get graphics context"; + DeinitXlib(); + return false; + } + + options_.x_display()->AddEventHandler(ConfigureNotify, this); + + // Check for XFixes extension. This is required for cursor shape + // notifications, and for our use of XDamage. + if (XFixesQueryExtension(display(), &xfixes_event_base_, + &xfixes_error_base_)) { + has_xfixes_ = true; + } else { + RTC_LOG(LS_INFO) << "X server does not support XFixes."; + } + + // Register for changes to the dimensions of the root window. + XSelectInput(display(), root_window_, StructureNotifyMask); + + if (!x_server_pixel_buffer_.Init(atom_cache_.get(), + DefaultRootWindow(display()))) { + RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer."; + return false; + } + + if (options_.use_update_notifications()) { + InitXDamage(); + } + + InitXrandr(); + + // Default source set here so that selected_monitor_rect_ is sized correctly. + SelectSource(kFullDesktopScreenId); + + return true; +} + +void ScreenCapturerX11::InitXDamage() { + // Our use of XDamage requires XFixes. + if (!has_xfixes_) { + return; + } + + // Check for XDamage extension. + if (!XDamageQueryExtension(display(), &damage_event_base_, + &damage_error_base_)) { + RTC_LOG(LS_INFO) << "X server does not support XDamage."; + return; + } + + // TODO(lambroslambrou): Disable DAMAGE in situations where it is known + // to fail, such as when Desktop Effects are enabled, with graphics + // drivers (nVidia, ATI) that fail to report DAMAGE notifications + // properly. + + // Request notifications every time the screen becomes damaged. + damage_handle_ = + XDamageCreate(display(), root_window_, XDamageReportNonEmpty); + if (!damage_handle_) { + RTC_LOG(LS_ERROR) << "Unable to initialize XDamage."; + return; + } + + // Create an XFixes server-side region to collate damage into. + damage_region_ = XFixesCreateRegion(display(), 0, 0); + if (!damage_region_) { + XDamageDestroy(display(), damage_handle_); + RTC_LOG(LS_ERROR) << "Unable to create XFixes region."; + return; + } + + options_.x_display()->AddEventHandler(damage_event_base_ + XDamageNotify, + this); + + use_damage_ = true; + RTC_LOG(LS_INFO) << "Using XDamage extension."; +} + +RTC_NO_SANITIZE("cfi-icall") +void ScreenCapturerX11::InitXrandr() { + int major_version = 0; + int minor_version = 0; + int error_base_ignored = 0; + if (XRRQueryExtension(display(), &randr_event_base_, &error_base_ignored) && + XRRQueryVersion(display(), &major_version, &minor_version)) { + if (major_version > 1 || (major_version == 1 && minor_version >= 5)) { + // Dynamically link XRRGetMonitors and XRRFreeMonitors as a workaround + // to avoid a dependency issue with Debian 8. + get_monitors_ = reinterpret_cast<get_monitors_func>( + dlsym(RTLD_DEFAULT, "XRRGetMonitors")); + free_monitors_ = reinterpret_cast<free_monitors_func>( + dlsym(RTLD_DEFAULT, "XRRFreeMonitors")); + if (get_monitors_ && free_monitors_) { + use_randr_ = true; + RTC_LOG(LS_INFO) << "Using XRandR extension v" << major_version << '.' + << minor_version << '.'; + monitors_ = + get_monitors_(display(), root_window_, true, &num_monitors_); + + // Register for screen change notifications + XRRSelectInput(display(), root_window_, RRScreenChangeNotifyMask); + options_.x_display()->AddEventHandler( + randr_event_base_ + RRScreenChangeNotify, this); + } else { + RTC_LOG(LS_ERROR) << "Unable to link XRandR monitor functions."; + } + } else { + RTC_LOG(LS_ERROR) << "XRandR entension is older than v1.5."; + } + } else { + RTC_LOG(LS_ERROR) << "X server does not support XRandR."; + } +} + +RTC_NO_SANITIZE("cfi-icall") +void ScreenCapturerX11::UpdateMonitors() { + if (monitors_) { + free_monitors_(monitors_); + monitors_ = nullptr; + } + + monitors_ = get_monitors_(display(), root_window_, true, &num_monitors_); + + if (selected_monitor_name_) { + if (selected_monitor_name_ == static_cast<Atom>(kFullDesktopScreenId)) { + selected_monitor_rect_ = + DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); + return; + } + + for (int i = 0; i < num_monitors_; ++i) { + XRRMonitorInfo& m = monitors_[i]; + if (selected_monitor_name_ == m.name) { + RTC_LOG(LS_INFO) << "XRandR monitor " << m.name << " rect updated."; + selected_monitor_rect_ = + DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height); + const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect(); + if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) { + // This is never expected to happen, but crop the rectangle anyway + // just in case the server returns inconsistent information. + // CaptureScreen() expects `selected_monitor_rect_` to lie within + // the pixel-buffer's rectangle. + RTC_LOG(LS_WARNING) + << "Cropping selected monitor rect to fit the pixel-buffer."; + selected_monitor_rect_.IntersectWith(pixel_buffer_rect); + } + return; + } + } + + // The selected monitor is not connected anymore + RTC_LOG(LS_INFO) << "XRandR selected monitor " << selected_monitor_name_ + << " lost."; + selected_monitor_rect_ = DesktopRect::MakeWH(0, 0); + } +} + +void ScreenCapturerX11::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void ScreenCapturerX11::CaptureFrame() { + TRACE_EVENT0("webrtc", "ScreenCapturerX11::CaptureFrame"); + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; + } + + // Process XEvents for XDamage and cursor shape tracking. + options_.x_display()->ProcessPendingXEvents(); + + // ProcessPendingXEvents() may call ScreenConfigurationChanged() which + // reinitializes `x_server_pixel_buffer_`. Check if the pixel buffer is still + // in a good shape. + if (!x_server_pixel_buffer_.is_initialized()) { + // We failed to initialize pixel buffer. + RTC_LOG(LS_ERROR) << "Pixel buffer is not initialized."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + // Allocate the current frame buffer only if it is not already allocated. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) { + std::unique_ptr<DesktopFrame> frame( + new BasicDesktopFrame(selected_monitor_rect_.size())); + + // We set the top-left of the frame so the mouse cursor will be composited + // properly, and our frame buffer will not be overrun while blitting. + frame->set_top_left(selected_monitor_rect_.top_left()); + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame))); + } + + std::unique_ptr<DesktopFrame> result = CaptureScreen(); + if (!result) { + RTC_LOG(LS_WARNING) << "Temporarily failed to capture screen."; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + last_invalid_region_ = result->updated_region(); + result->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(Result::SUCCESS, std::move(result)); +} + +bool ScreenCapturerX11::GetSourceList(SourceList* sources) { + RTC_DCHECK(sources->size() == 0); + if (!use_randr_) { + sources->push_back({}); + return true; + } + + // Ensure that `monitors_` is updated with changes that may have happened + // between calls to GetSourceList(). + options_.x_display()->ProcessPendingXEvents(); + + for (int i = 0; i < num_monitors_; ++i) { + XRRMonitorInfo& m = monitors_[i]; + char* monitor_title = XGetAtomName(display(), m.name); + + // Note name is an X11 Atom used to id the monitor. + sources->push_back({static_cast<SourceId>(m.name), 0, monitor_title}); + XFree(monitor_title); + } + + return true; +} + +bool ScreenCapturerX11::SelectSource(SourceId id) { + // Prevent the reuse of any frame buffers allocated for a previously selected + // source. This is required to stop crashes, or old data from appearing in + // a captured frame, when the new source is sized differently then the source + // that was selected at the time a reused frame buffer was created. + queue_.Reset(); + + if (!use_randr_ || id == kFullDesktopScreenId) { + selected_monitor_name_ = kFullDesktopScreenId; + selected_monitor_rect_ = + DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); + return true; + } + + for (int i = 0; i < num_monitors_; ++i) { + if (id == static_cast<SourceId>(monitors_[i].name)) { + RTC_LOG(LS_INFO) << "XRandR selected source: " << id; + XRRMonitorInfo& m = monitors_[i]; + selected_monitor_name_ = m.name; + selected_monitor_rect_ = + DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height); + const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect(); + if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) { + RTC_LOG(LS_WARNING) + << "Cropping selected monitor rect to fit the pixel-buffer."; + selected_monitor_rect_.IntersectWith(pixel_buffer_rect); + } + return true; + } + } + return false; +} + +bool ScreenCapturerX11::HandleXEvent(const XEvent& event) { + if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) { + const XDamageNotifyEvent* damage_event = + reinterpret_cast<const XDamageNotifyEvent*>(&event); + if (damage_event->damage != damage_handle_) + return false; + RTC_DCHECK(damage_event->level == XDamageReportNonEmpty); + return true; + } else if (use_randr_ && + event.type == randr_event_base_ + RRScreenChangeNotify) { + XRRUpdateConfiguration(const_cast<XEvent*>(&event)); + UpdateMonitors(); + RTC_LOG(LS_INFO) << "XRandR screen change event received."; + return false; + } else if (event.type == ConfigureNotify) { + ScreenConfigurationChanged(); + return false; + } + return false; +} + +std::unique_ptr<DesktopFrame> ScreenCapturerX11::CaptureScreen() { + std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share(); + RTC_DCHECK(selected_monitor_rect_.size().equals(frame->size())); + RTC_DCHECK(selected_monitor_rect_.top_left().equals(frame->top_left())); + + // Pass the screen size to the helper, so it can clip the invalid region if it + // expands that region to a grid. Note that the helper operates in the + // DesktopFrame coordinate system where the top-left pixel is (0, 0), even for + // a monitor with non-zero offset relative to `x_server_pixel_buffer_`. + helper_.set_size_most_recent(frame->size()); + + // In the DAMAGE case, ensure the frame is up-to-date with the previous frame + // if any. If there isn't a previous frame, that means a screen-resolution + // change occurred, and `invalid_rects` will be updated to include the whole + // screen. + if (use_damage_ && queue_.previous_frame()) + SynchronizeFrame(); + + DesktopRegion* updated_region = frame->mutable_updated_region(); + + x_server_pixel_buffer_.Synchronize(); + if (use_damage_ && queue_.previous_frame()) { + // Atomically fetch and clear the damage region. + XDamageSubtract(display(), damage_handle_, None, damage_region_); + int rects_num = 0; + XRectangle bounds; + XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_, + &rects_num, &bounds); + for (int i = 0; i < rects_num; ++i) { + auto damage_rect = DesktopRect::MakeXYWH(rects[i].x, rects[i].y, + rects[i].width, rects[i].height); + + // Damage-regions are relative to `x_server_pixel_buffer`, so convert the + // region to DesktopFrame coordinates where the top-left is always (0, 0), + // before adding to the frame's updated_region. `helper_` also operates in + // DesktopFrame coordinates, and it will take care of cropping away any + // damage-regions that lie outside the selected monitor. + damage_rect.Translate(-frame->top_left()); + updated_region->AddRect(damage_rect); + } + XFree(rects); + helper_.InvalidateRegion(*updated_region); + + // Capture the damaged portions of the desktop. + helper_.TakeInvalidRegion(updated_region); + + for (DesktopRegion::Iterator it(*updated_region); !it.IsAtEnd(); + it.Advance()) { + auto rect = it.rect(); + rect.Translate(frame->top_left()); + if (!x_server_pixel_buffer_.CaptureRect(rect, frame.get())) + return nullptr; + } + } else { + // Doing full-screen polling, or this is the first capture after a + // screen-resolution change. In either case, need a full-screen capture. + if (!x_server_pixel_buffer_.CaptureRect(selected_monitor_rect_, + frame.get())) { + return nullptr; + } + updated_region->SetRect(DesktopRect::MakeSize(frame->size())); + } + + return std::move(frame); +} + +void ScreenCapturerX11::ScreenConfigurationChanged() { + TRACE_EVENT0("webrtc", "ScreenCapturerX11::ScreenConfigurationChanged"); + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + if (!x_server_pixel_buffer_.Init(atom_cache_.get(), + DefaultRootWindow(display()))) { + RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " + "configuration change."; + } + + if (use_randr_) { + // Adding/removing RANDR monitors can generate a ConfigureNotify event + // without generating any RRScreenChangeNotify event. So it is important to + // update the monitors here even if the screen resolution hasn't changed. + UpdateMonitors(); + } else { + selected_monitor_rect_ = + DesktopRect::MakeSize(x_server_pixel_buffer_.window_size()); + } +} + +void ScreenCapturerX11::SynchronizeFrame() { + // Synchronize the current buffer with the previous one since we do not + // capture the entire desktop. Note that encoder may be reading from the + // previous buffer at this time so thread access complaints are false + // positives. + + // TODO(hclam): We can reduce the amount of copying here by subtracting + // `capturer_helper_`s region from `last_invalid_region_`. + // http://crbug.com/92354 + RTC_DCHECK(queue_.previous_frame()); + + DesktopFrame* current = queue_.current_frame(); + DesktopFrame* last = queue_.previous_frame(); + RTC_DCHECK(current != last); + for (DesktopRegion::Iterator it(last_invalid_region_); !it.IsAtEnd(); + it.Advance()) { + const DesktopRect& r = it.rect(); + current->CopyPixelsFrom(*last, r.top_left(), r); + } +} + +RTC_NO_SANITIZE("cfi-icall") +void ScreenCapturerX11::DeinitXlib() { + if (monitors_) { + free_monitors_(monitors_); + monitors_ = nullptr; + } + + if (gc_) { + XFreeGC(display(), gc_); + gc_ = nullptr; + } + + x_server_pixel_buffer_.Release(); + + if (display()) { + if (damage_handle_) { + XDamageDestroy(display(), damage_handle_); + damage_handle_ = 0; + } + + if (damage_region_) { + XFixesDestroyRegion(display(), damage_region_); + damage_region_ = 0; + } + } +} + +// static +std::unique_ptr<DesktopCapturer> ScreenCapturerX11::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + if (!options.x_display()) + return nullptr; + + std::unique_ptr<ScreenCapturerX11> capturer(new ScreenCapturerX11()); + if (!capturer.get()->Init(options)) { + return nullptr; + } + + return std::move(capturer); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h new file mode 100644 index 0000000000..d2a437aaa2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/screen_capturer_x11.h @@ -0,0 +1,147 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_ + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xrandr.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/linux/x11/x_atom_cache.h" +#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" + +namespace webrtc { + +// A class to perform video frame capturing for Linux on X11. +// +// If XDamage is used, this class sets DesktopFrame::updated_region() according +// to the areas reported by XDamage. Otherwise this class does not detect +// DesktopFrame::updated_region(), the field is always set to the entire frame +// rectangle. ScreenCapturerDifferWrapper should be used if that functionality +// is necessary. +class ScreenCapturerX11 : public DesktopCapturer, + public SharedXDisplay::XEventHandler { + public: + ScreenCapturerX11(); + ~ScreenCapturerX11() override; + + ScreenCapturerX11(const ScreenCapturerX11&) = delete; + ScreenCapturerX11& operator=(const ScreenCapturerX11&) = delete; + + static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer( + const DesktopCaptureOptions& options); + + // TODO(ajwong): Do we really want this to be synchronous? + bool Init(const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* delegate) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + Display* display() { return options_.x_display()->display(); } + + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + void InitXDamage(); + void InitXrandr(); + void UpdateMonitors(); + + // Capture screen pixels to the current buffer in the queue. In the DAMAGE + // case, the ScreenCapturerHelper already holds the list of invalid rectangles + // from HandleXEvent(). In the non-DAMAGE case, this captures the + // whole screen, then calculates some invalid rectangles that include any + // differences between this and the previous capture. + std::unique_ptr<DesktopFrame> CaptureScreen(); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + // Synchronize the current buffer with `last_buffer_`, by copying pixels from + // the area of `last_invalid_rects`. + // Note this only works on the assumption that kNumBuffers == 2, as + // `last_invalid_rects` holds the differences from the previous buffer and + // the one prior to that (which will then be the current buffer). + void SynchronizeFrame(); + + void DeinitXlib(); + + DesktopCaptureOptions options_; + + Callback* callback_ = nullptr; + + // X11 graphics context. + GC gc_ = nullptr; + Window root_window_ = BadValue; + + // XRandR 1.5 monitors. + bool use_randr_ = false; + int randr_event_base_ = 0; + XRRMonitorInfo* monitors_ = nullptr; + int num_monitors_ = 0; + DesktopRect selected_monitor_rect_; + // selected_monitor_name_ will be changed to kFullDesktopScreenId + // by a call to SelectSource() at the end of Init() because + // selected_monitor_rect_ should be updated as well. + // Setting it to kFullDesktopScreenId here might be misleading. + Atom selected_monitor_name_ = 0; + typedef XRRMonitorInfo* (*get_monitors_func)(Display*, Window, Bool, int*); + typedef void (*free_monitors_func)(XRRMonitorInfo*); + get_monitors_func get_monitors_ = nullptr; + free_monitors_func free_monitors_ = nullptr; + + // XFixes. + bool has_xfixes_ = false; + int xfixes_event_base_ = -1; + int xfixes_error_base_ = -1; + + // XDamage information. + bool use_damage_ = false; + Damage damage_handle_ = 0; + int damage_event_base_ = -1; + int damage_error_base_ = -1; + XserverRegion damage_region_ = 0; + + // Access to the X Server's pixel buffer. + XServerPixelBuffer x_server_pixel_buffer_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + // Invalid region from the previous capture. This is used to synchronize the + // current with the last buffer used. + DesktopRegion last_invalid_region_; + + std::unique_ptr<XAtomCache> atom_cache_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc new file mode 100644 index 0000000000..b7849508b0 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.cc @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/shared_x_display.h" + +#include <X11/Xlib.h> +#include <X11/extensions/XTest.h> + +#include <algorithm> + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +SharedXDisplay::SharedXDisplay(Display* display) : display_(display) { + RTC_DCHECK(display_); +} + +SharedXDisplay::~SharedXDisplay() { + RTC_DCHECK(event_handlers_.empty()); + XCloseDisplay(display_); +} + +// static +rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create( + absl::string_view display_name) { + Display* display = XOpenDisplay( + display_name.empty() ? NULL : std::string(display_name).c_str()); + if (!display) { + RTC_LOG(LS_ERROR) << "Unable to open display"; + return nullptr; + } + return rtc::scoped_refptr<SharedXDisplay>(new SharedXDisplay(display)); +} + +// static +rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::CreateDefault() { + return Create(std::string()); +} + +void SharedXDisplay::AddEventHandler(int type, XEventHandler* handler) { + event_handlers_[type].push_back(handler); +} + +void SharedXDisplay::RemoveEventHandler(int type, XEventHandler* handler) { + EventHandlersMap::iterator handlers = event_handlers_.find(type); + if (handlers == event_handlers_.end()) + return; + + std::vector<XEventHandler*>::iterator new_end = + std::remove(handlers->second.begin(), handlers->second.end(), handler); + handlers->second.erase(new_end, handlers->second.end()); + + // Check if no handlers left for this event. + if (handlers->second.empty()) + event_handlers_.erase(handlers); +} + +void SharedXDisplay::ProcessPendingXEvents() { + // Hold reference to `this` to prevent it from being destroyed while + // processing events. + rtc::scoped_refptr<SharedXDisplay> self(this); + + // Find the number of events that are outstanding "now." We don't just loop + // on XPending because we want to guarantee this terminates. + int events_to_process = XPending(display()); + XEvent e; + + for (int i = 0; i < events_to_process; i++) { + XNextEvent(display(), &e); + EventHandlersMap::iterator handlers = event_handlers_.find(e.type); + if (handlers == event_handlers_.end()) + continue; + for (std::vector<XEventHandler*>::iterator it = handlers->second.begin(); + it != handlers->second.end(); ++it) { + if ((*it)->HandleXEvent(e)) + break; + } + } +} + +void SharedXDisplay::IgnoreXServerGrabs() { + int test_event_base = 0; + int test_error_base = 0; + int major = 0; + int minor = 0; + if (XTestQueryExtension(display(), &test_event_base, &test_error_base, &major, + &minor)) { + XTestGrabControl(display(), true); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h new file mode 100644 index 0000000000..084da80167 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/shared_x_display.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_ + +#include <map> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/ref_counted_base.h" +#include "api/scoped_refptr.h" +#include "rtc_base/system/rtc_export.h" + +// Including Xlib.h will involve evil defines (Bool, Status, True, False), which +// easily conflict with other headers. +typedef struct _XDisplay Display; +typedef union _XEvent XEvent; + +namespace webrtc { + +// A ref-counted object to store XDisplay connection. +class RTC_EXPORT SharedXDisplay + : public rtc::RefCountedNonVirtual<SharedXDisplay> { + public: + class XEventHandler { + public: + virtual ~XEventHandler() {} + + // Processes XEvent. Returns true if the event has been handled. + virtual bool HandleXEvent(const XEvent& event) = 0; + }; + + // Creates a new X11 Display for the `display_name`. NULL is returned if X11 + // connection failed. Equivalent to CreateDefault() when `display_name` is + // empty. + static rtc::scoped_refptr<SharedXDisplay> Create( + absl::string_view display_name); + + // Creates X11 Display connection for the default display (e.g. specified in + // DISPLAY). NULL is returned if X11 connection failed. + static rtc::scoped_refptr<SharedXDisplay> CreateDefault(); + + Display* display() { return display_; } + + // Adds a new event `handler` for XEvent's of `type`. + void AddEventHandler(int type, XEventHandler* handler); + + // Removes event `handler` added using `AddEventHandler`. Doesn't do anything + // if `handler` is not registered. + void RemoveEventHandler(int type, XEventHandler* handler); + + // Processes pending XEvents, calling corresponding event handlers. + void ProcessPendingXEvents(); + + void IgnoreXServerGrabs(); + + ~SharedXDisplay(); + + SharedXDisplay(const SharedXDisplay&) = delete; + SharedXDisplay& operator=(const SharedXDisplay&) = delete; + + protected: + // Takes ownership of `display`. + explicit SharedXDisplay(Display* display); + + private: + typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap; + + Display* display_; + + EventHandlersMap event_handlers_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc new file mode 100644 index 0000000000..3015a474ff --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.cc @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/window_capturer_x11.h" + +#include <X11/Xutil.h> +#include <X11/extensions/Xcomposite.h> +#include <X11/extensions/composite.h> +#include <string.h> + +#include <memory> +#include <string> +#include <utility> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/linux/x11/window_finder_x11.h" +#include "modules/desktop_capture/linux/x11/window_list_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +WindowCapturerX11::WindowCapturerX11(const DesktopCaptureOptions& options) + : x_display_(options.x_display()), + atom_cache_(display()), + window_finder_(&atom_cache_) { + int event_base, error_base, major_version, minor_version; + if (XCompositeQueryExtension(display(), &event_base, &error_base) && + XCompositeQueryVersion(display(), &major_version, &minor_version) && + // XCompositeNameWindowPixmap() requires version 0.2 + (major_version > 0 || minor_version >= 2)) { + has_composite_extension_ = true; + } else { + RTC_LOG(LS_INFO) << "Xcomposite extension not available or too old."; + } + + x_display_->AddEventHandler(ConfigureNotify, this); +} + +WindowCapturerX11::~WindowCapturerX11() { + x_display_->RemoveEventHandler(ConfigureNotify, this); +} + +bool WindowCapturerX11::GetSourceList(SourceList* sources) { + return GetWindowList(&atom_cache_, [this, sources](::Window window) { + Source w; + w.id = window; + w.pid = (pid_t)GetWindowProcessID(window); + if (this->GetWindowTitle(window, &w.title)) { + sources->push_back(w); + } + return true; + }); +} + +bool WindowCapturerX11::SelectSource(SourceId id) { + if (!x_server_pixel_buffer_.Init(&atom_cache_, id)) + return false; + + // Tell the X server to send us window resizing events. + XSelectInput(display(), id, StructureNotifyMask); + + selected_window_ = id; + + // In addition to needing X11 server-side support for Xcomposite, it actually + // needs to be turned on for the window. If the user has modern + // hardware/drivers but isn't using a compositing window manager, that won't + // be the case. Here we automatically turn it on. + + // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11 + // remembers who has requested this and will turn it off for us when we exit. + XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic); + + return true; +} + +bool WindowCapturerX11::FocusOnSelectedSource() { + if (!selected_window_) + return false; + + unsigned int num_children; + ::Window* children; + ::Window parent; + ::Window root; + // Find the root window to pass event to. + int status = XQueryTree(display(), selected_window_, &root, &parent, + &children, &num_children); + if (status == 0) { + RTC_LOG(LS_ERROR) << "Failed to query for the root window."; + return false; + } + + if (children) + XFree(children); + + XRaiseWindow(display(), selected_window_); + + // Some window managers (e.g., metacity in GNOME) consider it illegal to + // raise a window without also giving it input focus with + // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. + Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True); + if (atom != None) { + XEvent xev; + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.window = selected_window_; + xev.xclient.message_type = atom; + + // The format member is set to 8, 16, or 32 and specifies whether the + // data should be viewed as a list of bytes, shorts, or longs. + xev.xclient.format = 32; + + memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l)); + + XSendEvent(display(), root, False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } + XFlush(display()); + return true; +} + +void WindowCapturerX11::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void WindowCapturerX11::CaptureFrame() { + TRACE_EVENT0("webrtc", "WindowCapturerX11::CaptureFrame"); + x_display_->ProcessPendingXEvents(); + + if (!x_server_pixel_buffer_.IsWindowValid()) { + RTC_LOG(LS_ERROR) << "The window is no longer valid."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + if (!has_composite_extension_) { + // Without the Xcomposite extension we capture when the whole window is + // visible on screen and not covered by any other window. This is not + // something we want so instead, just bail out. + RTC_LOG(LS_ERROR) << "No Xcomposite extension detected."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + if (GetWindowState(&atom_cache_, selected_window_) == IconicState) { + // Window is in minimized. Return a 1x1 frame as same as OSX/Win does. + std::unique_ptr<DesktopFrame> frame( + new BasicDesktopFrame(DesktopSize(1, 1))); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); + return; + } + + std::unique_ptr<DesktopFrame> frame( + new BasicDesktopFrame(x_server_pixel_buffer_.window_size())); + + x_server_pixel_buffer_.Synchronize(); + if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()), + frame.get())) { + RTC_LOG(LS_WARNING) << "Temporarily failed to capture winodw."; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + frame->set_top_left(x_server_pixel_buffer_.window_rect().top_left()); + + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool WindowCapturerX11::IsOccluded(const DesktopVector& pos) { + return window_finder_.GetWindowUnderPoint(pos) != + static_cast<WindowId>(selected_window_); +} + +bool WindowCapturerX11::HandleXEvent(const XEvent& event) { + if (event.type == ConfigureNotify) { + XConfigureEvent xce = event.xconfigure; + if (xce.window == selected_window_) { + if (!DesktopRectFromXAttributes(xce).equals( + x_server_pixel_buffer_.window_rect())) { + if (!x_server_pixel_buffer_.Init(&atom_cache_, selected_window_)) { + RTC_LOG(LS_ERROR) + << "Failed to initialize pixel buffer after resizing."; + } + } + } + } + + // Always returns false, so other observers can still receive the events. + return false; +} + +bool WindowCapturerX11::GetWindowTitle(::Window window, std::string* title) { + int status; + bool result = false; + XTextProperty window_name; + window_name.value = nullptr; + if (window) { + status = XGetWMName(display(), window, &window_name); + if (status && window_name.value && window_name.nitems) { + int cnt; + char** list = nullptr; + status = + Xutf8TextPropertyToTextList(display(), &window_name, &list, &cnt); + if (status >= Success && cnt && *list) { + if (cnt > 1) { + RTC_LOG(LS_INFO) << "Window has " << cnt + << " text properties, only using the first one."; + } + *title = *list; + result = true; + } + if (list) + XFreeStringList(list); + } + if (window_name.value) + XFree(window_name.value); + } + return result; +} + +int WindowCapturerX11::GetWindowProcessID(::Window window) { + // Get _NET_WM_PID property of the window. + Atom process_atom = XInternAtom(display(), "_NET_WM_PID", True); + XWindowProperty<uint32_t> process_id(display(), window, process_atom); + + return process_id.is_valid() ? *process_id.data() : 0; +} + +// static +std::unique_ptr<DesktopCapturer> WindowCapturerX11::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + if (!options.x_display()) + return nullptr; + return std::unique_ptr<DesktopCapturer>(new WindowCapturerX11(options)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h new file mode 100644 index 0000000000..cfd29eca66 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_capturer_x11.h @@ -0,0 +1,78 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_ + +#include <X11/X.h> +#include <X11/Xlib.h> + +#include <memory> +#include <string> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/linux/x11/x_window_property.h" +#include "modules/desktop_capture/linux/x11/window_finder_x11.h" +#include "modules/desktop_capture/linux/x11/x_atom_cache.h" +#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h" + +namespace webrtc { + +class WindowCapturerX11 : public DesktopCapturer, + public SharedXDisplay::XEventHandler { + public: + explicit WindowCapturerX11(const DesktopCaptureOptions& options); + ~WindowCapturerX11() override; + + WindowCapturerX11(const WindowCapturerX11&) = delete; + WindowCapturerX11& operator=(const WindowCapturerX11&) = delete; + + static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + private: + Display* display() { return x_display_->display(); } + + // Returns window title for the specified X `window`. + bool GetWindowTitle(::Window window, std::string* title); + + // Returns the id of the owning process. + int GetWindowProcessID(::Window window); + + Callback* callback_ = nullptr; + + rtc::scoped_refptr<SharedXDisplay> x_display_; + + bool has_composite_extension_ = false; + + ::Window selected_window_ = 0; + XServerPixelBuffer x_server_pixel_buffer_; + XAtomCache atom_cache_; + WindowFinderX11 window_finder_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc new file mode 100644 index 0000000000..dec17ab51f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.cc @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/window_finder_x11.h" + +#include <X11/X.h> + +#include <memory> + +#include "modules/desktop_capture/linux/x11/window_list_utils.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +WindowFinderX11::WindowFinderX11(XAtomCache* cache) : cache_(cache) { + RTC_DCHECK(cache_); +} + +WindowFinderX11::~WindowFinderX11() = default; + +WindowId WindowFinderX11::GetWindowUnderPoint(DesktopVector point) { + WindowId id = kNullWindowId; + GetWindowList(cache_, [&id, this, point](::Window window) { + DesktopRect rect; + if (GetWindowRect(this->cache_->display(), window, &rect) && + rect.Contains(point)) { + id = window; + return false; + } + return true; + }); + return id; +} + +// static +std::unique_ptr<WindowFinder> WindowFinder::Create( + const WindowFinder::Options& options) { + if (options.cache == nullptr) { + return nullptr; + } + + return std::make_unique<WindowFinderX11>(options.cache); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h new file mode 100644 index 0000000000..91de876417 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_finder_x11.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_ + +#include "modules/desktop_capture/window_finder.h" + +namespace webrtc { + +class XAtomCache; + +// The implementation of WindowFinder for X11. +class WindowFinderX11 final : public WindowFinder { + public: + explicit WindowFinderX11(XAtomCache* cache); + ~WindowFinderX11() override; + + // WindowFinder implementation. + WindowId GetWindowUnderPoint(DesktopVector point) override; + + private: + XAtomCache* const cache_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc new file mode 100644 index 0000000000..ff2d467e29 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.cc @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/window_list_utils.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <string.h> + +#include <algorithm> + +#include "modules/desktop_capture/linux/x11/x_error_trap.h" +#include "modules/desktop_capture/linux/x11/x_window_property.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +class DeferXFree { + public: + explicit DeferXFree(void* data) : data_(data) {} + ~DeferXFree(); + + private: + void* const data_; +}; + +DeferXFree::~DeferXFree() { + if (data_) + XFree(data_); +} + +// Iterates through `window` hierarchy to find first visible window, i.e. one +// that has WM_STATE property set to NormalState. +// See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 . +::Window GetApplicationWindow(XAtomCache* cache, ::Window window) { + int32_t state = GetWindowState(cache, window); + if (state == NormalState) { + // Window has WM_STATE==NormalState. Return it. + return window; + } else if (state == IconicState) { + // Window is in minimized. Skip it. + return 0; + } + + RTC_DCHECK_EQ(state, WithdrawnState); + // If the window is in WithdrawnState then look at all of its children. + ::Window root, parent; + ::Window* children; + unsigned int num_children; + if (!XQueryTree(cache->display(), window, &root, &parent, &children, + &num_children)) { + RTC_LOG(LS_ERROR) << "Failed to query for child windows although window" + "does not have a valid WM_STATE."; + return 0; + } + ::Window app_window = 0; + for (unsigned int i = 0; i < num_children; ++i) { + app_window = GetApplicationWindow(cache, children[i]); + if (app_window) + break; + } + + if (children) + XFree(children); + return app_window; +} + +// Returns true if the `window` is a desktop element. +bool IsDesktopElement(XAtomCache* cache, ::Window window) { + RTC_DCHECK(cache); + if (window == 0) + return false; + + // First look for _NET_WM_WINDOW_TYPE. The standard + // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306) + // says this hint *should* be present on all windows, and we use the existence + // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not + // a desktop element (that is, only "normal" windows should be shareable). + XWindowProperty<uint32_t> window_type(cache->display(), window, + cache->WindowType()); + if (window_type.is_valid() && window_type.size() > 0) { + uint32_t* end = window_type.data() + window_type.size(); + bool is_normal = + (end != std::find(window_type.data(), end, cache->WindowTypeNormal())); + return !is_normal; + } + + // Fall back on using the hint. + XClassHint class_hint; + Status status = XGetClassHint(cache->display(), window, &class_hint); + if (status == 0) { + // No hints, assume this is a normal application window. + return false; + } + + DeferXFree free_res_name(class_hint.res_name); + DeferXFree free_res_class(class_hint.res_class); + return strcmp("gnome-panel", class_hint.res_name) == 0 || + strcmp("desktop_window", class_hint.res_name) == 0; +} + +} // namespace + +int32_t GetWindowState(XAtomCache* cache, ::Window window) { + // Get WM_STATE property of the window. + XWindowProperty<uint32_t> window_state(cache->display(), window, + cache->WmState()); + + // WM_STATE is considered to be set to WithdrawnState when it missing. + return window_state.is_valid() ? *window_state.data() : WithdrawnState; +} + +bool GetWindowList(XAtomCache* cache, + rtc::FunctionView<bool(::Window)> on_window) { + RTC_DCHECK(cache); + RTC_DCHECK(on_window); + ::Display* const display = cache->display(); + + int failed_screens = 0; + const int num_screens = XScreenCount(display); + for (int screen = 0; screen < num_screens; screen++) { + ::Window root_window = XRootWindow(display, screen); + ::Window parent; + ::Window* children; + unsigned int num_children; + { + XErrorTrap error_trap(display); + if (XQueryTree(display, root_window, &root_window, &parent, &children, + &num_children) == 0 || + error_trap.GetLastErrorAndDisable() != 0) { + failed_screens++; + RTC_LOG(LS_ERROR) << "Failed to query for child windows for screen " + << screen; + continue; + } + } + + DeferXFree free_children(children); + + for (unsigned int i = 0; i < num_children; i++) { + // Iterates in reverse order to return windows from front to back. + ::Window app_window = + GetApplicationWindow(cache, children[num_children - 1 - i]); + if (app_window && !IsDesktopElement(cache, app_window)) { + if (!on_window(app_window)) { + return true; + } + } + } + } + + return failed_screens < num_screens; +} + +bool GetWindowRect(::Display* display, + ::Window window, + DesktopRect* rect, + XWindowAttributes* attributes /* = nullptr */) { + XWindowAttributes local_attributes; + int offset_x; + int offset_y; + if (attributes == nullptr) { + attributes = &local_attributes; + } + + { + XErrorTrap error_trap(display); + if (!XGetWindowAttributes(display, window, attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + *rect = DesktopRectFromXAttributes(*attributes); + + { + XErrorTrap error_trap(display); + ::Window child; + if (!XTranslateCoordinates(display, window, attributes->root, -rect->left(), + -rect->top(), &offset_x, &offset_y, &child) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + rect->Translate(offset_x, offset_y); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h new file mode 100644 index 0000000000..923842df14 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/window_list_utils.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_ + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <stdint.h> + +#include "api/function_view.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/linux/x11/x_atom_cache.h" + +namespace webrtc { + +// Synchronously iterates all on-screen windows in `cache`.display() in +// decreasing z-order and sends them one-by-one to `on_window` function before +// GetWindowList() returns. If `on_window` returns false, this function ignores +// other windows and returns immediately. GetWindowList() returns false if +// native APIs failed. If multiple screens are attached to the `display`, this +// function returns false only when native APIs failed on all screens. Menus, +// panels and minimized windows will be ignored. +bool GetWindowList(XAtomCache* cache, + rtc::FunctionView<bool(::Window)> on_window); + +// Returns WM_STATE property of the `window`. This function returns +// WithdrawnState if the `window` is missing. +int32_t GetWindowState(XAtomCache* cache, ::Window window); + +// Returns the rectangle of the `window` in the coordinates of `display`. This +// function returns false if native APIs failed. If `attributes` is provided, it +// will be filled with the attributes of `window`. The `rect` is in system +// coordinate, i.e. the primary monitor always starts from (0, 0). +bool GetWindowRect(::Display* display, + ::Window window, + DesktopRect* rect, + XWindowAttributes* attributes = nullptr); + +// Creates a DesktopRect from `attributes`. +template <typename T> +DesktopRect DesktopRectFromXAttributes(const T& attributes) { + return DesktopRect::MakeXYWH(attributes.x, attributes.y, attributes.width, + attributes.height); +} + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc new file mode 100644 index 0000000000..157ba8b8fd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.cc @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/x_atom_cache.h" + +#include "rtc_base/checks.h" + +namespace webrtc { + +XAtomCache::XAtomCache(::Display* display) : display_(display) { + RTC_DCHECK(display_); +} + +XAtomCache::~XAtomCache() = default; + +::Display* XAtomCache::display() const { + return display_; +} + +Atom XAtomCache::WmState() { + return CreateIfNotExist(&wm_state_, "WM_STATE"); +} + +Atom XAtomCache::WindowType() { + return CreateIfNotExist(&window_type_, "_NET_WM_WINDOW_TYPE"); +} + +Atom XAtomCache::WindowTypeNormal() { + return CreateIfNotExist(&window_type_normal_, "_NET_WM_WINDOW_TYPE_NORMAL"); +} + +Atom XAtomCache::IccProfile() { + return CreateIfNotExist(&icc_profile_, "_ICC_PROFILE"); +} + +Atom XAtomCache::CreateIfNotExist(Atom* atom, const char* name) { + RTC_DCHECK(atom); + if (*atom == None) { + *atom = XInternAtom(display(), name, True); + } + return *atom; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h new file mode 100644 index 0000000000..39d957e98b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_atom_cache.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_ + +#include <X11/X.h> +#include <X11/Xlib.h> + +namespace webrtc { + +// A cache of Atom. Each Atom object is created on demand. +class XAtomCache final { + public: + explicit XAtomCache(::Display* display); + ~XAtomCache(); + + ::Display* display() const; + + Atom WmState(); + Atom WindowType(); + Atom WindowTypeNormal(); + Atom IccProfile(); + + private: + // If |*atom| is None, this function uses XInternAtom() to retrieve an Atom. + Atom CreateIfNotExist(Atom* atom, const char* name); + + ::Display* const display_; + Atom wm_state_ = None; + Atom window_type_ = None; + Atom window_type_normal_ = None; + Atom icc_profile_ = None; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc new file mode 100644 index 0000000000..3314dd286c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.cc @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/x_error_trap.h" + +#include <stddef.h> + +#include <limits> + +#include "rtc_base/checks.h" + + +namespace webrtc { + +Bool XErrorTrap::XServerErrorHandler(Display* display, xReply* rep, + char* /* buf */, int /* len */, + XPointer data) { + XErrorTrap* self = reinterpret_cast<XErrorTrap*>(data); + if (rep->generic.type != X_Error || + // Overflow-safe last_request_read <= last_ignored_request_ for skipping + // async replies from requests before XErrorTrap was created. + self->last_ignored_request_ - display->last_request_read < + std::numeric_limits<unsigned long>::max() >> 1) + return False; + self->last_xserver_error_code_ = rep->error.errorCode; + return True; +} + +XErrorTrap::XErrorTrap(Display* display) + : display_(display), + last_xserver_error_code_(0), + enabled_(true) { + // Use async_handlers instead of XSetErrorHandler(). async_handlers can + // remain in place and then be safely removed at the right time even if a + // handler change happens concurrently on another thread. async_handlers + // are processed first and so can prevent errors reaching the global + // XSetErrorHandler handler. They also will not see errors from or affect + // handling of errors on other Displays, which may be processed on other + // threads. + LockDisplay(display); + async_handler_.next = display->async_handlers; + async_handler_.handler = XServerErrorHandler; + async_handler_.data = reinterpret_cast<XPointer>(this); + display->async_handlers = &async_handler_; + last_ignored_request_ = display->request; + UnlockDisplay(display); +} + +int XErrorTrap::GetLastErrorAndDisable() { + assert(enabled_); + enabled_ = false; + LockDisplay(display_); + DeqAsyncHandler(display_, &async_handler_); + UnlockDisplay(display_); + return last_xserver_error_code_; +} + +XErrorTrap::~XErrorTrap() { + if (enabled_) + GetLastErrorAndDisable(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h new file mode 100644 index 0000000000..df7e86bf03 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_error_trap.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_ + +#include <X11/Xlibint.h> +#undef max // Xlibint.h defines this and it breaks std::max +#undef min // Xlibint.h defines this and it breaks std::min + +namespace webrtc { + +// Helper class that registers X Window error handler. Caller can use +// GetLastErrorAndDisable() to get the last error that was caught, if any. +// An XErrorTrap may be constructed on any thread, but errors are collected +// from all threads and so |display| should be used only on one thread. +// Other Displays are unaffected. +class XErrorTrap { + public: + explicit XErrorTrap(Display* display); + ~XErrorTrap(); + + XErrorTrap(const XErrorTrap&) = delete; + XErrorTrap& operator=(const XErrorTrap&) = delete; + + // Returns last error and removes unregisters the error handler. + // Must not be called more than once. + int GetLastErrorAndDisable(); + + private: + static Bool XServerErrorHandler(Display* display, xReply* rep, + char* /* buf */, int /* len */, + XPointer data); + + _XAsyncHandler async_handler_; + Display* display_; + unsigned long last_ignored_request_; + int last_xserver_error_code_; + bool enabled_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc new file mode 100644 index 0000000000..fd6fc7daf4 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.cc @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h" + +#include <X11/Xutil.h> +#include <stdint.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/shm.h> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/linux/x11/window_list_utils.h" +#include "modules/desktop_capture/linux/x11/x_error_trap.h" +#include "modules/desktop_capture/linux/x11/x_window_property.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +// Returns the number of bits `mask` has to be shifted left so its last +// (most-significant) bit set becomes the most-significant bit of the word. +// When `mask` is 0 the function returns 31. +uint32_t MaskToShift(uint32_t mask) { + int shift = 0; + if ((mask & 0xffff0000u) == 0) { + mask <<= 16; + shift += 16; + } + if ((mask & 0xff000000u) == 0) { + mask <<= 8; + shift += 8; + } + if ((mask & 0xf0000000u) == 0) { + mask <<= 4; + shift += 4; + } + if ((mask & 0xc0000000u) == 0) { + mask <<= 2; + shift += 2; + } + if ((mask & 0x80000000u) == 0) + shift += 1; + + return shift; +} + +// Returns true if `image` is in RGB format. +bool IsXImageRGBFormat(XImage* image) { + return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && + image->green_mask == 0xff00 && image->blue_mask == 0xff; +} + +// We expose two forms of blitting to handle variations in the pixel format. +// In FastBlit(), the operation is effectively a memcpy. +void FastBlit(XImage* x_image, + uint8_t* src_pos, + const DesktopRect& rect, + DesktopFrame* frame) { + RTC_DCHECK_LE(frame->top_left().x(), rect.left()); + RTC_DCHECK_LE(frame->top_left().y(), rect.top()); + + int src_stride = x_image->bytes_per_line; + int dst_x = rect.left() - frame->top_left().x(); + int dst_y = rect.top() - frame->top_left().y(); + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + + int height = rect.height(); + int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel; + for (int y = 0; y < height; ++y) { + memcpy(dst_pos, src_pos, row_bytes); + src_pos += src_stride; + dst_pos += frame->stride(); + } +} + +void SlowBlit(XImage* x_image, + uint8_t* src_pos, + const DesktopRect& rect, + DesktopFrame* frame) { + RTC_DCHECK_LE(frame->top_left().x(), rect.left()); + RTC_DCHECK_LE(frame->top_left().y(), rect.top()); + + int src_stride = x_image->bytes_per_line; + int dst_x = rect.left() - frame->top_left().x(); + int dst_y = rect.top() - frame->top_left().y(); + int width = rect.width(), height = rect.height(); + + uint32_t red_mask = x_image->red_mask; + uint32_t green_mask = x_image->red_mask; + uint32_t blue_mask = x_image->blue_mask; + + uint32_t red_shift = MaskToShift(red_mask); + uint32_t green_shift = MaskToShift(green_mask); + uint32_t blue_shift = MaskToShift(blue_mask); + + int bits_per_pixel = x_image->bits_per_pixel; + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + // TODO(hclam): Optimize, perhaps using MMX code or by converting to + // YUV directly. + // TODO(sergeyu): This code doesn't handle XImage byte order properly and + // won't work with 24bpp images. Fix it. + for (int y = 0; y < height; y++) { + uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos); + uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos); + uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos); + for (int x = 0; x < width; x++) { + // Dereference through an appropriately-aligned pointer. + uint32_t pixel; + if (bits_per_pixel == 32) { + pixel = src_pos_32[x]; + } else if (bits_per_pixel == 16) { + pixel = src_pos_16[x]; + } else { + pixel = src_pos[x]; + } + uint32_t r = (pixel & red_mask) << red_shift; + uint32_t g = (pixel & green_mask) << green_shift; + uint32_t b = (pixel & blue_mask) << blue_shift; + // Write as 32-bit RGB. + dst_pos_32[x] = + ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff); + } + dst_pos += frame->stride(); + src_pos += src_stride; + } +} + +} // namespace + +XServerPixelBuffer::XServerPixelBuffer() {} + +XServerPixelBuffer::~XServerPixelBuffer() { + Release(); +} + +void XServerPixelBuffer::Release() { + if (x_image_) { + XDestroyImage(x_image_); + x_image_ = nullptr; + } + if (x_shm_image_) { + XDestroyImage(x_shm_image_); + x_shm_image_ = nullptr; + } + if (shm_pixmap_) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + } + if (shm_gc_) { + XFreeGC(display_, shm_gc_); + shm_gc_ = nullptr; + } + + ReleaseSharedMemorySegment(); + + window_ = 0; +} + +void XServerPixelBuffer::ReleaseSharedMemorySegment() { + if (!shm_segment_info_) + return; + if (shm_segment_info_->shmaddr != nullptr) + shmdt(shm_segment_info_->shmaddr); + if (shm_segment_info_->shmid != -1) + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + delete shm_segment_info_; + shm_segment_info_ = nullptr; +} + +bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) { + Release(); + display_ = cache->display(); + + XWindowAttributes attributes; + if (!GetWindowRect(display_, window, &window_rect_, &attributes)) { + return false; + } + + if (cache->IccProfile() != None) { + // `window` is the root window when doing screen capture. + XWindowProperty<uint8_t> icc_profile_property(cache->display(), window, + cache->IccProfile()); + if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) { + icc_profile_ = std::vector<uint8_t>( + icc_profile_property.data(), + icc_profile_property.data() + icc_profile_property.size()); + } else { + RTC_LOG(LS_WARNING) << "Failed to get icc profile"; + } + } + + window_ = window; + InitShm(attributes); + + return true; +} + +void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) { + Visual* default_visual = attributes.visual; + int default_depth = attributes.depth; + + int major, minor; + Bool have_pixmaps; + if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) { + // Shared memory not supported. CaptureRect will use the XImage API instead. + return; + } + + bool using_shm = false; + shm_segment_info_ = new XShmSegmentInfo; + shm_segment_info_->shmid = -1; + shm_segment_info_->shmaddr = nullptr; + shm_segment_info_->readOnly = False; + x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth, + ZPixmap, 0, shm_segment_info_, + window_rect_.width(), window_rect_.height()); + if (x_shm_image_) { + shm_segment_info_->shmid = + shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height, + IPC_CREAT | 0600); + if (shm_segment_info_->shmid != -1) { + void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0); + if (shmat_result != reinterpret_cast<void*>(-1)) { + shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result); + x_shm_image_->data = shm_segment_info_->shmaddr; + + XErrorTrap error_trap(display_); + using_shm = XShmAttach(display_, shm_segment_info_); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) + using_shm = false; + if (using_shm) { + RTC_LOG(LS_VERBOSE) + << "Using X shared memory segment " << shm_segment_info_->shmid; + } + } + } else { + RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. " + "Performance may be degraded."; + } + } + + if (!using_shm) { + RTC_LOG(LS_WARNING) + << "Not using shared memory. Performance may be degraded."; + ReleaseSharedMemorySegment(); + return; + } + + if (have_pixmaps) + have_pixmaps = InitPixmaps(default_depth); + + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + shm_segment_info_->shmid = -1; + + RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "." + << minor << " with" << (have_pixmaps ? "" : "out") + << " pixmaps."; +} + +bool XServerPixelBuffer::InitPixmaps(int depth) { + if (XShmPixmapFormat(display_) != ZPixmap) + return false; + + { + XErrorTrap error_trap(display_); + shm_pixmap_ = XShmCreatePixmap( + display_, window_, shm_segment_info_->shmaddr, shm_segment_info_, + window_rect_.width(), window_rect_.height(), depth); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) { + // `shm_pixmap_` is not not valid because the request was not processed + // by the X Server, so zero it. + shm_pixmap_ = 0; + return false; + } + } + + { + XErrorTrap error_trap(display_); + XGCValues shm_gc_values; + shm_gc_values.subwindow_mode = IncludeInferiors; + shm_gc_values.graphics_exposures = False; + shm_gc_ = XCreateGC(display_, window_, + GCSubwindowMode | GCGraphicsExposures, &shm_gc_values); + XSync(display_, False); + if (error_trap.GetLastErrorAndDisable() != 0) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + shm_gc_ = 0; // See shm_pixmap_ comment above. + return false; + } + } + + return true; +} + +bool XServerPixelBuffer::IsWindowValid() const { + XWindowAttributes attributes; + { + XErrorTrap error_trap(display_); + if (!XGetWindowAttributes(display_, window_, &attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + return true; +} + +void XServerPixelBuffer::Synchronize() { + if (shm_segment_info_ && !shm_pixmap_) { + // XShmGetImage can fail if the display is being reconfigured. + XErrorTrap error_trap(display_); + // XShmGetImage fails if the window is partially out of screen. + xshm_get_image_succeeded_ = + XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes); + } +} + +bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect, + DesktopFrame* frame) { + RTC_DCHECK_LE(rect.right(), window_rect_.width()); + RTC_DCHECK_LE(rect.bottom(), window_rect_.height()); + + XImage* image; + uint8_t* data; + + if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) { + if (shm_pixmap_) { + XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(), + rect.top(), rect.width(), rect.height(), rect.left(), + rect.top()); + XSync(display_, False); + } + + image = x_shm_image_; + data = reinterpret_cast<uint8_t*>(image->data) + + rect.top() * image->bytes_per_line + + rect.left() * image->bits_per_pixel / 8; + + } else { + if (x_image_) + XDestroyImage(x_image_); + x_image_ = XGetImage(display_, window_, rect.left(), rect.top(), + rect.width(), rect.height(), AllPlanes, ZPixmap); + if (!x_image_) + return false; + + image = x_image_; + data = reinterpret_cast<uint8_t*>(image->data); + } + + if (IsXImageRGBFormat(image)) { + FastBlit(image, data, rect, frame); + } else { + SlowBlit(image, data, rect, frame); + } + + if (!icc_profile_.empty()) + frame->set_icc_profile(icc_profile_); + + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h new file mode 100644 index 0000000000..38af3a3e76 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_server_pixel_buffer.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Don't include this file in any .h files because it pulls in some X headers. + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_ + +#include <X11/Xutil.h> +#include <X11/extensions/XShm.h> + +#include <memory> +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class DesktopFrame; +class XAtomCache; + +// A class to allow the X server's pixel buffer to be accessed as efficiently +// as possible. +class XServerPixelBuffer { + public: + XServerPixelBuffer(); + ~XServerPixelBuffer(); + + XServerPixelBuffer(const XServerPixelBuffer&) = delete; + XServerPixelBuffer& operator=(const XServerPixelBuffer&) = delete; + + void Release(); + + // Allocate (or reallocate) the pixel buffer for `window`. Returns false in + // case of an error (e.g. window doesn't exist). + bool Init(XAtomCache* cache, Window window); + + bool is_initialized() { return window_ != 0; } + + // Returns the size of the window the buffer was initialized for. + DesktopSize window_size() { return window_rect_.size(); } + + // Returns the rectangle of the window the buffer was initialized for. + const DesktopRect& window_rect() { return window_rect_; } + + // Returns true if the window can be found. + bool IsWindowValid() const; + + // If shared memory is being used without pixmaps, synchronize this pixel + // buffer with the root window contents (otherwise, this is a no-op). + // This is to avoid doing a full-screen capture for each individual + // rectangle in the capture list, when it only needs to be done once at the + // beginning. + void Synchronize(); + + // Capture the specified rectangle and stores it in the `frame`. In the case + // where the full-screen data is captured by Synchronize(), this simply + // returns the pointer without doing any more work. The caller must ensure + // that `rect` is not larger than window_size(). + bool CaptureRect(const DesktopRect& rect, DesktopFrame* frame); + + private: + void ReleaseSharedMemorySegment(); + + void InitShm(const XWindowAttributes& attributes); + bool InitPixmaps(int depth); + + Display* display_ = nullptr; + Window window_ = 0; + DesktopRect window_rect_; + XImage* x_image_ = nullptr; + XShmSegmentInfo* shm_segment_info_ = nullptr; + XImage* x_shm_image_ = nullptr; + Pixmap shm_pixmap_ = 0; + GC shm_gc_ = nullptr; + bool xshm_get_image_succeeded_ = false; + std::vector<uint8_t> icc_profile_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc new file mode 100644 index 0000000000..5e16dac404 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.cc @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/x11/x_window_property.h" + +namespace webrtc { + +XWindowPropertyBase::XWindowPropertyBase(Display* display, + Window window, + Atom property, + int expected_size) { + const int kBitsPerByte = 8; + Atom actual_type; + int actual_format; + unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty + int status = XGetWindowProperty(display, window, property, 0L, ~0L, False, + AnyPropertyType, &actual_type, &actual_format, + &size_, &bytes_after, &data_); + if (status != Success) { + data_ = nullptr; + return; + } + if ((expected_size * kBitsPerByte) != actual_format) { + size_ = 0; + return; + } + + is_valid_ = true; +} + +XWindowPropertyBase::~XWindowPropertyBase() { + if (data_) + XFree(data_); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h new file mode 100644 index 0000000000..28dfb97311 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/linux/x11/x_window_property.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_ + +#include <X11/X.h> +#include <X11/Xlib.h> + +namespace webrtc { + +class XWindowPropertyBase { + public: + XWindowPropertyBase(Display* display, + Window window, + Atom property, + int expected_size); + virtual ~XWindowPropertyBase(); + + XWindowPropertyBase(const XWindowPropertyBase&) = delete; + XWindowPropertyBase& operator=(const XWindowPropertyBase&) = delete; + + // True if we got properly value successfully. + bool is_valid() const { return is_valid_; } + + // Size and value of the property. + size_t size() const { return size_; } + + protected: + unsigned char* data_ = nullptr; + + private: + bool is_valid_ = false; + unsigned long size_ = 0; // NOLINT: type required by XGetWindowProperty +}; + +// Convenience wrapper for XGetWindowProperty() results. +template <class PropertyType> +class XWindowProperty : public XWindowPropertyBase { + public: + XWindowProperty(Display* display, const Window window, const Atom property) + : XWindowPropertyBase(display, window, property, sizeof(PropertyType)) {} + ~XWindowProperty() override = default; + + XWindowProperty(const XWindowProperty&) = delete; + XWindowProperty& operator=(const XWindowProperty&) = delete; + + const PropertyType* data() const { + return reinterpret_cast<PropertyType*>(data_); + } + PropertyType* data() { return reinterpret_cast<PropertyType*>(data_); } +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h new file mode 100644 index 0000000000..2ad5474e44 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// Describes the configuration of a specific display. +struct MacDisplayConfiguration { + MacDisplayConfiguration(); + MacDisplayConfiguration(const MacDisplayConfiguration& other); + MacDisplayConfiguration(MacDisplayConfiguration&& other); + ~MacDisplayConfiguration(); + + MacDisplayConfiguration& operator=(const MacDisplayConfiguration& other); + MacDisplayConfiguration& operator=(MacDisplayConfiguration&& other); + + // Cocoa identifier for this display. + CGDirectDisplayID id = 0; + + // Bounds of this display in Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Bounds of this display in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale = 1.0f; + + // Display type, built-in or external. + bool is_builtin; +}; + +typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations; + +// Describes the configuration of the whole desktop. +struct RTC_EXPORT MacDesktopConfiguration { + // Used to request bottom-up or top-down coordinates. + enum Origin { BottomLeftOrigin, TopLeftOrigin }; + + MacDesktopConfiguration(); + MacDesktopConfiguration(const MacDesktopConfiguration& other); + MacDesktopConfiguration(MacDesktopConfiguration&& other); + ~MacDesktopConfiguration(); + + MacDesktopConfiguration& operator=(const MacDesktopConfiguration& other); + MacDesktopConfiguration& operator=(MacDesktopConfiguration&& other); + + // Returns the desktop & display configurations. + // If BottomLeftOrigin is used, the output is in Cocoa-style "bottom-up" + // (the origin is the bottom-left of the primary monitor, and coordinates + // increase as you move up the screen). Otherwise, the configuration will be + // converted to follow top-left coordinate system as Windows and X11. + static MacDesktopConfiguration GetCurrent(Origin origin); + + // Returns true if the given desktop configuration equals this one. + bool Equals(const MacDesktopConfiguration& other); + + // If `id` corresponds to the built-in display, return its configuration, + // otherwise return the configuration for the display with the specified id, + // or nullptr if no such display exists. + const MacDisplayConfiguration* FindDisplayConfigurationById( + CGDirectDisplayID id); + + // Bounds of the desktop excluding monitors with DPI settings different from + // the main monitor. In Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Same as bounds, but expressed in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale = 1.0f; + + // Configurations of the displays making up the desktop area. + MacDisplayConfigurations displays; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm new file mode 100644 index 0000000000..3b888c37c2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration.mm @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_configuration.h" + +#include <math.h> +#include <algorithm> +#include <Cocoa/Cocoa.h> + +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) { + return DesktopRect::MakeLTRB( + static_cast<int>(floor(ns_rect.origin.x)), + static_cast<int>(floor(ns_rect.origin.y)), + static_cast<int>(ceil(ns_rect.origin.x + ns_rect.size.width)), + static_cast<int>(ceil(ns_rect.origin.y + ns_rect.size.height))); +} + +// Inverts the position of `rect` from bottom-up coordinates to top-down, +// relative to `bounds`. +void InvertRectYOrigin(const DesktopRect& bounds, + DesktopRect* rect) { + RTC_DCHECK_EQ(bounds.top(), 0); + *rect = DesktopRect::MakeXYWH( + rect->left(), bounds.bottom() - rect->bottom(), + rect->width(), rect->height()); +} + +MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) { + MacDisplayConfiguration display_config; + + // Fetch the NSScreenNumber, which is also the CGDirectDisplayID. + NSDictionary* device_description = [screen deviceDescription]; + display_config.id = static_cast<CGDirectDisplayID>( + [[device_description objectForKey:@"NSScreenNumber"] intValue]); + + // Determine the display's logical & physical dimensions. + NSRect ns_bounds = [screen frame]; + display_config.bounds = NSRectToDesktopRect(ns_bounds); + + // If the host is running Mac OS X 10.7+ or later, query the scaling factor + // between logical and physical (aka "backing") pixels, otherwise assume 1:1. + if ([screen respondsToSelector:@selector(backingScaleFactor)] && + [screen respondsToSelector:@selector(convertRectToBacking:)]) { + display_config.dip_to_pixel_scale = [screen backingScaleFactor]; + NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds]; + display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds); + } else { + display_config.pixel_bounds = display_config.bounds; + } + + // Determine if the display is built-in or external. + display_config.is_builtin = CGDisplayIsBuiltin(display_config.id); + + return display_config; +} + +} // namespace + +MacDisplayConfiguration::MacDisplayConfiguration() = default; +MacDisplayConfiguration::MacDisplayConfiguration( + const MacDisplayConfiguration& other) = default; +MacDisplayConfiguration::MacDisplayConfiguration( + MacDisplayConfiguration&& other) = default; +MacDisplayConfiguration::~MacDisplayConfiguration() = default; + +MacDisplayConfiguration& MacDisplayConfiguration::operator=( + const MacDisplayConfiguration& other) = default; +MacDisplayConfiguration& MacDisplayConfiguration::operator=( + MacDisplayConfiguration&& other) = default; + +MacDesktopConfiguration::MacDesktopConfiguration() = default; +MacDesktopConfiguration::MacDesktopConfiguration( + const MacDesktopConfiguration& other) = default; +MacDesktopConfiguration::MacDesktopConfiguration( + MacDesktopConfiguration&& other) = default; +MacDesktopConfiguration::~MacDesktopConfiguration() = default; + +MacDesktopConfiguration& MacDesktopConfiguration::operator=( + const MacDesktopConfiguration& other) = default; +MacDesktopConfiguration& MacDesktopConfiguration::operator=( + MacDesktopConfiguration&& other) = default; + +// static +MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) { + MacDesktopConfiguration desktop_config; + + NSArray* screens = [NSScreen screens]; + RTC_DCHECK(screens); + + // Iterator over the monitors, adding the primary monitor and monitors whose + // DPI match that of the primary monitor. + for (NSUInteger i = 0; i < [screens count]; ++i) { + MacDisplayConfiguration display_config = + GetConfigurationForScreen([screens objectAtIndex: i]); + + if (i == 0) + desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale; + + // Cocoa uses bottom-up coordinates, so if the caller wants top-down then + // we need to invert the positions of secondary monitors relative to the + // primary one (the primary monitor's position is (0,0) in both systems). + if (i > 0 && origin == TopLeftOrigin) { + InvertRectYOrigin(desktop_config.displays[0].bounds, + &display_config.bounds); + // `display_bounds` is density dependent, so we need to convert the + // primay monitor's position into the secondary monitor's density context. + float scaling_factor = display_config.dip_to_pixel_scale / + desktop_config.displays[0].dip_to_pixel_scale; + DesktopRect primary_bounds = DesktopRect::MakeLTRB( + desktop_config.displays[0].pixel_bounds.left() * scaling_factor, + desktop_config.displays[0].pixel_bounds.top() * scaling_factor, + desktop_config.displays[0].pixel_bounds.right() * scaling_factor, + desktop_config.displays[0].pixel_bounds.bottom() * scaling_factor); + InvertRectYOrigin(primary_bounds, &display_config.pixel_bounds); + } + + // Add the display to the configuration. + desktop_config.displays.push_back(display_config); + + // Update the desktop bounds to account for this display, unless the current + // display uses different DPI settings. + if (display_config.dip_to_pixel_scale == + desktop_config.dip_to_pixel_scale) { + desktop_config.bounds.UnionWith(display_config.bounds); + desktop_config.pixel_bounds.UnionWith(display_config.pixel_bounds); + } + } + + return desktop_config; +} + +// For convenience of comparing MacDisplayConfigurations in +// MacDesktopConfiguration::Equals. +bool operator==(const MacDisplayConfiguration& left, + const MacDisplayConfiguration& right) { + return left.id == right.id && + left.bounds.equals(right.bounds) && + left.pixel_bounds.equals(right.pixel_bounds) && + left.dip_to_pixel_scale == right.dip_to_pixel_scale; +} + +bool MacDesktopConfiguration::Equals(const MacDesktopConfiguration& other) { + return bounds.equals(other.bounds) && + pixel_bounds.equals(other.pixel_bounds) && + dip_to_pixel_scale == other.dip_to_pixel_scale && + displays == other.displays; +} + +const MacDisplayConfiguration* +MacDesktopConfiguration::FindDisplayConfigurationById( + CGDirectDisplayID id) { + bool is_builtin = CGDisplayIsBuiltin(id); + for (MacDisplayConfigurations::const_iterator it = displays.begin(); + it != displays.end(); ++it) { + // The MBP having both discrete and integrated graphic cards will do + // automate graphics switching by default. When it switches from discrete to + // integrated one, the current display ID of the built-in display will + // change and this will cause screen capture stops. + // So make screen capture of built-in display continuing even if its display + // ID is changed. + if ((is_builtin && it->is_builtin) || (!is_builtin && it->id == id)) return &(*it); + } + return NULL; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc new file mode 100644 index 0000000000..048a679ecc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" + +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +DesktopConfigurationMonitor::DesktopConfigurationMonitor() { + CGError err = CGDisplayRegisterReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + RTC_LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; + MutexLock lock(&desktop_configuration_lock_); + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); +} + +DesktopConfigurationMonitor::~DesktopConfigurationMonitor() { + CGError err = CGDisplayRemoveReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + RTC_LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; +} + +MacDesktopConfiguration DesktopConfigurationMonitor::desktop_configuration() { + MutexLock lock(&desktop_configuration_lock_); + return desktop_configuration_; +} + +// static +// This method may be called on any system thread. +void DesktopConfigurationMonitor::DisplaysReconfiguredCallback( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_parameter) { + DesktopConfigurationMonitor* monitor = + reinterpret_cast<DesktopConfigurationMonitor*>(user_parameter); + monitor->DisplaysReconfigured(display, flags); +} + +void DesktopConfigurationMonitor::DisplaysReconfigured( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags) { + TRACE_EVENT0("webrtc", "DesktopConfigurationMonitor::DisplaysReconfigured"); + RTC_LOG(LS_INFO) << "DisplaysReconfigured: " + "DisplayID " + << display << "; ChangeSummaryFlags " << flags; + + if (flags & kCGDisplayBeginConfigurationFlag) { + reconfiguring_displays_.insert(display); + return; + } + + reconfiguring_displays_.erase(display); + if (reconfiguring_displays_.empty()) { + MutexLock lock(&desktop_configuration_lock_); + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h new file mode 100644 index 0000000000..747295a538 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <memory> +#include <set> + +#include "api/ref_counted_base.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +// The class provides functions to synchronize capturing and display +// reconfiguring across threads, and the up-to-date MacDesktopConfiguration. +class DesktopConfigurationMonitor final + : public rtc::RefCountedNonVirtual<DesktopConfigurationMonitor> { + public: + DesktopConfigurationMonitor(); + ~DesktopConfigurationMonitor(); + + DesktopConfigurationMonitor(const DesktopConfigurationMonitor&) = delete; + DesktopConfigurationMonitor& operator=(const DesktopConfigurationMonitor&) = + delete; + + // Returns the current desktop configuration. + MacDesktopConfiguration desktop_configuration(); + + private: + static void DisplaysReconfiguredCallback(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_parameter); + void DisplaysReconfigured(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags); + + Mutex desktop_configuration_lock_; + MacDesktopConfiguration desktop_configuration_ + RTC_GUARDED_BY(&desktop_configuration_lock_); + std::set<CGDirectDisplayID> reconfiguring_displays_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h new file mode 100644 index 0000000000..d6279f9b36 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ + +#include <CoreGraphics/CoreGraphics.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameCGImage final : public DesktopFrame { + public: + // Create an image containing a snapshot of the display at the time this is + // being called. + static std::unique_ptr<DesktopFrameCGImage> CreateForDisplay( + CGDirectDisplayID display_id); + + // Create an image containing a snaphot of the given window at the time this + // is being called. This also works when the window is overlapped or in + // another workspace. + static std::unique_ptr<DesktopFrameCGImage> CreateForWindow( + CGWindowID window_id); + + ~DesktopFrameCGImage() override; + + DesktopFrameCGImage(const DesktopFrameCGImage&) = delete; + DesktopFrameCGImage& operator=(const DesktopFrameCGImage&) = delete; + + private: + static std::unique_ptr<DesktopFrameCGImage> CreateFromCGImage( + rtc::ScopedCFTypeRef<CGImageRef> cg_image); + + // This constructor expects `cg_image` to hold a non-null CGImageRef. + DesktopFrameCGImage(DesktopSize size, + int stride, + uint8_t* data, + rtc::ScopedCFTypeRef<CGImageRef> cg_image, + rtc::ScopedCFTypeRef<CFDataRef> cg_data); + + const rtc::ScopedCFTypeRef<CGImageRef> cg_image_; + const rtc::ScopedCFTypeRef<CFDataRef> cg_data_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm new file mode 100644 index 0000000000..0fb69b272d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_cgimage.mm @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" + +#include <AvailabilityMacros.h> + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForDisplay( + CGDirectDisplayID display_id) { + // Create an image containing a snapshot of the display. + rtc::ScopedCFTypeRef<CGImageRef> cg_image(CGDisplayCreateImage(display_id)); + if (!cg_image) { + return nullptr; + } + + return DesktopFrameCGImage::CreateFromCGImage(cg_image); +} + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForWindow(CGWindowID window_id) { + rtc::ScopedCFTypeRef<CGImageRef> cg_image( + CGWindowListCreateImage(CGRectNull, + kCGWindowListOptionIncludingWindow, + window_id, + kCGWindowImageBoundsIgnoreFraming)); + if (!cg_image) { + return nullptr; + } + + return DesktopFrameCGImage::CreateFromCGImage(cg_image); +} + +// static +std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateFromCGImage( + rtc::ScopedCFTypeRef<CGImageRef> cg_image) { + // Verify that the image has 32-bit depth. + int bits_per_pixel = CGImageGetBitsPerPixel(cg_image.get()); + if (bits_per_pixel / 8 != DesktopFrame::kBytesPerPixel) { + RTC_LOG(LS_ERROR) << "CGDisplayCreateImage() returned imaged with " << bits_per_pixel + << " bits per pixel. Only 32-bit depth is supported."; + return nullptr; + } + + // Request access to the raw pixel data via the image's DataProvider. + CGDataProviderRef cg_provider = CGImageGetDataProvider(cg_image.get()); + RTC_DCHECK(cg_provider); + + // CGDataProviderCopyData returns a new data object containing a copy of the provider’s + // data. + rtc::ScopedCFTypeRef<CFDataRef> cg_data(CGDataProviderCopyData(cg_provider)); + RTC_DCHECK(cg_data); + + // CFDataGetBytePtr returns a read-only pointer to the bytes of a CFData object. + uint8_t* data = const_cast<uint8_t*>(CFDataGetBytePtr(cg_data.get())); + RTC_DCHECK(data); + + DesktopSize size(CGImageGetWidth(cg_image.get()), CGImageGetHeight(cg_image.get())); + int stride = CGImageGetBytesPerRow(cg_image.get()); + + std::unique_ptr<DesktopFrameCGImage> frame( + new DesktopFrameCGImage(size, stride, data, cg_image, cg_data)); + + CGColorSpaceRef cg_color_space = CGImageGetColorSpace(cg_image.get()); + if (cg_color_space) { +#if !defined(MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13 + rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCProfile(cg_color_space)); +#else + rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCData(cg_color_space)); +#endif + if (cf_icc_profile) { + const uint8_t* data_as_byte = + reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(cf_icc_profile.get())); + const size_t data_size = CFDataGetLength(cf_icc_profile.get()); + if (data_as_byte && data_size > 0) { + frame->set_icc_profile(std::vector<uint8_t>(data_as_byte, data_as_byte + data_size)); + } + } + } + + return frame; +} + +DesktopFrameCGImage::DesktopFrameCGImage(DesktopSize size, + int stride, + uint8_t* data, + rtc::ScopedCFTypeRef<CGImageRef> cg_image, + rtc::ScopedCFTypeRef<CFDataRef> cg_data) + : DesktopFrame(size, stride, data, nullptr), cg_image_(cg_image), cg_data_(cg_data) { + RTC_DCHECK(cg_image_); + RTC_DCHECK(cg_data_); +} + +DesktopFrameCGImage::~DesktopFrameCGImage() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h new file mode 100644 index 0000000000..73da0f693c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ + +#include <CoreGraphics/CoreGraphics.h> +#include <IOSurface/IOSurface.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameIOSurface final : public DesktopFrame { + public: + // Lock an IOSurfaceRef containing a snapshot of a display. Return NULL if + // failed to lock. + static std::unique_ptr<DesktopFrameIOSurface> Wrap( + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + ~DesktopFrameIOSurface() override; + + DesktopFrameIOSurface(const DesktopFrameIOSurface&) = delete; + DesktopFrameIOSurface& operator=(const DesktopFrameIOSurface&) = delete; + + private: + // This constructor expects `io_surface` to hold a non-null IOSurfaceRef. + explicit DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + const rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm new file mode 100644 index 0000000000..b59b319db9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_iosurface.mm @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_iosurface.h" + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopFrameIOSurface> DesktopFrameIOSurface::Wrap( + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) { + if (!io_surface) { + return nullptr; + } + + IOSurfaceIncrementUseCount(io_surface.get()); + IOReturn status = IOSurfaceLock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr); + if (status != kIOReturnSuccess) { + RTC_LOG(LS_ERROR) << "Failed to lock the IOSurface with status " << status; + IOSurfaceDecrementUseCount(io_surface.get()); + return nullptr; + } + + // Verify that the image has 32-bit depth. + int bytes_per_pixel = IOSurfaceGetBytesPerElement(io_surface.get()); + if (bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + RTC_LOG(LS_ERROR) << "CGDisplayStream handler returned IOSurface with " << (8 * bytes_per_pixel) + << " bits per pixel. Only 32-bit depth is supported."; + IOSurfaceUnlock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr); + IOSurfaceDecrementUseCount(io_surface.get()); + return nullptr; + } + + return std::unique_ptr<DesktopFrameIOSurface>(new DesktopFrameIOSurface(io_surface)); +} + +DesktopFrameIOSurface::DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) + : DesktopFrame( + DesktopSize(IOSurfaceGetWidth(io_surface.get()), IOSurfaceGetHeight(io_surface.get())), + IOSurfaceGetBytesPerRow(io_surface.get()), + static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface.get())), + nullptr), + io_surface_(io_surface) { + RTC_DCHECK(io_surface_); +} + +DesktopFrameIOSurface::~DesktopFrameIOSurface() { + IOSurfaceUnlock(io_surface_.get(), kIOSurfaceLockReadOnly, nullptr); + IOSurfaceDecrementUseCount(io_surface_.get()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h new file mode 100644 index 0000000000..aad28d2f30 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ + +#include <CoreGraphics/CoreGraphics.h> +#include <IOSurface/IOSurface.h> + +#include <map> +#include <memory> + +#include "api/sequence_checker.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +class DesktopFrameProvider { + public: + explicit DesktopFrameProvider(bool allow_iosurface); + ~DesktopFrameProvider(); + + DesktopFrameProvider(const DesktopFrameProvider&) = delete; + DesktopFrameProvider& operator=(const DesktopFrameProvider&) = delete; + + // The caller takes ownership of the returned desktop frame. Otherwise + // returns null if `display_id` is invalid or not ready. Note that this + // function does not remove the frame from the internal container. Caller + // has to call the Release function. + std::unique_ptr<DesktopFrame> TakeLatestFrameForDisplay( + CGDirectDisplayID display_id); + + // OS sends the latest IOSurfaceRef through + // CGDisplayStreamFrameAvailableHandler callback; we store it here. + void InvalidateIOSurface(CGDirectDisplayID display_id, + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface); + + // Expected to be called before stopping the CGDisplayStreamRef streams. + void Release(); + + private: + SequenceChecker thread_checker_; + const bool allow_iosurface_; + + // Most recent IOSurface that contains a capture of matching display. + std::map<CGDirectDisplayID, std::unique_ptr<SharedDesktopFrame>> io_surfaces_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm new file mode 100644 index 0000000000..009504a22b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/desktop_frame_provider.mm @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/desktop_frame_provider.h" + +#include <utility> + +#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" +#include "modules/desktop_capture/mac/desktop_frame_iosurface.h" + +namespace webrtc { + +DesktopFrameProvider::DesktopFrameProvider(bool allow_iosurface) + : allow_iosurface_(allow_iosurface) { + thread_checker_.Detach(); +} + +DesktopFrameProvider::~DesktopFrameProvider() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + Release(); +} + +std::unique_ptr<DesktopFrame> DesktopFrameProvider::TakeLatestFrameForDisplay( + CGDirectDisplayID display_id) { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_ || !io_surfaces_[display_id]) { + // Regenerate a snapshot. If iosurface is on it will be empty until the + // stream handler is called. + return DesktopFrameCGImage::CreateForDisplay(display_id); + } + + return io_surfaces_[display_id]->Share(); +} + +void DesktopFrameProvider::InvalidateIOSurface(CGDirectDisplayID display_id, + rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_) { + return; + } + + std::unique_ptr<DesktopFrameIOSurface> desktop_frame_iosurface = + DesktopFrameIOSurface::Wrap(io_surface); + + io_surfaces_[display_id] = desktop_frame_iosurface ? + SharedDesktopFrame::Wrap(std::move(desktop_frame_iosurface)) : + nullptr; +} + +void DesktopFrameProvider::Release() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + if (!allow_iosurface_) { + return; + } + + io_surfaces_.clear(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc new file mode 100644 index 0000000000..45cd3223d2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" + +#include <libproc.h> + +#include <algorithm> +#include <functional> +#include <string> + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "api/function_view.h" +#include "modules/desktop_capture/mac/window_list_utils.h" + +namespace webrtc { +namespace { + +static constexpr const char* kPowerPointSlideShowTitles[] = { + "PowerPoint-Bildschirmpräsentation", + "Προβολή παρουσίασης PowerPoint", + "PowerPoint スライド ショー", + "PowerPoint Slide Show", + "PowerPoint 幻灯片放映", + "Presentación de PowerPoint", + "PowerPoint-slideshow", + "Presentazione di PowerPoint", + "Prezentácia programu PowerPoint", + "Apresentação do PowerPoint", + "PowerPoint-bildspel", + "Prezentace v aplikaci PowerPoint", + "PowerPoint 슬라이드 쇼", + "PowerPoint-lysbildefremvisning", + "PowerPoint-vetítés", + "PowerPoint Slayt Gösterisi", + "Pokaz slajdów programu PowerPoint", + "PowerPoint 投影片放映", + "Демонстрация PowerPoint", + "Diaporama PowerPoint", + "PowerPoint-diaesitys", + "Peragaan Slide PowerPoint", + "PowerPoint-diavoorstelling", + "การนำเสนอสไลด์ PowerPoint", + "Apresentação de slides do PowerPoint", + "הצגת שקופיות של PowerPoint", + "عرض شرائح في PowerPoint"}; + +class FullScreenMacApplicationHandler : public FullScreenApplicationHandler { + public: + using TitlePredicate = + std::function<bool(absl::string_view, absl::string_view)>; + + FullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId, + TitlePredicate title_predicate, + bool ignore_original_window) + : FullScreenApplicationHandler(sourceId), + title_predicate_(title_predicate), + owner_pid_(GetWindowOwnerPid(sourceId)), + ignore_original_window_(ignore_original_window) {} + + protected: + using CachePredicate = + rtc::FunctionView<bool(const DesktopCapturer::Source&)>; + + void InvalidateCacheIfNeeded(const DesktopCapturer::SourceList& source_list, + int64_t timestamp, + CachePredicate predicate) const { + if (timestamp != cache_timestamp_) { + cache_sources_.clear(); + std::copy_if(source_list.begin(), source_list.end(), + std::back_inserter(cache_sources_), predicate); + cache_timestamp_ = timestamp; + } + } + + WindowId FindFullScreenWindowWithSamePid( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const { + InvalidateCacheIfNeeded(source_list, timestamp, + [&](const DesktopCapturer::Source& src) { + return src.id != GetSourceId() && + GetWindowOwnerPid(src.id) == owner_pid_; + }); + if (cache_sources_.empty()) + return kCGNullWindowID; + + const auto original_window = GetSourceId(); + const std::string title = GetWindowTitle(original_window); + + // We can ignore any windows with empty titles cause regardless type of + // application it's impossible to verify that full screen window and + // original window are related to the same document. + if (title.empty()) + return kCGNullWindowID; + + MacDesktopConfiguration desktop_config = + MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + const auto it = std::find_if( + cache_sources_.begin(), cache_sources_.end(), + [&](const DesktopCapturer::Source& src) { + const std::string window_title = GetWindowTitle(src.id); + + if (window_title.empty()) + return false; + + if (title_predicate_ && !title_predicate_(title, window_title)) + return false; + + return IsWindowFullScreen(desktop_config, src.id); + }); + + return it != cache_sources_.end() ? it->id : 0; + } + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const override { + return !ignore_original_window_ && IsWindowOnScreen(GetSourceId()) + ? 0 + : FindFullScreenWindowWithSamePid(source_list, timestamp); + } + + protected: + const TitlePredicate title_predicate_; + const int owner_pid_; + const bool ignore_original_window_; + mutable int64_t cache_timestamp_ = 0; + mutable DesktopCapturer::SourceList cache_sources_; +}; + +bool equal_title_predicate(absl::string_view original_title, + absl::string_view title) { + return original_title == title; +} + +bool slide_show_title_predicate(absl::string_view original_title, + absl::string_view title) { + if (title.find(original_title) == absl::string_view::npos) + return false; + + for (const char* pp_slide_title : kPowerPointSlideShowTitles) { + if (absl::StartsWith(title, pp_slide_title)) + return true; + } + return false; +} + +class OpenOfficeApplicationHandler : public FullScreenMacApplicationHandler { + public: + OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId) + : FullScreenMacApplicationHandler(sourceId, nullptr, false) {} + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& source_list, + int64_t timestamp) const override { + InvalidateCacheIfNeeded(source_list, timestamp, + [&](const DesktopCapturer::Source& src) { + return GetWindowOwnerPid(src.id) == owner_pid_; + }); + + const auto original_window = GetSourceId(); + const std::string original_title = GetWindowTitle(original_window); + + // Check if we have only one document window, otherwise it's not possible + // to securely match a document window and a slide show window which has + // empty title. + if (std::any_of(cache_sources_.begin(), cache_sources_.end(), + [&original_title](const DesktopCapturer::Source& src) { + return src.title.length() && src.title != original_title; + })) { + return kCGNullWindowID; + } + + MacDesktopConfiguration desktop_config = + MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + // Looking for slide show window, + // it must be a full screen window with empty title + const auto slide_show_window = std::find_if( + cache_sources_.begin(), cache_sources_.end(), [&](const auto& src) { + return src.title.empty() && + IsWindowFullScreen(desktop_config, src.id); + }); + + if (slide_show_window == cache_sources_.end()) { + return kCGNullWindowID; + } + + return slide_show_window->id; + } +}; + +} // namespace + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId) { + std::unique_ptr<FullScreenApplicationHandler> result; + int pid = GetWindowOwnerPid(sourceId); + char buffer[PROC_PIDPATHINFO_MAXSIZE]; + int path_length = proc_pidpath(pid, buffer, sizeof(buffer)); + if (path_length > 0) { + const char* last_slash = strrchr(buffer, '/'); + const std::string name{last_slash ? last_slash + 1 : buffer}; + const std::string owner_name = GetWindowOwnerName(sourceId); + FullScreenMacApplicationHandler::TitlePredicate predicate = nullptr; + bool ignore_original_window = false; + if (name.find("Google Chrome") == 0 || name == "Chromium") { + predicate = equal_title_predicate; + } else if (name == "Microsoft PowerPoint") { + predicate = slide_show_title_predicate; + ignore_original_window = true; + } else if (name == "Keynote") { + predicate = equal_title_predicate; + } else if (owner_name == "OpenOffice") { + return std::make_unique<OpenOfficeApplicationHandler>(sourceId); + } + + if (predicate) { + result.reset(new FullScreenMacApplicationHandler(sourceId, predicate, + ignore_original_window)); + } + } + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h new file mode 100644 index 0000000000..f795a22030 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ + +#include <memory> +#include "modules/desktop_capture/full_screen_application_handler.h" + +namespace webrtc { + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_MAC_APPLICATION_HANDLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h new file mode 100644 index 0000000000..7be05cc639 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ + +#include <CoreGraphics/CoreGraphics.h> + +#include <memory> +#include <vector> + +#include "api/sequence_checker.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/desktop_frame_provider.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" + +namespace webrtc { + +class DisplayStreamManager; + +// A class to perform video frame capturing for mac. +class ScreenCapturerMac final : public DesktopCapturer { + public: + ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region, + bool allow_iosurface); + ~ScreenCapturerMac() override; + + ScreenCapturerMac(const ScreenCapturerMac&) = delete; + ScreenCapturerMac& operator=(const ScreenCapturerMac&) = delete; + + // TODO(julien.isorce): Remove Init() or make it private. + bool Init(); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + + private: + // Returns false if the selected screen is no longer valid. + bool CgBlit(const DesktopFrame& frame, const DesktopRegion& region); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + bool RegisterRefreshAndMoveHandlers(); + void UnregisterRefreshAndMoveHandlers(); + + void ScreenRefresh(CGDirectDisplayID display_id, + CGRectCount count, + const CGRect* rect_array, + DesktopVector display_origin, + IOSurfaceRef io_surface); + void ReleaseBuffers(); + + std::unique_ptr<DesktopFrame> CreateFrame(); + + const bool detect_updated_region_; + + Callback* callback_ = nullptr; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + // Current display configuration. + MacDesktopConfiguration desktop_config_; + + // Currently selected display, or 0 if the full desktop is selected. On OS X + // 10.6 and before, this is always 0. + CGDirectDisplayID current_display_ = 0; + + // The physical pixel bounds of the current screen. + DesktopRect screen_pixel_bounds_; + + // The dip to physical pixel scale of the current screen. + float dip_to_pixel_scale_ = 1.0f; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Contains an invalid region from the previous capture. + DesktopRegion last_invalid_region_; + + // Monitoring display reconfiguration. + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_; + + CGWindowID excluded_window_ = 0; + + // List of streams, one per screen. + std::vector<CGDisplayStreamRef> display_streams_; + + // Container holding latest state of the snapshot per displays. + DesktopFrameProvider desktop_frame_provider_; + + // Start, CaptureFrame and destructor have to called in the same thread. + SequenceChecker thread_checker_; + + // Used to force CaptureFrame to update it's screen configuration + // and reregister event handlers. This ensure that this + // occurs on the ScreenCapture thread. Read and written from + // both the VideoCapture thread and ScreenCapture thread. + // Protected by desktop_config_monitor_. + bool update_screen_configuration_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_SCREEN_CAPTURER_MAC_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm new file mode 100644 index 0000000000..cad0c5b65b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/screen_capturer_mac.mm @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <utility> + +#include "modules/desktop_capture/mac/screen_capturer_mac.h" + +#include "modules/desktop_capture/mac/desktop_frame_provider.h" +#include "modules/desktop_capture/mac/window_list_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "sdk/objc/helpers/scoped_cftyperef.h" + +namespace webrtc { + +namespace { + +// Scales all coordinates of a rect by a specified factor. +DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { + return DesktopRect::MakeLTRB(static_cast<int>(floor(rect.origin.x * scale)), + static_cast<int>(floor(rect.origin.y * scale)), + static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)), + static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale))); +} + +// Copy pixels in the `rect` from `src_place` to `dest_plane`. `rect` should be +// relative to the origin of `src_plane` and `dest_plane`. +void CopyRect(const uint8_t* src_plane, + int src_plane_stride, + uint8_t* dest_plane, + int dest_plane_stride, + int bytes_per_pixel, + const DesktopRect& rect) { + // Get the address of the starting point. + const int src_y_offset = src_plane_stride * rect.top(); + const int dest_y_offset = dest_plane_stride * rect.top(); + const int x_offset = bytes_per_pixel * rect.left(); + src_plane += src_y_offset + x_offset; + dest_plane += dest_y_offset + x_offset; + + // Copy pixels in the rectangle line by line. + const int bytes_per_line = bytes_per_pixel * rect.width(); + const int height = rect.height(); + for (int i = 0; i < height; ++i) { + memcpy(dest_plane, src_plane, bytes_per_line); + src_plane += src_plane_stride; + dest_plane += dest_plane_stride; + } +} + +// Returns an array of CGWindowID for all the on-screen windows except +// `window_to_exclude`, or NULL if the window is not found or it fails. The +// caller should release the returned CFArrayRef. +CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) { + if (!window_to_exclude) return nullptr; + + CFArrayRef all_windows = + CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + if (!all_windows) return nullptr; + + CFMutableArrayRef returned_array = + CFArrayCreateMutable(nullptr, CFArrayGetCount(all_windows), nullptr); + + bool found = false; + for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) { + CFDictionaryRef window = + reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(all_windows, i)); + + CGWindowID id = GetWindowId(window); + if (id == window_to_exclude) { + found = true; + continue; + } + CFArrayAppendValue(returned_array, reinterpret_cast<void*>(id)); + } + CFRelease(all_windows); + + if (!found) { + CFRelease(returned_array); + returned_array = nullptr; + } + return returned_array; +} + +// Returns the bounds of `window` in physical pixels, enlarged by a small amount +// on four edges to take account of the border/shadow effects. +DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, float dip_to_pixel_scale) { + // The amount of pixels to add to the actual window bounds to take into + // account of the border/shadow effects. + static const int kBorderEffectSize = 20; + CGRect rect; + CGWindowID ids[1]; + ids[0] = window; + + CFArrayRef window_id_array = + CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); + CFArrayRef window_array = CGWindowListCreateDescriptionFromArray(window_id_array); + + if (CFArrayGetCount(window_array) > 0) { + CFDictionaryRef win = + reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(window_array, 0)); + CFDictionaryRef bounds_ref = + reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(win, kCGWindowBounds)); + CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect); + } + + CFRelease(window_id_array); + CFRelease(window_array); + + rect.origin.x -= kBorderEffectSize; + rect.origin.y -= kBorderEffectSize; + rect.size.width += kBorderEffectSize * 2; + rect.size.height += kBorderEffectSize * 2; + // `rect` is in DIP, so convert to physical pixels. + return ScaleAndRoundCGRect(rect, dip_to_pixel_scale); +} + +// Create an image of the given region using the given `window_list`. +// `pixel_bounds` should be in the primary display's coordinate in physical +// pixels. +rtc::ScopedCFTypeRef<CGImageRef> CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds, + float dip_to_pixel_scale, + CFArrayRef window_list) { + CGRect window_bounds; + // The origin is in DIP while the size is in physical pixels. That's what + // CGWindowListCreateImageFromArray expects. + window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale; + window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale; + window_bounds.size.width = pixel_bounds.width(); + window_bounds.size.height = pixel_bounds.height(); + + return rtc::ScopedCFTypeRef<CGImageRef>( + CGWindowListCreateImageFromArray(window_bounds, window_list, kCGWindowImageDefault)); +} + +} // namespace + +ScreenCapturerMac::ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region, + bool allow_iosurface) + : detect_updated_region_(detect_updated_region), + desktop_config_monitor_(desktop_config_monitor), + desktop_frame_provider_(allow_iosurface) { + RTC_LOG(LS_INFO) << "Allow IOSurface: " << allow_iosurface; + thread_checker_.Detach(); +} + +ScreenCapturerMac::~ScreenCapturerMac() { + RTC_DCHECK(thread_checker_.IsCurrent()); + ReleaseBuffers(); + UnregisterRefreshAndMoveHandlers(); +} + +bool ScreenCapturerMac::Init() { + TRACE_EVENT0("webrtc", "ScreenCapturerMac::Init"); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + return true; +} + +void ScreenCapturerMac::ReleaseBuffers() { + // The buffers might be in use by the encoder, so don't delete them here. + // Instead, mark them as "needs update"; next time the buffers are used by + // the capturer, they will be recreated if necessary. + queue_.Reset(); +} + +void ScreenCapturerMac::Start(Callback* callback) { + RTC_DCHECK(thread_checker_.IsCurrent()); + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + TRACE_EVENT_INSTANT1( + "webrtc", "ScreenCapturermac::Start", "target display id ", current_display_); + + callback_ = callback; + update_screen_configuration_ = false; + // Start and operate CGDisplayStream handler all from capture thread. + if (!RegisterRefreshAndMoveHandlers()) { + RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + ScreenConfigurationChanged(); +} + +void ScreenCapturerMac::CaptureFrame() { + RTC_DCHECK(thread_checker_.IsCurrent()); + TRACE_EVENT0("webrtc", "creenCapturerMac::CaptureFrame"); + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; + } + + MacDesktopConfiguration new_config = desktop_config_monitor_->desktop_configuration(); + if (update_screen_configuration_ || !desktop_config_.Equals(new_config)) { + update_screen_configuration_ = false; + desktop_config_ = new_config; + // If the display configuraiton has changed then refresh capturer data + // structures. Occasionally, the refresh and move handlers are lost when + // the screen mode changes, so re-register them here. + UnregisterRefreshAndMoveHandlers(); + if (!RegisterRefreshAndMoveHandlers()) { + RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + ScreenConfigurationChanged(); + } + + // When screen is zoomed in/out, OSX only updates the part of Rects currently + // displayed on screen, with relative location to current top-left on screen. + // This will cause problems when we copy the dirty regions to the captured + // image. So we invalidate the whole screen to copy all the screen contents. + // With CGI method, the zooming will be ignored and the whole screen contents + // will be captured as before. + // With IOSurface method, the zoomed screen contents will be captured. + if (UAZoomEnabled()) { + helper_.InvalidateScreen(screen_pixel_bounds_.size()); + } + + DesktopRegion region; + helper_.TakeInvalidRegion(®ion); + + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(CreateFrame())); + + DesktopFrame* current_frame = queue_.current_frame(); + + if (!CgBlit(*current_frame, region)) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share(); + if (detect_updated_region_) { + *new_frame->mutable_updated_region() = region; + } else { + new_frame->mutable_updated_region()->AddRect(DesktopRect::MakeSize(new_frame->size())); + } + + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + new_frame->set_top_left( + config->bounds.top_left().subtract(desktop_config_.bounds.top_left())); + } + } + + helper_.set_size_most_recent(new_frame->size()); + + new_frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(Result::SUCCESS, std::move(new_frame)); +} + +void ScreenCapturerMac::SetExcludedWindow(WindowId window) { + excluded_window_ = window; +} + +bool ScreenCapturerMac::GetSourceList(SourceList* screens) { + RTC_DCHECK(screens->size() == 0); + + for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin(); + it != desktop_config_.displays.end(); + ++it) { + Source value = {it->id, 0, std::string()}; + screens->push_back(value); + } + return true; +} + +bool ScreenCapturerMac::SelectSource(SourceId id) { + if (id == kFullDesktopScreenId) { + current_display_ = 0; + } else { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(static_cast<CGDirectDisplayID>(id)); + if (!config) return false; + current_display_ = config->id; + } + + ScreenConfigurationChanged(); + return true; +} + +bool ScreenCapturerMac::CgBlit(const DesktopFrame& frame, const DesktopRegion& region) { + // If not all screen region is dirty, copy the entire contents of the previous capture buffer, + // to capture over. + if (queue_.previous_frame() && !region.Equals(DesktopRegion(screen_pixel_bounds_))) { + memcpy(frame.data(), queue_.previous_frame()->data(), frame.stride() * frame.size().height()); + } + + MacDisplayConfigurations displays_to_capture; + if (current_display_) { + // Capturing a single screen. Note that the screen id may change when + // screens are added or removed. + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + displays_to_capture.push_back(*config); + } else { + RTC_LOG(LS_ERROR) << "The selected screen cannot be found for capturing."; + return false; + } + } else { + // Capturing the whole desktop. + displays_to_capture = desktop_config_.displays; + } + + // Create the window list once for all displays. + CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_); + + for (size_t i = 0; i < displays_to_capture.size(); ++i) { + const MacDisplayConfiguration& display_config = displays_to_capture[i]; + + // Capturing mixed-DPI on one surface is hard, so we only return displays + // that match the "primary" display's DPI. The primary display is always + // the first in the list. + if (i > 0 && display_config.dip_to_pixel_scale != displays_to_capture[0].dip_to_pixel_scale) { + continue; + } + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-screen_pixel_bounds_.left(), -screen_pixel_bounds_.top()); + + // Determine which parts of the blit region, if any, lay within the monitor. + DesktopRegion copy_region = region; + copy_region.IntersectWith(display_bounds); + if (copy_region.is_empty()) continue; + + // Translate the region to be copied into display-relative coordinates. + copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + + DesktopRect excluded_window_bounds; + rtc::ScopedCFTypeRef<CGImageRef> excluded_image; + if (excluded_window_ && window_list) { + // Get the region of the excluded window relative the primary display. + excluded_window_bounds = + GetExcludedWindowPixelBounds(excluded_window_, display_config.dip_to_pixel_scale); + excluded_window_bounds.IntersectWith(display_config.pixel_bounds); + + // Create the image under the excluded window first, because it's faster + // than captuing the whole display. + if (!excluded_window_bounds.is_empty()) { + excluded_image = CreateExcludedWindowRegionImage( + excluded_window_bounds, display_config.dip_to_pixel_scale, window_list); + } + } + + std::unique_ptr<DesktopFrame> frame_source = + desktop_frame_provider_.TakeLatestFrameForDisplay(display_config.id); + if (!frame_source) { + continue; + } + + const uint8_t* display_base_address = frame_source->data(); + int src_bytes_per_row = frame_source->stride(); + RTC_DCHECK(display_base_address); + + // `frame_source` size may be different from display_bounds in case the screen was + // resized recently. + copy_region.IntersectWith(frame_source->rect()); + + // Copy the dirty region from the display buffer into our desktop buffer. + uint8_t* out_ptr = frame.GetFrameDataAtPos(display_bounds.top_left()); + for (DesktopRegion::Iterator it(copy_region); !it.IsAtEnd(); it.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + DesktopFrame::kBytesPerPixel, + it.rect()); + } + + if (excluded_image) { + CGDataProviderRef provider = CGImageGetDataProvider(excluded_image.get()); + rtc::ScopedCFTypeRef<CFDataRef> excluded_image_data(CGDataProviderCopyData(provider)); + RTC_DCHECK(excluded_image_data); + display_base_address = CFDataGetBytePtr(excluded_image_data.get()); + src_bytes_per_row = CGImageGetBytesPerRow(excluded_image.get()); + + // Translate the bounds relative to the desktop, because `frame` data + // starts from the desktop top-left corner. + DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds); + window_bounds_relative_to_desktop.Translate(-screen_pixel_bounds_.left(), + -screen_pixel_bounds_.top()); + + DesktopRect rect_to_copy = DesktopRect::MakeSize(excluded_window_bounds.size()); + rect_to_copy.IntersectWith(DesktopRect::MakeWH(CGImageGetWidth(excluded_image.get()), + CGImageGetHeight(excluded_image.get()))); + + if (CGImageGetBitsPerPixel(excluded_image.get()) / 8 == DesktopFrame::kBytesPerPixel) { + CopyRect(display_base_address, + src_bytes_per_row, + frame.GetFrameDataAtPos(window_bounds_relative_to_desktop.top_left()), + frame.stride(), + DesktopFrame::kBytesPerPixel, + rect_to_copy); + } + } + } + if (window_list) CFRelease(window_list); + return true; +} + +void ScreenCapturerMac::ScreenConfigurationChanged() { + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + screen_pixel_bounds_ = config ? config->pixel_bounds : DesktopRect(); + dip_to_pixel_scale_ = config ? config->dip_to_pixel_scale : 1.0f; + } else { + screen_pixel_bounds_ = desktop_config_.pixel_bounds; + dip_to_pixel_scale_ = desktop_config_.dip_to_pixel_scale; + } + + // Release existing buffers, which will be of the wrong size. + ReleaseBuffers(); + + // Clear the dirty region, in case the display is down-sizing. + helper_.ClearInvalidRegion(); + + // Re-mark the entire desktop as dirty. + helper_.InvalidateScreen(screen_pixel_bounds_.size()); + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); +} + +bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { + RTC_DCHECK(thread_checker_.IsCurrent()); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + for (const auto& config : desktop_config_.displays) { + size_t pixel_width = config.pixel_bounds.width(); + size_t pixel_height = config.pixel_bounds.height(); + if (pixel_width == 0 || pixel_height == 0) continue; + CGDirectDisplayID display_id = config.id; + DesktopVector display_origin = config.pixel_bounds.top_left(); + + CGDisplayStreamFrameAvailableHandler handler = ^(CGDisplayStreamFrameStatus status, + uint64_t display_time, + IOSurfaceRef frame_surface, + CGDisplayStreamUpdateRef updateRef) { + RTC_DCHECK(thread_checker_.IsCurrent()); + if (status == kCGDisplayStreamFrameStatusStopped) return; + + // Only pay attention to frame updates. + if (status != kCGDisplayStreamFrameStatusFrameComplete) return; + + size_t count = 0; + const CGRect* rects = + CGDisplayStreamUpdateGetRects(updateRef, kCGDisplayStreamUpdateDirtyRects, &count); + if (count != 0) { + // According to CGDisplayStream.h, it's safe to call + // CGDisplayStreamStop() from within the callback. + ScreenRefresh(display_id, count, rects, display_origin, frame_surface); + } + }; + + rtc::ScopedCFTypeRef<CFDictionaryRef> properties_dict( + CFDictionaryCreate(kCFAllocatorDefault, + (const void* []){kCGDisplayStreamShowCursor}, + (const void* []){kCFBooleanFalse}, + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CGDisplayStreamRef display_stream = CGDisplayStreamCreate( + display_id, pixel_width, pixel_height, 'BGRA', properties_dict.get(), handler); + + if (display_stream) { + CGError error = CGDisplayStreamStart(display_stream); + if (error != kCGErrorSuccess) return false; + + CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(display_stream); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); + display_streams_.push_back(display_stream); + } + } + + return true; +} + +void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { + RTC_DCHECK(thread_checker_.IsCurrent()); + + for (CGDisplayStreamRef stream : display_streams_) { + CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(stream); + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); + CGDisplayStreamStop(stream); + CFRelease(stream); + } + display_streams_.clear(); + + // Release obsolete io surfaces. + desktop_frame_provider_.Release(); +} + +void ScreenCapturerMac::ScreenRefresh(CGDirectDisplayID display_id, + CGRectCount count, + const CGRect* rect_array, + DesktopVector display_origin, + IOSurfaceRef io_surface) { + if (screen_pixel_bounds_.is_empty()) ScreenConfigurationChanged(); + + // The refresh rects are in display coordinates. We want to translate to + // framebuffer coordinates. If a specific display is being captured, then no + // change is necessary. If all displays are being captured, then we want to + // translate by the origin of the display. + DesktopVector translate_vector; + if (!current_display_) translate_vector = display_origin; + + DesktopRegion region; + for (CGRectCount i = 0; i < count; ++i) { + // All rects are already in physical pixel coordinates. + DesktopRect rect = DesktopRect::MakeXYWH(rect_array[i].origin.x, + rect_array[i].origin.y, + rect_array[i].size.width, + rect_array[i].size.height); + + rect.Translate(translate_vector); + + region.AddRect(rect); + } + // Always having the latest iosurface before invalidating a region. + // See https://bugs.chromium.org/p/webrtc/issues/detail?id=8652 for details. + desktop_frame_provider_.InvalidateIOSurface( + display_id, rtc::ScopedCFTypeRef<IOSurfaceRef>(io_surface, rtc::RetainPolicy::RETAIN)); + helper_.InvalidateRegion(region); +} + +std::unique_ptr<DesktopFrame> ScreenCapturerMac::CreateFrame() { + std::unique_ptr<DesktopFrame> frame(new BasicDesktopFrame(screen_pixel_bounds_.size())); + frame->set_dpi( + DesktopVector(kStandardDPI * dip_to_pixel_scale_, kStandardDPI * dip_to_pixel_scale_)); + return frame; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc new file mode 100644 index 0000000000..989ec7ea54 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.cc @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mac/window_list_utils.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include <algorithm> +#include <cmath> +#include <iterator> +#include <limits> +#include <list> +#include <map> +#include <memory> +#include <utility> + +#include "rtc_base/checks.h" + +static_assert(static_cast<webrtc::WindowId>(kCGNullWindowID) == + webrtc::kNullWindowId, + "kNullWindowId needs to equal to kCGNullWindowID."); + +namespace webrtc { + +namespace { + +// WindowName of the status indicator dot shown since Monterey in the taskbar. +// Testing on 12.2.1 shows this is independent of system language setting. +const CFStringRef kStatusIndicator = CFSTR("StatusIndicator"); +const CFStringRef kStatusIndicatorOwnerName = CFSTR("Window Server"); + +bool ToUtf8(const CFStringRef str16, std::string* str8) { + size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16), + kCFStringEncodingUTF8) + + 1; + std::unique_ptr<char[]> buffer(new char[maxlen]); + if (!buffer || + !CFStringGetCString(str16, buffer.get(), maxlen, kCFStringEncodingUTF8)) { + return false; + } + str8->assign(buffer.get()); + return true; +} + +// Get CFDictionaryRef from `id` and call `on_window` against it. This function +// returns false if native APIs fail, typically it indicates that the `id` does +// not represent a window. `on_window` will not be called if false is returned +// from this function. +bool GetWindowRef(CGWindowID id, + rtc::FunctionView<void(CFDictionaryRef)> on_window) { + RTC_DCHECK(on_window); + + // TODO(zijiehe): `id` is a 32-bit integer, casting it to an array seems not + // safe enough. Maybe we should create a new + // const void* arr[] = { + // reinterpret_cast<void*>(id) } + // }; + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void**>(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + + bool result = false; + // TODO(zijiehe): CFArrayGetCount(window_array) should always return 1. + // Otherwise, we should treat it as failure. + if (window_array && CFArrayGetCount(window_array)) { + on_window(reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0))); + result = true; + } + + if (window_array) { + CFRelease(window_array); + } + CFRelease(window_id_array); + return result; +} + +} // namespace + +bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window, + bool ignore_minimized, + bool only_zero_layer) { + RTC_DCHECK(on_window); + + // Only get on screen, non-desktop windows. + // According to + // https://developer.apple.com/documentation/coregraphics/cgwindowlistoption/1454105-optiononscreenonly + // , when kCGWindowListOptionOnScreenOnly is used, the order of windows are in + // decreasing z-order. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return false; + + MacDesktopConfiguration desktop_config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + // Check windows to make sure they have an id, title, and use window layer + // other than 0. + CFIndex count = CFArrayGetCount(window_array); + for (CFIndex i = 0; i < count; i++) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, i)); + if (!window) { + continue; + } + + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + continue; + } + + CFNumberRef window_layer = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (!window_layer) { + continue; + } + + // Skip windows with layer!=0 (menu, dock). + int layer; + if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)) { + continue; + } + if (only_zero_layer && layer != 0) { + continue; + } + + // Skip windows that are minimized and not full screen. + if (ignore_minimized && !IsWindowOnScreen(window) && + !IsWindowFullScreen(desktop_config, window)) { + continue; + } + + // If window title is empty, only consider it if it is either on screen or + // fullscreen. + CFStringRef window_title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + if (!window_title && !IsWindowOnScreen(window) && + !IsWindowFullScreen(desktop_config, window)) { + continue; + } + + CFStringRef window_owner_name = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowOwnerName)); + // Ignore the red dot status indicator shown in the stats bar. Unlike the + // rest of the system UI it has a window_layer of 0, so was otherwise + // included. See crbug.com/1297731. + if (window_title && CFEqual(window_title, kStatusIndicator) && + window_owner_name && + CFEqual(window_owner_name, kStatusIndicatorOwnerName)) { + continue; + } + + if (!on_window(window)) { + break; + } + } + + CFRelease(window_array); + return true; +} + +bool GetWindowList(DesktopCapturer::SourceList* windows, + bool ignore_minimized, + bool only_zero_layer) { + // Use a std::list so that iterators are preversed upon insertion and + // deletion. + std::list<DesktopCapturer::Source> sources; + std::map<int, std::list<DesktopCapturer::Source>::const_iterator> pid_itr_map; + const bool ret = GetWindowList( + [&sources, &pid_itr_map](CFDictionaryRef window) { + WindowId window_id = GetWindowId(window); + if (window_id != kNullWindowId) { + const std::string title = GetWindowTitle(window); + const int pid = GetWindowOwnerPid(window); + // Check if window for the same pid have been already inserted. + std::map<int, + std::list<DesktopCapturer::Source>::const_iterator>::iterator + itr = pid_itr_map.find(pid); + + // Only consider empty titles if the app has no other window with a + // proper title. + if (title.empty()) { + std::string owner_name = GetWindowOwnerName(window); + + // At this time we do not know if there will be other windows + // for the same pid unless they have been already inserted, hence + // the check in the map. Also skip the window if owner name is + // empty too. + if (!owner_name.empty() && (itr == pid_itr_map.end())) { + sources.push_back(DesktopCapturer::Source{window_id, pid, owner_name}); + RTC_DCHECK(!sources.empty()); + // Get an iterator on the last valid element in the source list. + std::list<DesktopCapturer::Source>::const_iterator last_source = + --sources.end(); + pid_itr_map.insert( + std::pair<int, + std::list<DesktopCapturer::Source>::const_iterator>( + pid, last_source)); + } + } else { + sources.push_back(DesktopCapturer::Source{window_id, pid, title}); + // Once the window with empty title has been removed no other empty + // windows are allowed for the same pid. + if (itr != pid_itr_map.end() && (itr->second != sources.end())) { + sources.erase(itr->second); + // sdt::list::end() never changes during the lifetime of that + // list. + itr->second = sources.end(); + } + } + } + return true; + }, + ignore_minimized, only_zero_layer); + + if (!ret) + return false; + + RTC_DCHECK(windows); + windows->reserve(windows->size() + sources.size()); + std::copy(std::begin(sources), std::end(sources), + std::back_inserter(*windows)); + + return true; +} + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CFDictionaryRef window) { + bool fullscreen = false; + CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + + CGRect bounds; + if (bounds_ref && + CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) { + for (MacDisplayConfigurations::const_iterator it = + desktop_config.displays.begin(); + it != desktop_config.displays.end(); it++) { + if (it->bounds.equals( + DesktopRect::MakeXYWH(bounds.origin.x, bounds.origin.y, + bounds.size.width, bounds.size.height))) { + fullscreen = true; + break; + } + } + } + + return fullscreen; +} + +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CGWindowID id) { + bool fullscreen = false; + GetWindowRef(id, [&](CFDictionaryRef window) { + fullscreen = IsWindowFullScreen(desktop_config, window); + }); + return fullscreen; +} + +bool IsWindowOnScreen(CFDictionaryRef window) { + CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + return on_screen != NULL && CFBooleanGetValue(on_screen); +} + +bool IsWindowOnScreen(CGWindowID id) { + bool on_screen; + if (GetWindowRef(id, [&on_screen](CFDictionaryRef window) { + on_screen = IsWindowOnScreen(window); + })) { + return on_screen; + } + return false; +} + +std::string GetWindowTitle(CFDictionaryRef window) { + CFStringRef title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + std::string result; + if (title && ToUtf8(title, &result)) { + return result; + } + + return std::string(); +} + +std::string GetWindowTitle(CGWindowID id) { + std::string title; + if (GetWindowRef(id, [&title](CFDictionaryRef window) { + title = GetWindowTitle(window); + })) { + return title; + } + return std::string(); +} + +std::string GetWindowOwnerName(CFDictionaryRef window) { + CFStringRef owner_name = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowOwnerName)); + std::string result; + if (owner_name && ToUtf8(owner_name, &result)) { + return result; + } + return std::string(); +} + +std::string GetWindowOwnerName(CGWindowID id) { + std::string owner_name; + if (GetWindowRef(id, [&owner_name](CFDictionaryRef window) { + owner_name = GetWindowOwnerName(window); + })) { + return owner_name; + } + return std::string(); +} + +WindowId GetWindowId(CFDictionaryRef window) { + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + return kNullWindowId; + } + + // Note: WindowId is 64-bit on 64-bit system, but CGWindowID is always 32-bit. + // CFNumberGetValue() fills only top 32 bits, so we should use CGWindowID to + // receive the window id. + CGWindowID id; + if (!CFNumberGetValue(window_id, kCFNumberIntType, &id)) { + return kNullWindowId; + } + + return id; +} + +int GetWindowOwnerPid(CFDictionaryRef window) { + CFNumberRef window_pid = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + if (!window_pid) { + return 0; + } + + int pid; + if (!CFNumberGetValue(window_pid, kCFNumberIntType, &pid)) { + return 0; + } + + return pid; +} + +int GetWindowOwnerPid(CGWindowID id) { + int pid; + if (GetWindowRef(id, [&pid](CFDictionaryRef window) { + pid = GetWindowOwnerPid(window); + })) { + return pid; + } + return 0; +} + +float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config, + DesktopVector position) { + // Find the dpi to physical pixel scale for the screen where the mouse cursor + // is. + for (auto it = desktop_config.displays.begin(); + it != desktop_config.displays.end(); ++it) { + if (it->bounds.Contains(position)) { + return it->dip_to_pixel_scale; + } + } + return 1; +} + +float GetWindowScaleFactor(CGWindowID id, DesktopSize size) { + DesktopRect window_bounds = GetWindowBounds(id); + float scale = 1.0f; + + if (!window_bounds.is_empty() && !size.is_empty()) { + float scale_x = size.width() / window_bounds.width(); + float scale_y = size.height() / window_bounds.height(); + // Currently the scale in X and Y directions must be same. + if ((std::fabs(scale_x - scale_y) <= + std::numeric_limits<float>::epsilon() * std::max(scale_x, scale_y)) && + scale_x > 0.0f) { + scale = scale_x; + } + } + + return scale; +} + +DesktopRect GetWindowBounds(CFDictionaryRef window) { + CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + if (!window_bounds) { + return DesktopRect(); + } + + CGRect gc_window_rect; + if (!CGRectMakeWithDictionaryRepresentation(window_bounds, &gc_window_rect)) { + return DesktopRect(); + } + + return DesktopRect::MakeXYWH(gc_window_rect.origin.x, gc_window_rect.origin.y, + gc_window_rect.size.width, + gc_window_rect.size.height); +} + +DesktopRect GetWindowBounds(CGWindowID id) { + DesktopRect result; + if (GetWindowRef(id, [&result](CFDictionaryRef window) { + result = GetWindowBounds(window); + })) { + return result; + } + return DesktopRect(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h new file mode 100644 index 0000000000..a9b1e7007c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mac/window_list_utils.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <string> +#include "api/function_view.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" + +namespace webrtc { + +// Iterates all on-screen windows in decreasing z-order and sends them +// one-by-one to `on_window` function. If `on_window` returns false, this +// function returns immediately. GetWindowList() returns false if native APIs +// failed. Menus, dock (if `only_zero_layer`), minimized windows (if +// `ignore_minimized` is true) and any windows which do not have a valid window +// id or title will be ignored. +bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window, + bool ignore_minimized, + bool only_zero_layer); + +// Another helper function to get the on-screen windows. +bool GetWindowList(DesktopCapturer::SourceList* windows, + bool ignore_minimized, + bool only_zero_layer); + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CFDictionaryRef window); + +// Returns true if the window is occupying a full screen. +bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, + CGWindowID id); + +// Returns true if the `window` is on screen. This function returns false if +// native APIs fail. +bool IsWindowOnScreen(CFDictionaryRef window); + +// Returns true if the window is on screen. This function returns false if +// native APIs fail or `id` cannot be found. +bool IsWindowOnScreen(CGWindowID id); + +// Returns utf-8 encoded title of `window`. If `window` is not a window or no +// valid title can be retrieved, this function returns an empty string. +std::string GetWindowTitle(CFDictionaryRef window); + +// Returns utf-8 encoded title of window `id`. If `id` cannot be found or no +// valid title can be retrieved, this function returns an empty string. +std::string GetWindowTitle(CGWindowID id); + +// Returns utf-8 encoded owner name of `window`. If `window` is not a window or +// if no valid owner name can be retrieved, returns an empty string. +std::string GetWindowOwnerName(CFDictionaryRef window); + +// Returns utf-8 encoded owner name of the given window `id`. If `id` cannot be +// found or if no valid owner name can be retrieved, returns an empty string. +std::string GetWindowOwnerName(CGWindowID id); + +// Returns id of `window`. If `window` is not a window or the window id cannot +// be retrieved, this function returns kNullWindowId. +WindowId GetWindowId(CFDictionaryRef window); + +// Returns the pid of the process owning `window`. Return 0 if `window` is not +// a window or no valid owner can be retrieved. +int GetWindowOwnerPid(CFDictionaryRef window); + +// Returns the pid of the process owning the window `id`. Return 0 if `id` +// cannot be found or no valid owner can be retrieved. +int GetWindowOwnerPid(CGWindowID id); + +// Returns the DIP to physical pixel scale at `position`. `position` is in +// *unscaled* system coordinate, i.e. it's device-independent and the primary +// monitor starts from (0, 0). If `position` is out of the system display, this +// function returns 1. +float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config, + DesktopVector position); + +// Returns the DIP to physical pixel scale factor of the window with `id`. +// The bounds of the window with `id` is in DIP coordinates and `size` is the +// CGImage size of the window with `id` in physical coordinates. Comparing them +// can give the current scale factor. +// If the window overlaps multiple monitors, OS will decide on which monitor the +// window is displayed and use its scale factor to the window. So this method +// still works. +float GetWindowScaleFactor(CGWindowID id, DesktopSize size); + +// Returns the bounds of `window`. If `window` is not a window or the bounds +// cannot be retrieved, this function returns an empty DesktopRect. The returned +// DesktopRect is in system coordinate, i.e. the primary monitor always starts +// from (0, 0). +// Deprecated: This function should be avoided in favor of the overload with +// MacDesktopConfiguration. +DesktopRect GetWindowBounds(CFDictionaryRef window); + +// Returns the bounds of window with `id`. If `id` does not represent a window +// or the bounds cannot be retrieved, this function returns an empty +// DesktopRect. The returned DesktopRect is in system coordinates. +// Deprecated: This function should be avoided in favor of the overload with +// MacDesktopConfiguration. +DesktopRect GetWindowBounds(CGWindowID id); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.cc b/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.cc new file mode 100644 index 0000000000..de77d99e18 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.cc @@ -0,0 +1,23 @@ +/* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mock_desktop_capturer_callback.h" + +namespace webrtc { + +MockDesktopCapturerCallback::MockDesktopCapturerCallback() = default; +MockDesktopCapturerCallback::~MockDesktopCapturerCallback() = default; + +void MockDesktopCapturerCallback::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) { + OnCaptureResultPtr(result, &frame); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.h b/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.h new file mode 100644 index 0000000000..08de5ad737 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mock_desktop_capturer_callback.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MOCK_DESKTOP_CAPTURER_CALLBACK_H_ +#define MODULES_DESKTOP_CAPTURE_MOCK_DESKTOP_CAPTURER_CALLBACK_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockDesktopCapturerCallback : public DesktopCapturer::Callback { + public: + MockDesktopCapturerCallback(); + ~MockDesktopCapturerCallback() override; + + MockDesktopCapturerCallback(const MockDesktopCapturerCallback&) = delete; + MockDesktopCapturerCallback& operator=(const MockDesktopCapturerCallback&) = + delete; + + MOCK_METHOD(void, + OnCaptureResultPtr, + (DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame)); + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) final override; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MOCK_DESKTOP_CAPTURER_CALLBACK_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.cc b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.cc new file mode 100644 index 0000000000..e826552b0f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.cc @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mouse_cursor.h" + +#include "modules/desktop_capture/desktop_frame.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +MouseCursor::MouseCursor() {} + +MouseCursor::MouseCursor(DesktopFrame* image, const DesktopVector& hotspot) + : image_(image), hotspot_(hotspot) { + RTC_DCHECK(0 <= hotspot_.x() && hotspot_.x() <= image_->size().width()); + RTC_DCHECK(0 <= hotspot_.y() && hotspot_.y() <= image_->size().height()); +} + +MouseCursor::~MouseCursor() {} + +// static +MouseCursor* MouseCursor::CopyOf(const MouseCursor& cursor) { + return cursor.image() + ? new MouseCursor(BasicDesktopFrame::CopyOf(*cursor.image()), + cursor.hotspot()) + : new MouseCursor(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.h b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.h new file mode 100644 index 0000000000..2dd793179b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ +#define MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class RTC_EXPORT MouseCursor { + public: + MouseCursor(); + + // Takes ownership of `image`. `hotspot` must be within `image` boundaries. + MouseCursor(DesktopFrame* image, const DesktopVector& hotspot); + + ~MouseCursor(); + + MouseCursor(const MouseCursor&) = delete; + MouseCursor& operator=(const MouseCursor&) = delete; + + static MouseCursor* CopyOf(const MouseCursor& cursor); + + void set_image(DesktopFrame* image) { image_.reset(image); } + const DesktopFrame* image() const { return image_.get(); } + + void set_hotspot(const DesktopVector& hotspot) { hotspot_ = hotspot; } + const DesktopVector& hotspot() const { return hotspot_; } + + private: + std::unique_ptr<DesktopFrame> image_; + DesktopVector hotspot_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor.h b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor.h new file mode 100644 index 0000000000..e88a5b7201 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ +#define MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class DesktopCaptureOptions; +class DesktopFrame; +class MouseCursor; + +// Captures mouse shape and position. +class MouseCursorMonitor { + public: + // Deprecated: CursorState will not be provided. + enum CursorState { + // Cursor on top of the window including window decorations. + INSIDE, + + // Cursor is outside of the window. + OUTSIDE, + }; + + enum Mode { + // Capture only shape of the mouse cursor, but not position. + SHAPE_ONLY, + + // Capture both, mouse cursor shape and position. + SHAPE_AND_POSITION, + }; + + // Callback interface used to pass current mouse cursor position and shape. + class Callback { + public: + // Called in response to Capture() when the cursor shape has changed. Must + // take ownership of `cursor`. + virtual void OnMouseCursor(MouseCursor* cursor) = 0; + + // Called in response to Capture(). `position` indicates cursor position + // relative to the `window` specified in the constructor. + // Deprecated: use the following overload instead. + virtual void OnMouseCursorPosition(CursorState state, + const DesktopVector& position) {} + + // Called in response to Capture(). `position` indicates cursor absolute + // position on the system in fullscreen coordinate, i.e. the top-left + // monitor always starts from (0, 0). + // The coordinates of the position is controlled by OS, but it's always + // consistent with DesktopFrame.rect().top_left(). + // TODO(zijiehe): Ensure all implementations return the absolute position. + // TODO(zijiehe): Current this overload works correctly only when capturing + // mouse cursor against fullscreen. + virtual void OnMouseCursorPosition(const DesktopVector& position) {} + + protected: + virtual ~Callback() {} + }; + + virtual ~MouseCursorMonitor() {} + + // Creates a capturer that notifies of mouse cursor events while the cursor is + // over the specified window. + // + // Deprecated: use Create() function. + static MouseCursorMonitor* CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window); + + // Creates a capturer that monitors the mouse cursor shape and position over + // the specified screen. + // + // Deprecated: use Create() function. + static RTC_EXPORT MouseCursorMonitor* CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen); + + // Creates a capturer that monitors the mouse cursor shape and position across + // the entire desktop. The capturer ensures that the top-left monitor starts + // from (0, 0). + static std::unique_ptr<MouseCursorMonitor> Create( + const DesktopCaptureOptions& options); + + // Initializes the monitor with the `callback`, which must remain valid until + // capturer is destroyed. + virtual void Init(Callback* callback, Mode mode) = 0; + + // Captures current cursor shape and position (depending on the `mode` passed + // to Init()). Calls Callback::OnMouseCursor() if cursor shape has + // changed since the last call (or when Capture() is called for the first + // time) and then Callback::OnMouseCursorPosition() if mode is set to + // SHAPE_AND_POSITION. + virtual void Capture() = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc new file mode 100644 index 0000000000..6162970074 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc @@ -0,0 +1,65 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" + +#if defined(WEBRTC_USE_X11) +#include "modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h" +#endif // defined(WEBRTC_USE_X11) + +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) +#include "modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h" +#endif // defined(WEBRTC_USE_PIPEWIRE) + +namespace webrtc { + +// static +MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window) { +#if defined(WEBRTC_USE_X11) + return MouseCursorMonitorX11::CreateForWindow(options, window); +#else + return nullptr; +#endif // defined(WEBRTC_USE_X11) +} + +// static +MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { +#if defined(WEBRTC_USE_X11) + return MouseCursorMonitorX11::CreateForScreen(options, screen); +#else + return nullptr; +#endif // defined(WEBRTC_USE_X11) +} + +// static +std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( + const DesktopCaptureOptions& options) { +#if defined(WEBRTC_USE_PIPEWIRE) && !defined(WEBRTC_MOZILLA_BUILD) + if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland() && + options.screencast_stream()) { + return std::make_unique<MouseCursorMonitorPipeWire>(options); + } +#endif // defined(WEBRTC_USE_PIPEWIRE) + +#if defined(WEBRTC_USE_X11) + return MouseCursorMonitorX11::Create(options); +#else + return nullptr; +#endif // defined(WEBRTC_USE_X11) +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm new file mode 100644 index 0000000000..512103ab5e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mouse_cursor_monitor.h" + + +#include <memory> + +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/window_list_utils.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { +CGImageRef CreateScaledCGImage(CGImageRef image, int width, int height) { + // Create context, keeping original image properties. + CGColorSpaceRef colorspace = CGImageGetColorSpace(image); + CGContextRef context = CGBitmapContextCreate(nullptr, + width, + height, + CGImageGetBitsPerComponent(image), + width * DesktopFrame::kBytesPerPixel, + colorspace, + CGImageGetBitmapInfo(image)); + + if (!context) return nil; + + // Draw image to context, resizing it. + CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); + // Extract resulting image from context. + CGImageRef imgRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + return imgRef; +} +} // namespace + +class MouseCursorMonitorMac : public MouseCursorMonitor { + public: + MouseCursorMonitorMac(const DesktopCaptureOptions& options, + CGWindowID window_id, + ScreenId screen_id); + ~MouseCursorMonitorMac() override; + + void Init(Callback* callback, Mode mode) override; + void Capture() override; + + private: + static void DisplaysReconfiguredCallback(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void *user_parameter); + void DisplaysReconfigured(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags); + + void CaptureImage(float scale); + + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; + CGWindowID window_id_; + ScreenId screen_id_; + Callback* callback_ = NULL; + Mode mode_; + __strong NSImage* last_cursor_ = NULL; +}; + +MouseCursorMonitorMac::MouseCursorMonitorMac(const DesktopCaptureOptions& options, + CGWindowID window_id, + ScreenId screen_id) + : configuration_monitor_(options.configuration_monitor()), + window_id_(window_id), + screen_id_(screen_id), + mode_(SHAPE_AND_POSITION) { + RTC_DCHECK(window_id == kCGNullWindowID || screen_id == kInvalidScreenId); +} + +MouseCursorMonitorMac::~MouseCursorMonitorMac() {} + +void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; + mode_ = mode; +} + +void MouseCursorMonitorMac::Capture() { + RTC_DCHECK(callback_); + + CGEventRef event = CGEventCreate(NULL); + CGPoint gc_position = CGEventGetLocation(event); + CFRelease(event); + + DesktopVector position(gc_position.x, gc_position.y); + + MacDesktopConfiguration configuration = + configuration_monitor_->desktop_configuration(); + float scale = GetScaleFactorAtPosition(configuration, position); + + CaptureImage(scale); + + if (mode_ != SHAPE_AND_POSITION) + return; + + // Always report cursor position in DIP pixel. + callback_->OnMouseCursorPosition( + position.subtract(configuration.bounds.top_left())); +} + +void MouseCursorMonitorMac::CaptureImage(float scale) { + NSCursor* nscursor = [NSCursor currentSystemCursor]; + + NSImage* nsimage = [nscursor image]; + if (nsimage == nil || !nsimage.isValid) { + return; + } + NSSize nssize = [nsimage size]; // DIP size + + // No need to caputre cursor image if it's unchanged since last capture. + if (last_cursor_ && [[nsimage TIFFRepresentation] isEqual:[last_cursor_ TIFFRepresentation]]) return; + last_cursor_ = nsimage; + + DesktopSize size(round(nssize.width * scale), + round(nssize.height * scale)); // Pixel size + NSPoint nshotspot = [nscursor hotSpot]; + DesktopVector hotspot( + std::max(0, + std::min(size.width(), static_cast<int>(nshotspot.x * scale))), + std::max(0, + std::min(size.height(), static_cast<int>(nshotspot.y * scale)))); + CGImageRef cg_image = + [nsimage CGImageForProposedRect:NULL context:nil hints:nil]; + if (!cg_image) + return; + + // Before 10.12, OSX may report 1X cursor on Retina screen. (See + // crbug.com/632995.) After 10.12, OSX may report 2X cursor on non-Retina + // screen. (See crbug.com/671436.) So scaling the cursor if needed. + CGImageRef scaled_cg_image = nil; + if (CGImageGetWidth(cg_image) != static_cast<size_t>(size.width())) { + scaled_cg_image = CreateScaledCGImage(cg_image, size.width(), size.height()); + if (scaled_cg_image != nil) { + cg_image = scaled_cg_image; + } + } + if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 || + CGImageGetWidth(cg_image) != static_cast<size_t>(size.width()) || + CGImageGetBitsPerComponent(cg_image) != 8) { + if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); + return; + } + + CGDataProviderRef provider = CGImageGetDataProvider(cg_image); + CFDataRef image_data_ref = CGDataProviderCopyData(provider); + if (image_data_ref == NULL) { + if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); + return; + } + + const uint8_t* src_data = + reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref)); + + // Create a MouseCursor that describes the cursor and pass it to + // the client. + std::unique_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(size.width(), size.height()))); + + int src_stride = CGImageGetBytesPerRow(cg_image); + image->CopyPixelsFrom(src_data, src_stride, DesktopRect::MakeSize(size)); + + CFRelease(image_data_ref); + if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); + + std::unique_ptr<MouseCursor> cursor( + new MouseCursor(image.release(), hotspot)); + + callback_->OnMouseCursor(cursor.release()); +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( + const DesktopCaptureOptions& options, WindowId window) { + return new MouseCursorMonitorMac(options, window, kInvalidScreenId); +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + return new MouseCursorMonitorMac(options, kCGNullWindowID, screen); +} + +std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( + const DesktopCaptureOptions& options) { + return std::unique_ptr<MouseCursorMonitor>( + CreateForScreen(options, kFullDesktopScreenId)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc new file mode 100644 index 0000000000..ab1bc2fa33 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <stddef.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" + +namespace webrtc { + +MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window) { + return NULL; +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + return NULL; +} + +std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( + const DesktopCaptureOptions& options) { + return std::unique_ptr<MouseCursorMonitor>( + CreateForScreen(options, kFullDesktopScreenId)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc new file mode 100644 index 0000000000..f771276a2b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/mouse_cursor_monitor.h" + +#include <stddef.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "rtc_base/checks.h" +#include "test/gtest.h" + +namespace webrtc { + +class MouseCursorMonitorTest : public ::testing::Test, + public MouseCursorMonitor::Callback { + public: + MouseCursorMonitorTest() : position_received_(false) {} + + // MouseCursorMonitor::Callback interface + void OnMouseCursor(MouseCursor* cursor_image) override { + cursor_image_.reset(cursor_image); + } + + void OnMouseCursorPosition(const DesktopVector& position) override { + position_ = position; + position_received_ = true; + } + + protected: + std::unique_ptr<MouseCursor> cursor_image_; + DesktopVector position_; + bool position_received_; +}; + +// TODO(sergeyu): On Mac we need to initialize NSApplication before running the +// tests. Figure out how to do that without breaking other tests in +// modules_unittests and enable these tests on Mac. +// https://code.google.com/p/webrtc/issues/detail?id=2532 +// +// Disabled on Windows due to flake, see: +// https://code.google.com/p/webrtc/issues/detail?id=3408 +// Disabled on Linux due to flake, see: +// https://code.google.com/p/webrtc/issues/detail?id=3245 +#if !defined(WEBRTC_MAC) && !defined(WEBRTC_WIN) && !defined(WEBRTC_LINUX) +#define MAYBE(x) x +#else +#define MAYBE(x) DISABLED_##x +#endif + +TEST_F(MouseCursorMonitorTest, MAYBE(FromScreen)) { + std::unique_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForScreen( + DesktopCaptureOptions::CreateDefault(), + webrtc::kFullDesktopScreenId)); + RTC_DCHECK(capturer.get()); + capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION); + capturer->Capture(); + + EXPECT_TRUE(cursor_image_.get()); + EXPECT_GE(cursor_image_->hotspot().x(), 0); + EXPECT_LE(cursor_image_->hotspot().x(), + cursor_image_->image()->size().width()); + EXPECT_GE(cursor_image_->hotspot().y(), 0); + EXPECT_LE(cursor_image_->hotspot().y(), + cursor_image_->image()->size().height()); + + EXPECT_TRUE(position_received_); +} + +TEST_F(MouseCursorMonitorTest, MAYBE(FromWindow)) { + DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault(); + + // First get list of windows. + std::unique_ptr<DesktopCapturer> window_capturer( + DesktopCapturer::CreateWindowCapturer(options)); + + // If window capturing is not supported then skip this test. + if (!window_capturer.get()) + return; + + DesktopCapturer::SourceList sources; + EXPECT_TRUE(window_capturer->GetSourceList(&sources)); + + // Iterate over all windows and try capturing mouse cursor for each of them. + for (size_t i = 0; i < sources.size(); ++i) { + cursor_image_.reset(); + position_received_ = false; + + std::unique_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForWindow( + DesktopCaptureOptions::CreateDefault(), sources[i].id)); + RTC_DCHECK(capturer.get()); + + capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION); + capturer->Capture(); + + EXPECT_TRUE(cursor_image_.get()); + EXPECT_TRUE(position_received_); + } +} + +// Make sure that OnMouseCursorPosition() is not called in the SHAPE_ONLY mode. +TEST_F(MouseCursorMonitorTest, MAYBE(ShapeOnly)) { + std::unique_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForScreen( + DesktopCaptureOptions::CreateDefault(), + webrtc::kFullDesktopScreenId)); + RTC_DCHECK(capturer.get()); + capturer->Init(this, MouseCursorMonitor::SHAPE_ONLY); + capturer->Capture(); + + EXPECT_TRUE(cursor_image_.get()); + EXPECT_FALSE(position_received_); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc new file mode 100644 index 0000000000..c22425b5a2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <string.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/mouse_cursor_monitor.h" +#include "modules/desktop_capture/win/cursor.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "rtc_base/logging.h" + +#include <windows.h> + +namespace webrtc { + +namespace { + +bool IsSameCursorShape(const CURSORINFO& left, const CURSORINFO& right) { + // If the cursors are not showing, we do not care the hCursor handle. + return left.flags == right.flags && + (left.flags != CURSOR_SHOWING || left.hCursor == right.hCursor); +} + +} // namespace + +class MouseCursorMonitorWin : public MouseCursorMonitor { + public: + explicit MouseCursorMonitorWin(HWND window); + explicit MouseCursorMonitorWin(ScreenId screen); + ~MouseCursorMonitorWin() override; + + void Init(Callback* callback, Mode mode) override; + void Capture() override; + + private: + // Get the rect of the currently selected screen, relative to the primary + // display's top-left. If the screen is disabled or disconnected, or any error + // happens, an empty rect is returned. + DesktopRect GetScreenRect(); + + HWND window_; + ScreenId screen_; + + Callback* callback_; + Mode mode_; + + HDC desktop_dc_; + + // The last CURSORINFO (converted to MouseCursor) we have sent to the client. + CURSORINFO last_cursor_; +}; + +MouseCursorMonitorWin::MouseCursorMonitorWin(HWND window) + : window_(window), + screen_(kInvalidScreenId), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + desktop_dc_(NULL) { + memset(&last_cursor_, 0, sizeof(CURSORINFO)); +} + +MouseCursorMonitorWin::MouseCursorMonitorWin(ScreenId screen) + : window_(NULL), + screen_(screen), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + desktop_dc_(NULL) { + RTC_DCHECK_GE(screen, kFullDesktopScreenId); + memset(&last_cursor_, 0, sizeof(CURSORINFO)); +} + +MouseCursorMonitorWin::~MouseCursorMonitorWin() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); +} + +void MouseCursorMonitorWin::Init(Callback* callback, Mode mode) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RTC_DCHECK(IsGUIThread(false)); + + callback_ = callback; + mode_ = mode; + + desktop_dc_ = GetDC(NULL); +} + +void MouseCursorMonitorWin::Capture() { +// TODO: Bug 1666266. Commented out to pass new tests added in bug 1634044. +// RTC_DCHECK(IsGUIThread(false)); + RTC_DCHECK(callback_); + + CURSORINFO cursor_info; + cursor_info.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&cursor_info)) { + RTC_LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " + << GetLastError(); + return; + } + + if (!IsSameCursorShape(cursor_info, last_cursor_)) { + // Mozilla - CURSOR_SUPPRESSED is win8 and above; so we seem not to be able to see the symbol + if (cursor_info.flags != CURSOR_SHOWING) { + // The cursor is intentionally hidden now, send an empty bitmap. + last_cursor_ = cursor_info; + callback_->OnMouseCursor(new MouseCursor( + new BasicDesktopFrame(DesktopSize()), DesktopVector())); + } else { + // According to MSDN https://goo.gl/u6gyuC, HCURSOR instances returned by + // functions other than CreateCursor do not need to be actively destroyed. + // And CloseHandle function (https://goo.gl/ja5ycW) does not close a + // cursor, so assume a HCURSOR does not need to be closed. + if (cursor_info.flags == 0) { + // Host machine does not have a hardware mouse attached, we will send a + // default one instead. + // Note, Windows automatically caches cursor resource, so we do not need + // to cache the result of LoadCursor. + cursor_info.hCursor = LoadCursor(nullptr, IDC_ARROW); + } + std::unique_ptr<MouseCursor> cursor( + CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); + if (cursor) { + last_cursor_ = cursor_info; + callback_->OnMouseCursor(cursor.release()); + } + } + } + + if (mode_ != SHAPE_AND_POSITION) + return; + + // CURSORINFO::ptScreenPos is in full desktop coordinate. + DesktopVector position(cursor_info.ptScreenPos.x, cursor_info.ptScreenPos.y); + bool inside = cursor_info.flags == CURSOR_SHOWING; + + if (window_) { + DesktopRect original_rect; + DesktopRect cropped_rect; + if (!GetCroppedWindowRect(window_, /*avoid_cropping_border*/ false, + &cropped_rect, &original_rect)) { + position.set(0, 0); + inside = false; + } else { + if (inside) { + HWND windowUnderCursor = WindowFromPoint(cursor_info.ptScreenPos); + inside = windowUnderCursor + ? (window_ == GetAncestor(windowUnderCursor, GA_ROOT)) + : false; + } + position = position.subtract(cropped_rect.top_left()); + } + } else { + RTC_DCHECK_NE(screen_, kInvalidScreenId); + DesktopRect rect = GetScreenRect(); + if (inside) + inside = rect.Contains(position); + position = position.subtract(rect.top_left()); + } + + callback_->OnMouseCursorPosition(position); +} + +DesktopRect MouseCursorMonitorWin::GetScreenRect() { + RTC_DCHECK(IsGUIThread(false)); + RTC_DCHECK_NE(screen_, kInvalidScreenId); + if (screen_ == kFullDesktopScreenId) { + return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN)); + } + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL result = EnumDisplayDevices(NULL, screen_, &device, 0); + if (!result) + return DesktopRect(); + + DEVMODE device_mode; + device_mode.dmSize = sizeof(device_mode); + device_mode.dmDriverExtra = 0; + result = EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS, + &device_mode, 0); + if (!result) + return DesktopRect(); + + return DesktopRect::MakeXYWH( + device_mode.dmPosition.x, device_mode.dmPosition.y, + device_mode.dmPelsWidth, device_mode.dmPelsHeight); +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window) { + return new MouseCursorMonitorWin(reinterpret_cast<HWND>(window)); +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + return new MouseCursorMonitorWin(screen); +} + +std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( + const DesktopCaptureOptions& options) { + return std::unique_ptr<MouseCursorMonitor>( + CreateForScreen(options, kFullDesktopScreenId)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/primitives_gn/moz.build b/third_party/libwebrtc/modules/desktop_capture/primitives_gn/moz.build new file mode 100644 index 0000000000..c0ef33e2b3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/primitives_gn/moz.build @@ -0,0 +1,185 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### + ### DO NOT edit it by hand. ### + +COMPILE_FLAGS["OS_INCLUDES"] = [] +AllowCompilerWarnings() + +DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1" +DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True +DEFINES["RTC_ENABLE_VP9"] = True +DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0" +DEFINES["WEBRTC_LIBRARY_IMPL"] = True +DEFINES["WEBRTC_MOZILLA_BUILD"] = True +DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0" + +FINAL_LIBRARY = "webrtc" + + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/ipc/chromium/src", + "/media/libyuv/", + "/media/libyuv/libyuv/include/", + "/third_party/libwebrtc/", + "/third_party/libwebrtc/third_party/abseil-cpp/", + "/tools/profiler/public" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/desktop_capture/desktop_frame.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_geometry.cc", + "/third_party/libwebrtc/modules/desktop_capture/desktop_region.cc", + "/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.cc", + "/third_party/libwebrtc/modules/desktop_capture/shared_memory.cc" +] + +if not CONFIG["MOZ_DEBUG"]: + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0" + DEFINES["NDEBUG"] = True + DEFINES["NVALGRIND"] = True + +if CONFIG["MOZ_DEBUG"] == "1": + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1" + +if CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_MAC"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True + DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0" + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_AURA"] = "1" + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_NSS_CERTS"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_UDEV"] = True + DEFINES["WEBRTC_LINUX"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_BSD"] = True + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True + DEFINES["NOMINMAX"] = True + DEFINES["NTDDI_VERSION"] = "0x0A000000" + DEFINES["PSAPI_VERSION"] = "2" + DEFINES["UNICODE"] = True + DEFINES["USE_AURA"] = "1" + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_WIN"] = True + DEFINES["WIN32"] = True + DEFINES["WIN32_LEAN_AND_MEAN"] = True + DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP" + DEFINES["WINVER"] = "0x0A00" + DEFINES["_ATL_NO_OPENGL"] = True + DEFINES["_CRT_RAND_S"] = True + DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True + DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True + DEFINES["_HAS_EXCEPTIONS"] = "0" + DEFINES["_HAS_NODISCARD"] = True + DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True + DEFINES["_SECURE_ATL"] = True + DEFINES["_UNICODE"] = True + DEFINES["_WIN32_WINNT"] = "0x0A00" + DEFINES["_WINDOWS"] = True + DEFINES["__STD_C"] = True + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "arm": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + DEFINES["WEBRTC_ARCH_ARM"] = True + DEFINES["WEBRTC_ARCH_ARM_V7"] = True + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "ppc64": + + DEFINES["USE_X11"] = "1" + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0" + +if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +if CONFIG["CPU_ARCH"] == "arm" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +Library("primitives_gn") diff --git a/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.cc b/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.cc new file mode 100644 index 0000000000..9639d627fa --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.cc @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/resolution_tracker.h" + +namespace webrtc { + +bool ResolutionTracker::SetResolution(DesktopSize size) { + if (!initialized_) { + initialized_ = true; + last_size_ = size; + return false; + } + + if (last_size_.equals(size)) { + return false; + } + + last_size_ = size; + return true; +} + +void ResolutionTracker::Reset() { + initialized_ = false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.h b/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.h new file mode 100644 index 0000000000..8fe9d61862 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/resolution_tracker.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_RESOLUTION_TRACKER_H_ +#define MODULES_DESKTOP_CAPTURE_RESOLUTION_TRACKER_H_ + +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class ResolutionTracker final { + public: + // Sets the resolution to `size`. Returns true if a previous size was recorded + // and differs from `size`. + bool SetResolution(DesktopSize size); + + // Resets to the initial state. + void Reset(); + + private: + DesktopSize last_size_; + bool initialized_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_RESOLUTION_TRACKER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/rgba_color.cc b/third_party/libwebrtc/modules/desktop_capture/rgba_color.cc new file mode 100644 index 0000000000..362928a474 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/rgba_color.cc @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/rgba_color.h" + +#include "rtc_base/system/arch.h" + +namespace webrtc { + +namespace { + +bool AlphaEquals(uint8_t i, uint8_t j) { + // On Linux and Windows 8 or early version, '0' was returned for alpha channel + // from capturer APIs, on Windows 10, '255' was returned. So a workaround is + // to treat 0 as 255. + return i == j || ((i == 0 || i == 255) && (j == 0 || j == 255)); +} + +} // namespace + +RgbaColor::RgbaColor(uint8_t blue, uint8_t green, uint8_t red, uint8_t alpha) { + this->blue = blue; + this->green = green; + this->red = red; + this->alpha = alpha; +} + +RgbaColor::RgbaColor(uint8_t blue, uint8_t green, uint8_t red) + : RgbaColor(blue, green, red, 0xff) {} + +RgbaColor::RgbaColor(const uint8_t* bgra) + : RgbaColor(bgra[0], bgra[1], bgra[2], bgra[3]) {} + +RgbaColor::RgbaColor(uint32_t bgra) + : RgbaColor(reinterpret_cast<uint8_t*>(&bgra)) {} + +bool RgbaColor::operator==(const RgbaColor& right) const { + return blue == right.blue && green == right.green && red == right.red && + AlphaEquals(alpha, right.alpha); +} + +bool RgbaColor::operator!=(const RgbaColor& right) const { + return !(*this == right); +} + +uint32_t RgbaColor::ToUInt32() const { +#if defined(WEBRTC_ARCH_LITTLE_ENDIAN) + return blue | (green << 8) | (red << 16) | (alpha << 24); +#else + return (blue << 24) | (green << 16) | (red << 8) | alpha; +#endif +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/rgba_color.h b/third_party/libwebrtc/modules/desktop_capture/rgba_color.h new file mode 100644 index 0000000000..2b13430a45 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/rgba_color.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_RGBA_COLOR_H_ +#define MODULES_DESKTOP_CAPTURE_RGBA_COLOR_H_ + +#include <stdint.h> + +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +// A four-byte structure to store a color in BGRA format. This structure also +// provides functions to be created from uint8_t array, say, +// DesktopFrame::data(). It always uses BGRA order for internal storage to match +// DesktopFrame::data(). +struct RgbaColor final { + // Creates a color with BGRA channels. + RgbaColor(uint8_t blue, uint8_t green, uint8_t red, uint8_t alpha); + + // Creates a color with BGR channels, and set alpha channel to 255 (opaque). + RgbaColor(uint8_t blue, uint8_t green, uint8_t red); + + // Creates a color from four-byte in BGRA order, i.e. DesktopFrame::data(). + explicit RgbaColor(const uint8_t* bgra); + + // Creates a color from BGRA channels in a uint format. Consumers should make + // sure the memory order of the uint32_t is always BGRA from left to right, no + // matter the system endian. This function creates an equivalent RgbaColor + // instance from the ToUInt32() result of another RgbaColor instance. + explicit RgbaColor(uint32_t bgra); + + // Returns true if `this` and `right` is the same color. + bool operator==(const RgbaColor& right) const; + + // Returns true if `this` and `right` are different colors. + bool operator!=(const RgbaColor& right) const; + + uint32_t ToUInt32() const; + + uint8_t blue; + uint8_t green; + uint8_t red; + uint8_t alpha; +}; +static_assert( + DesktopFrame::kBytesPerPixel == sizeof(RgbaColor), + "A pixel in DesktopFrame should be safe to be represented by a RgbaColor"); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_RGBA_COLOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/rgba_color_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/rgba_color_unittest.cc new file mode 100644 index 0000000000..48e40594b1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/rgba_color_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/rgba_color.h" + +#include <cstdint> +#include <vector> + +#include "test/gtest.h" + +namespace webrtc { + +TEST(RgbaColorTest, ConvertFromAndToUInt32) { + static const std::vector<uint32_t> cases{ + 0, 1000, 2693, 3725, 4097, 12532, + 19902, 27002, 27723, 30944, 65535, 65536, + 231194, 255985, 322871, 883798, 9585200, 12410056, + 12641940, 30496970, 105735668, 110117847, 482769275, 542368468, + 798173396, 2678656711, 3231043200, UINT32_MAX, + }; + + for (uint32_t value : cases) { + RgbaColor left(value); + ASSERT_EQ(left.ToUInt32(), value); + RgbaColor right(left); + ASSERT_EQ(left.ToUInt32(), right.ToUInt32()); + } +} + +TEST(RgbaColorTest, AlphaChannelEquality) { + RgbaColor left(10, 10, 10, 0); + RgbaColor right(10, 10, 10, 255); + ASSERT_EQ(left, right); + right.alpha = 128; + ASSERT_NE(left, right); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capture_frame_queue.h b/third_party/libwebrtc/modules/desktop_capture/screen_capture_frame_queue.h new file mode 100644 index 0000000000..46e19da77e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capture_frame_queue.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ + +#include <memory> + +namespace webrtc { + +// Represents a queue of reusable video frames. Provides access to the 'current' +// frame - the frame that the caller is working with at the moment, and to the +// 'previous' frame - the predecessor of the current frame swapped by +// MoveToNextFrame() call, if any. +// +// The caller is expected to (re)allocate frames if current_frame() returns +// NULL. The caller can mark all frames in the queue for reallocation (when, +// say, frame dimensions change). The queue records which frames need updating +// which the caller can query. +// +// Frame consumer is expected to never hold more than kQueueLength frames +// created by this function and it should release the earliest one before trying +// to capture a new frame (i.e. before MoveToNextFrame() is called). +template <typename FrameType> +class ScreenCaptureFrameQueue { + public: + ScreenCaptureFrameQueue() = default; + ~ScreenCaptureFrameQueue() = default; + + ScreenCaptureFrameQueue(const ScreenCaptureFrameQueue&) = delete; + ScreenCaptureFrameQueue& operator=(const ScreenCaptureFrameQueue&) = delete; + + // Moves to the next frame in the queue, moving the 'current' frame to become + // the 'previous' one. + void MoveToNextFrame() { current_ = (current_ + 1) % kQueueLength; } + + // Replaces the current frame with a new one allocated by the caller. The + // existing frame (if any) is destroyed. Takes ownership of `frame`. + void ReplaceCurrentFrame(std::unique_ptr<FrameType> frame) { + frames_[current_] = std::move(frame); + } + + // Marks all frames obsolete and resets the previous frame pointer. No + // frames are freed though as the caller can still access them. + void Reset() { + for (int i = 0; i < kQueueLength; i++) { + frames_[i].reset(); + } + current_ = 0; + } + + FrameType* current_frame() const { return frames_[current_].get(); } + + FrameType* previous_frame() const { + return frames_[(current_ + kQueueLength - 1) % kQueueLength].get(); + } + + private: + // Index of the current frame. + int current_ = 0; + + static const int kQueueLength = 2; + std::unique_ptr<FrameType> frames_[kQueueLength]; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_darwin.mm b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_darwin.mm new file mode 100644 index 0000000000..d5a7bb0522 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_darwin.mm @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "modules/desktop_capture/mac/screen_capturer_mac.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + if (!options.configuration_monitor()) { + return nullptr; + } + + std::unique_ptr<ScreenCapturerMac> capturer(new ScreenCapturerMac( + options.configuration_monitor(), options.detect_updated_region(), options.allow_iosurface())); + if (!capturer.get()->Init()) { + return nullptr; + } + + return capturer; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc new file mode 100644 index 0000000000..c0ad841c05 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc @@ -0,0 +1,415 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_capturer_fuchsia.h" + +#include <fuchsia/sysmem/cpp/fidl.h> +#include <fuchsia/ui/composition/cpp/fidl.h> +#include <fuchsia/ui/scenic/cpp/fidl.h> +#include <lib/sys/cpp/component_context.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/divide_round.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { + +static constexpr uint32_t kMinBufferCount = 2; +static constexpr uint32_t kFuchsiaBytesPerPixel = 4; +static constexpr DesktopCapturer::SourceId kFuchsiaScreenId = 1; +// 500 milliseconds +static constexpr zx::duration kEventDelay = zx::msec(500); +static constexpr fuchsia::sysmem::ColorSpaceType kSRGBColorSpace = + fuchsia::sysmem::ColorSpaceType::SRGB; +static constexpr fuchsia::sysmem::PixelFormatType kBGRA32PixelFormatType = + fuchsia::sysmem::PixelFormatType::BGRA32; + +// Round |value| up to the closest multiple of |multiple| +size_t RoundUpToMultiple(size_t value, size_t multiple) { + return DivideRoundUp(value, multiple) * multiple; +} + +} // namespace + +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + if (ScreenCapturerFuchsia::CheckRequirements()) { + std::unique_ptr<ScreenCapturerFuchsia> capturer( + new ScreenCapturerFuchsia()); + return capturer; + } + return nullptr; +} + +ScreenCapturerFuchsia::ScreenCapturerFuchsia() + : component_context_( + sys::ComponentContext::CreateAndServeOutgoingDirectory()) { + RTC_DCHECK(CheckRequirements()); +} + +ScreenCapturerFuchsia::~ScreenCapturerFuchsia() { + // unmap virtual memory mapped pointers + uint32_t virt_mem_bytes = + buffer_collection_info_.settings.buffer_settings.size_bytes; + for (uint32_t buffer_index = 0; + buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { + uintptr_t address = + reinterpret_cast<uintptr_t>(virtual_memory_mapped_addrs_[buffer_index]); + zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes); + RTC_DCHECK(status == ZX_OK); + } +} + +// TODO(fxbug.dev/100303): Remove this function when Flatland is the only API. +bool ScreenCapturerFuchsia::CheckRequirements() { + std::unique_ptr<sys::ComponentContext> component_context = + sys::ComponentContext::CreateAndServeOutgoingDirectory(); + fuchsia::ui::scenic::ScenicSyncPtr scenic; + zx_status_t status = component_context->svc()->Connect(scenic.NewRequest()); + if (status != ZX_OK) { + RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status; + return false; + } + + bool scenic_uses_flatland = false; + scenic->UsesFlatland(&scenic_uses_flatland); + if (!scenic_uses_flatland) { + RTC_LOG(LS_ERROR) << "Screen capture not supported without Flatland."; + } + + return scenic_uses_flatland; +} + +void ScreenCapturerFuchsia::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + callback_ = callback; + + fatal_error_ = false; + + SetupBuffers(); +} + +void ScreenCapturerFuchsia::CaptureFrame() { + if (fatal_error_) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + zx::event event; + zx::event dup; + zx_status_t status = zx::event::create(0, &event); + if (status != ZX_OK) { + RTC_LOG(LS_ERROR) << "Failed to create event: " << status; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); + + fuchsia::ui::composition::GetNextFrameArgs next_frame_args; + next_frame_args.set_event(std::move(dup)); + + fuchsia::ui::composition::ScreenCapture_GetNextFrame_Result result; + screen_capture_->GetNextFrame(std::move(next_frame_args), &result); + if (result.is_err()) { + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.GetNextFrame() failed: " + << result.err() << "\n"; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay), + nullptr); + if (status != ZX_OK) { + RTC_LOG(LS_ERROR) << "Timed out waiting for ScreenCapture to render frame: " + << status; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + uint32_t buffer_index = result.response().buffer_id(); + + // TODO(bugs.webrtc.org/14097): Use SharedMemoryDesktopFrame and + // ScreenCaptureFrameQueue + std::unique_ptr<BasicDesktopFrame> frame( + new BasicDesktopFrame(DesktopSize(width_, height_))); + + uint32_t pixels_per_row = GetPixelsPerRow( + buffer_collection_info_.settings.image_format_constraints); + uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row; + frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride, + DesktopRect::MakeWH(width_, height_)); + + fuchsia::ui::composition::ScreenCapture_ReleaseFrame_Result release_result; + screen_capture_->ReleaseFrame(buffer_index, &release_result); + if (release_result.is_err()) { + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.ReleaseFrame() failed: " + << release_result.err(); + } + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + frame->set_capture_time_ms(capture_time_ms); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool ScreenCapturerFuchsia::GetSourceList(SourceList* screens) { + RTC_DCHECK(screens->size() == 0); + // Fuchsia only supports single monitor display at this point + screens->push_back({kFuchsiaScreenId, std::string("Fuchsia monitor")}); + return true; +} + +bool ScreenCapturerFuchsia::SelectSource(SourceId id) { + if (id == kFuchsiaScreenId || id == kFullDesktopScreenId) { + return true; + } + return false; +} + +fuchsia::sysmem::BufferCollectionConstraints +ScreenCapturerFuchsia::GetBufferConstraints() { + fuchsia::sysmem::BufferCollectionConstraints constraints; + constraints.usage.cpu = + fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite; + constraints.min_buffer_count = kMinBufferCount; + + constraints.has_buffer_memory_constraints = true; + constraints.buffer_memory_constraints.ram_domain_supported = true; + constraints.buffer_memory_constraints.cpu_domain_supported = true; + + constraints.image_format_constraints_count = 1; + fuchsia::sysmem::ImageFormatConstraints& image_constraints = + constraints.image_format_constraints[0]; + image_constraints.color_spaces_count = 1; + image_constraints.color_space[0] = + fuchsia::sysmem::ColorSpace{.type = kSRGBColorSpace}; + image_constraints.pixel_format.type = kBGRA32PixelFormatType; + image_constraints.pixel_format.has_format_modifier = true; + image_constraints.pixel_format.format_modifier.value = + fuchsia::sysmem::FORMAT_MODIFIER_LINEAR; + + image_constraints.required_min_coded_width = width_; + image_constraints.required_min_coded_height = height_; + image_constraints.required_max_coded_width = width_; + image_constraints.required_max_coded_height = height_; + + image_constraints.bytes_per_row_divisor = kFuchsiaBytesPerPixel; + + return constraints; +} + +void ScreenCapturerFuchsia::SetupBuffers() { + fuchsia::ui::scenic::ScenicSyncPtr scenic; + zx_status_t status = component_context_->svc()->Connect(scenic.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status; + return; + } + + fuchsia::ui::gfx::DisplayInfo display_info; + status = scenic->GetDisplayInfo(&display_info); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to get display dimensions: " + << status; + return; + } + width_ = display_info.width_in_px; + height_ = display_info.height_in_px; + + status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Sysmem Allocator: " << status; + return; + } + + fuchsia::sysmem::BufferCollectionTokenSyncPtr sysmem_token; + status = + sysmem_allocator_->AllocateSharedCollection(sysmem_token.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.Allocator.AllocateSharedCollection() failed: " + << status; + return; + } + + fuchsia::sysmem::BufferCollectionTokenSyncPtr flatland_token; + status = sysmem_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, + flatland_token.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.BufferCollectionToken.Duplicate() failed: " + << status; + return; + } + + status = sysmem_token->Sync(); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "fuchsia.sysmem.BufferCollectionToken.Sync() failed: " + << status; + return; + } + + status = sysmem_allocator_->BindSharedCollection(std::move(sysmem_token), + collection_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.Allocator.BindSharedCollection() failed: " << status; + return; + } + + status = collection_->SetConstraints(/*has_constraints=*/true, + GetBufferConstraints()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.BufferCollection.SetConstraints() failed: " + << status; + return; + } + + fuchsia::ui::composition::BufferCollectionImportToken import_token; + fuchsia::ui::composition::BufferCollectionExportToken export_token; + status = zx::eventpair::create(0, &export_token.value, &import_token.value); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "Failed to create BufferCollection import and export tokens: " + << status; + return; + } + + status = component_context_->svc()->Connect(flatland_allocator_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Flatland Allocator: " << status; + return; + } + + fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args; + buffer_collection_args.set_export_token(std::move(export_token)); + buffer_collection_args.set_buffer_collection_token(std::move(flatland_token)); + buffer_collection_args.set_usage( + fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT); + + fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result + buffer_collection_result; + flatland_allocator_->RegisterBufferCollection( + std::move(buffer_collection_args), &buffer_collection_result); + if (buffer_collection_result.is_err()) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.Allocator." + "RegisterBufferCollection() failed."; + return; + } + + zx_status_t allocation_status; + status = collection_->WaitForBuffersAllocated(&allocation_status, + &buffer_collection_info_); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: " + << status; + return; + } + if (allocation_status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection: " << status; + return; + } + status = collection_->Close(); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status; + return; + } + + status = component_context_->svc()->Connect(screen_capture_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Screen Capture: " << status; + return; + } + + // Configure buffers in ScreenCapture client. + fuchsia::ui::composition::ScreenCaptureConfig configure_args; + configure_args.set_import_token(std::move(import_token)); + configure_args.set_buffer_count(buffer_collection_info_.buffer_count); + configure_args.set_size({width_, height_}); + + fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result; + screen_capture_->Configure(std::move(configure_args), &configure_result); + if (configure_result.is_err()) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.ui.composition.ScreenCapture.Configure() failed: " + << configure_result.err(); + return; + } + + // We have a collection of virtual memory objects which the ScreenCapture + // client will write the frame data to when requested. We map each of these + // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to + // access this data. + uint32_t virt_mem_bytes = + buffer_collection_info_.settings.buffer_settings.size_bytes; + RTC_DCHECK(virt_mem_bytes > 0); + for (uint32_t buffer_index = 0; + buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { + const zx::vmo& virt_mem = buffer_collection_info_.buffers[buffer_index].vmo; + virtual_memory_mapped_addrs_[buffer_index] = nullptr; + auto status = zx::vmar::root_self()->map( + ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem, + /*vmo_offset*/ 0, virt_mem_bytes, + reinterpret_cast<uintptr_t*>( + &virtual_memory_mapped_addrs_[buffer_index])); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to map virtual memory: " << status; + return; + } + } +} + +uint32_t ScreenCapturerFuchsia::GetPixelsPerRow( + const fuchsia::sysmem::ImageFormatConstraints& constraints) { + uint32_t stride = RoundUpToMultiple( + std::max(constraints.min_bytes_per_row, width_ * kFuchsiaBytesPerPixel), + constraints.bytes_per_row_divisor); + uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel; + + return pixels_per_row; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h new file mode 100644 index 0000000000..444930963f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h @@ -0,0 +1,65 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ + +#include <fuchsia/sysmem/cpp/fidl.h> +#include <fuchsia/ui/composition/cpp/fidl.h> +#include <lib/sys/cpp/component_context.h> + +#include <memory> +#include <unordered_map> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +class ScreenCapturerFuchsia final : public DesktopCapturer { + public: + ScreenCapturerFuchsia(); + ~ScreenCapturerFuchsia() override; + + static bool CheckRequirements(); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + + private: + fuchsia::sysmem::BufferCollectionConstraints GetBufferConstraints(); + void SetupBuffers(); + uint32_t GetPixelsPerRow( + const fuchsia::sysmem::ImageFormatConstraints& constraints); + + Callback* callback_ = nullptr; + + std::unique_ptr<sys::ComponentContext> component_context_; + fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator_; + fuchsia::ui::composition::AllocatorSyncPtr flatland_allocator_; + fuchsia::ui::composition::ScreenCaptureSyncPtr screen_capture_; + fuchsia::sysmem::BufferCollectionSyncPtr collection_; + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info_; + std::unordered_map<uint32_t, uint8_t*> virtual_memory_mapped_addrs_; + + bool fatal_error_; + + // Dimensions of the screen we are capturing + uint32_t width_; + uint32_t height_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.cc new file mode 100644 index 0000000000..f8261a90b0 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.cc @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_capturer_helper.h" + + +namespace webrtc { + +void ScreenCapturerHelper::ClearInvalidRegion() { + MutexLock scoped_invalid_region_lock(&invalid_region_mutex_); + invalid_region_.Clear(); +} + +void ScreenCapturerHelper::InvalidateRegion( + const DesktopRegion& invalid_region) { + MutexLock scoped_invalid_region_lock(&invalid_region_mutex_); + invalid_region_.AddRegion(invalid_region); +} + +void ScreenCapturerHelper::InvalidateScreen(const DesktopSize& size) { + MutexLock scoped_invalid_region_lock(&invalid_region_mutex_); + invalid_region_.AddRect(DesktopRect::MakeSize(size)); +} + +void ScreenCapturerHelper::TakeInvalidRegion(DesktopRegion* invalid_region) { + invalid_region->Clear(); + + { + MutexLock scoped_invalid_region_lock(&invalid_region_mutex_); + invalid_region->Swap(&invalid_region_); + } + + if (log_grid_size_ > 0) { + DesktopRegion expanded_region; + ExpandToGrid(*invalid_region, log_grid_size_, &expanded_region); + expanded_region.Swap(invalid_region); + + invalid_region->IntersectWith(DesktopRect::MakeSize(size_most_recent_)); + } +} + +void ScreenCapturerHelper::SetLogGridSize(int log_grid_size) { + log_grid_size_ = log_grid_size; +} + +const DesktopSize& ScreenCapturerHelper::size_most_recent() const { + return size_most_recent_; +} + +void ScreenCapturerHelper::set_size_most_recent(const DesktopSize& size) { + size_most_recent_ = size; +} + +// Returns the largest multiple of `n` that is <= `x`. +// `n` must be a power of 2. `nMask` is ~(`n` - 1). +static int DownToMultiple(int x, int nMask) { + return (x & nMask); +} + +// Returns the smallest multiple of `n` that is >= `x`. +// `n` must be a power of 2. `nMask` is ~(`n` - 1). +static int UpToMultiple(int x, int n, int nMask) { + return ((x + n - 1) & nMask); +} + +void ScreenCapturerHelper::ExpandToGrid(const DesktopRegion& region, + int log_grid_size, + DesktopRegion* result) { + RTC_DCHECK_GE(log_grid_size, 1); + int grid_size = 1 << log_grid_size; + int grid_size_mask = ~(grid_size - 1); + + result->Clear(); + for (DesktopRegion::Iterator it(region); !it.IsAtEnd(); it.Advance()) { + int left = DownToMultiple(it.rect().left(), grid_size_mask); + int right = UpToMultiple(it.rect().right(), grid_size, grid_size_mask); + int top = DownToMultiple(it.rect().top(), grid_size_mask); + int bottom = UpToMultiple(it.rect().bottom(), grid_size, grid_size_mask); + result->AddRect(DesktopRect::MakeLTRB(left, top, right, bottom)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.h b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.h new file mode 100644 index 0000000000..cd7fa689c0 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ + +#include <memory> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +// ScreenCapturerHelper is intended to be used by an implementation of the +// ScreenCapturer interface. It maintains a thread-safe invalid region, and +// the size of the most recently captured screen, on behalf of the +// ScreenCapturer that owns it. +class ScreenCapturerHelper { + public: + ScreenCapturerHelper() = default; + ~ScreenCapturerHelper() = default; + + ScreenCapturerHelper(const ScreenCapturerHelper&) = delete; + ScreenCapturerHelper& operator=(const ScreenCapturerHelper&) = delete; + + // Clear out the invalid region. + void ClearInvalidRegion(); + + // Invalidate the specified region. + void InvalidateRegion(const DesktopRegion& invalid_region); + + // Invalidate the entire screen, of a given size. + void InvalidateScreen(const DesktopSize& size); + + // Copies current invalid region to `invalid_region` clears invalid region + // storage for the next frame. + void TakeInvalidRegion(DesktopRegion* invalid_region); + + // Access the size of the most recently captured screen. + const DesktopSize& size_most_recent() const; + void set_size_most_recent(const DesktopSize& size); + + // Lossy compression can result in color values leaking between pixels in one + // block. If part of a block changes, then unchanged parts of that block can + // be changed in the compressed output. So we need to re-render an entire + // block whenever part of the block changes. + // + // If `log_grid_size` is >= 1, then this function makes TakeInvalidRegion() + // produce an invalid region expanded so that its vertices lie on a grid of + // size 2 ^ `log_grid_size`. The expanded region is then clipped to the size + // of the most recently captured screen, as previously set by + // set_size_most_recent(). + // If `log_grid_size` is <= 0, then the invalid region is not expanded. + void SetLogGridSize(int log_grid_size); + + // Expands a region so that its vertices all lie on a grid. + // The grid size must be >= 2, so `log_grid_size` must be >= 1. + static void ExpandToGrid(const DesktopRegion& region, + int log_grid_size, + DesktopRegion* result); + + private: + // A region that has been manually invalidated (through InvalidateRegion). + // These will be returned as dirty_region in the capture data during the next + // capture. + DesktopRegion invalid_region_ RTC_GUARDED_BY(invalid_region_mutex_); + + // A lock protecting `invalid_region_` across threads. + Mutex invalid_region_mutex_; + + // The size of the most recently captured screen. + DesktopSize size_most_recent_; + + // The log (base 2) of the size of the grid to which the invalid region is + // expanded. + // If the value is <= 0, then the invalid region is not expanded to a grid. + int log_grid_size_ = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc new file mode 100644 index 0000000000..165bbe42de --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_capturer_helper.h" + +#include "test/gtest.h" + +namespace webrtc { + +class ScreenCapturerHelperTest : public ::testing::Test { + protected: + ScreenCapturerHelper capturer_helper_; +}; + +TEST_F(ScreenCapturerHelperTest, ClearInvalidRegion) { + DesktopRegion region(DesktopRect::MakeXYWH(1, 2, 3, 4)); + capturer_helper_.InvalidateRegion(region); + capturer_helper_.ClearInvalidRegion(); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(region.is_empty()); +} + +TEST_F(ScreenCapturerHelperTest, InvalidateRegion) { + DesktopRegion region; + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(region.is_empty()); + + region.SetRect(DesktopRect::MakeXYWH(1, 2, 3, 4)); + capturer_helper_.InvalidateRegion(region); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4)).Equals(region)); + + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4))); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(4, 2, 3, 4))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 6, 4)).Equals(region)); +} + +TEST_F(ScreenCapturerHelperTest, InvalidateScreen) { + DesktopRegion region; + capturer_helper_.InvalidateScreen(DesktopSize(12, 34)); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeWH(12, 34)).Equals(region)); +} + +TEST_F(ScreenCapturerHelperTest, SizeMostRecent) { + EXPECT_TRUE(capturer_helper_.size_most_recent().is_empty()); + capturer_helper_.set_size_most_recent(DesktopSize(12, 34)); + EXPECT_TRUE(DesktopSize(12, 34).equals(capturer_helper_.size_most_recent())); +} + +TEST_F(ScreenCapturerHelperTest, SetLogGridSize) { + capturer_helper_.set_size_most_recent(DesktopSize(10, 10)); + + DesktopRegion region; + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion().Equals(region)); + + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(-1); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(0); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(1); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(6, 6, 2, 2)).Equals(region)); + + capturer_helper_.SetLogGridSize(2); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(4, 4, 4, 4)).Equals(region)); + + capturer_helper_.SetLogGridSize(0); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); +} + +void TestExpandRegionToGrid(const DesktopRegion& region, + int log_grid_size, + const DesktopRegion& expanded_region_expected) { + DesktopRegion expanded_region1; + ScreenCapturerHelper::ExpandToGrid(region, log_grid_size, &expanded_region1); + EXPECT_TRUE(expanded_region_expected.Equals(expanded_region1)); + + DesktopRegion expanded_region2; + ScreenCapturerHelper::ExpandToGrid(expanded_region1, log_grid_size, + &expanded_region2); + EXPECT_TRUE(expanded_region1.Equals(expanded_region2)); +} + +void TestExpandRectToGrid(int l, + int t, + int r, + int b, + int log_grid_size, + int lExpanded, + int tExpanded, + int rExpanded, + int bExpanded) { + TestExpandRegionToGrid(DesktopRegion(DesktopRect::MakeLTRB(l, t, r, b)), + log_grid_size, + DesktopRegion(DesktopRect::MakeLTRB( + lExpanded, tExpanded, rExpanded, bExpanded))); +} + +TEST_F(ScreenCapturerHelperTest, ExpandToGrid) { + const int kLogGridSize = 4; + const int kGridSize = 1 << kLogGridSize; + for (int i = -2; i <= 2; i++) { + int x = i * kGridSize; + for (int j = -2; j <= 2; j++) { + int y = j * kGridSize; + TestExpandRectToGrid(x + 0, y + 0, x + 1, y + 1, kLogGridSize, x + 0, + y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x + 0, y + kGridSize - 1, x + 1, y + kGridSize, + kLogGridSize, x + 0, y + 0, x + kGridSize, + y + kGridSize); + TestExpandRectToGrid(x + kGridSize - 1, y + kGridSize - 1, x + kGridSize, + y + kGridSize, kLogGridSize, x + 0, y + 0, + x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x + kGridSize - 1, y + 0, x + kGridSize, y + 1, + kLogGridSize, x + 0, y + 0, x + kGridSize, + y + kGridSize); + TestExpandRectToGrid(x - 1, y + 0, x + 1, y + 1, kLogGridSize, + x - kGridSize, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x - 1, y - 1, x + 1, y + 0, kLogGridSize, + x - kGridSize, y - kGridSize, x + kGridSize, y); + TestExpandRectToGrid(x + 0, y - 1, x + 1, y + 1, kLogGridSize, x, + y - kGridSize, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x - 1, y - 1, x + 0, y + 1, kLogGridSize, + x - kGridSize, y - kGridSize, x, y + kGridSize); + + // Construct a region consisting of 3 pixels and verify that it's expanded + // properly to 3 squares that are kGridSize by kGridSize. + for (int q = 0; q < 4; ++q) { + DesktopRegion region; + DesktopRegion expanded_region_expected; + + if (q != 0) { + region.AddRect(DesktopRect::MakeXYWH(x - 1, y - 1, 1, 1)); + expanded_region_expected.AddRect(DesktopRect::MakeXYWH( + x - kGridSize, y - kGridSize, kGridSize, kGridSize)); + } + if (q != 1) { + region.AddRect(DesktopRect::MakeXYWH(x, y - 1, 1, 1)); + expanded_region_expected.AddRect( + DesktopRect::MakeXYWH(x, y - kGridSize, kGridSize, kGridSize)); + } + if (q != 2) { + region.AddRect(DesktopRect::MakeXYWH(x - 1, y, 1, 1)); + expanded_region_expected.AddRect( + DesktopRect::MakeXYWH(x - kGridSize, y, kGridSize, kGridSize)); + } + if (q != 3) { + region.AddRect(DesktopRect::MakeXYWH(x, y, 1, 1)); + expanded_region_expected.AddRect( + DesktopRect::MakeXYWH(x, y, kGridSize, kGridSize)); + } + + TestExpandRegionToGrid(region, kLogGridSize, expanded_region_expected); + } + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_integration_test.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_integration_test.cc new file mode 100644 index 0000000000..b33427ad42 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_integration_test.cc @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <string.h> + +#include <algorithm> +#include <initializer_list> +#include <iostream> // TODO(zijiehe): Remove once flaky has been resolved. +#include <memory> +#include <utility> + +// TODO(zijiehe): Remove once flaky has been resolved. +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mock_desktop_capturer_callback.h" +#include "modules/desktop_capture/rgba_color.h" +#include "modules/desktop_capture/screen_drawer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/third_party/base64/base64.h" +#include "test/gmock.h" +#include "test/gtest.h" + +#if defined(WEBRTC_WIN) +#include "modules/desktop_capture/win/screen_capturer_win_directx.h" +#include "rtc_base/win/windows_version.h" +#endif // defined(WEBRTC_WIN) + +using ::testing::_; + +namespace webrtc { + +namespace { + +ACTION_P2(SaveCaptureResult, result, dest) { + *result = arg0; + *dest = std::move(*arg1); +} + +// Returns true if color in `rect` of `frame` is `color`. +bool ArePixelsColoredBy(const DesktopFrame& frame, + DesktopRect rect, + RgbaColor color, + bool may_partially_draw) { + if (!may_partially_draw) { + // updated_region() should cover the painted area. + DesktopRegion updated_region(frame.updated_region()); + updated_region.IntersectWith(rect); + if (!updated_region.Equals(DesktopRegion(rect))) { + return false; + } + } + + // Color in the `rect` should be `color`. + uint8_t* row = frame.GetFrameDataAtPos(rect.top_left()); + for (int i = 0; i < rect.height(); i++) { + uint8_t* column = row; + for (int j = 0; j < rect.width(); j++) { + if (color != RgbaColor(column)) { + return false; + } + column += DesktopFrame::kBytesPerPixel; + } + row += frame.stride(); + } + return true; +} + +} // namespace + +class ScreenCapturerIntegrationTest : public ::testing::Test { + public: + void SetUp() override { + capturer_ = DesktopCapturer::CreateScreenCapturer( + DesktopCaptureOptions::CreateDefault()); + } + + protected: + void TestCaptureUpdatedRegion( + std::initializer_list<DesktopCapturer*> capturers) { + RTC_DCHECK(capturers.size() > 0); +// A large enough area for the tests, which should be able to be fulfilled +// by most systems. +#if defined(WEBRTC_WIN) + // On Windows, an interesting warning window may pop up randomly. The root + // cause is still under investigation, so reduce the test area to work + // around. Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6666. + const int kTestArea = 416; +#else + const int kTestArea = 512; +#endif + const int kRectSize = 32; + std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create(); + if (!drawer || drawer->DrawableRegion().is_empty()) { + RTC_LOG(LS_WARNING) + << "No ScreenDrawer implementation for current platform."; + return; + } + if (drawer->DrawableRegion().width() < kTestArea || + drawer->DrawableRegion().height() < kTestArea) { + RTC_LOG(LS_WARNING) + << "ScreenDrawer::DrawableRegion() is too small for the " + "CaptureUpdatedRegion tests."; + return; + } + + for (DesktopCapturer* capturer : capturers) { + capturer->Start(&callback_); + } + + // Draw a set of `kRectSize` by `kRectSize` rectangles at (`i`, `i`), or + // `i` by `i` rectangles at (`kRectSize`, `kRectSize`). One of (controlled + // by `c`) its primary colors is `i`, and the other two are 0x7f. So we + // won't draw a black or white rectangle. + for (int c = 0; c < 3; c++) { + // A fixed size rectangle. + for (int i = 0; i < kTestArea - kRectSize; i += 16) { + DesktopRect rect = DesktopRect::MakeXYWH(i, i, kRectSize, kRectSize); + rect.Translate(drawer->DrawableRegion().top_left()); + RgbaColor color((c == 0 ? (i & 0xff) : 0x7f), + (c == 1 ? (i & 0xff) : 0x7f), + (c == 2 ? (i & 0xff) : 0x7f)); + // Fail fast. + ASSERT_NO_FATAL_FAILURE( + TestCaptureOneFrame(capturers, drawer.get(), rect, color)); + } + + // A variable-size rectangle. + for (int i = 0; i < kTestArea - kRectSize; i += 16) { + DesktopRect rect = DesktopRect::MakeXYWH(kRectSize, kRectSize, i, i); + rect.Translate(drawer->DrawableRegion().top_left()); + RgbaColor color((c == 0 ? (i & 0xff) : 0x7f), + (c == 1 ? (i & 0xff) : 0x7f), + (c == 2 ? (i & 0xff) : 0x7f)); + // Fail fast. + ASSERT_NO_FATAL_FAILURE( + TestCaptureOneFrame(capturers, drawer.get(), rect, color)); + } + } + } + + void TestCaptureUpdatedRegion() { + TestCaptureUpdatedRegion({capturer_.get()}); + } + +#if defined(WEBRTC_WIN) + // Enable allow_directx_capturer in DesktopCaptureOptions, but let + // DesktopCapturer::CreateScreenCapturer() to decide whether a DirectX + // capturer should be used. + void MaybeCreateDirectxCapturer() { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_directx_capturer(true); + capturer_ = DesktopCapturer::CreateScreenCapturer(options); + } + + bool CreateDirectxCapturer() { + if (!ScreenCapturerWinDirectx::IsSupported()) { + RTC_LOG(LS_WARNING) << "Directx capturer is not supported"; + return false; + } + + MaybeCreateDirectxCapturer(); + return true; + } + + void CreateMagnifierCapturer() { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_use_magnification_api(true); + capturer_ = DesktopCapturer::CreateScreenCapturer(options); + } +#endif // defined(WEBRTC_WIN) + + std::unique_ptr<DesktopCapturer> capturer_; + MockDesktopCapturerCallback callback_; + + private: + // Repeats capturing the frame by using `capturers` one-by-one for 600 times, + // typically 30 seconds, until they succeeded captured a `color` rectangle at + // `rect`. This function uses `drawer`->WaitForPendingDraws() between two + // attempts to wait for the screen to update. + void TestCaptureOneFrame(std::vector<DesktopCapturer*> capturers, + ScreenDrawer* drawer, + DesktopRect rect, + RgbaColor color) { + const int wait_capture_round = 600; + drawer->Clear(); + size_t succeeded_capturers = 0; + for (int i = 0; i < wait_capture_round; i++) { + drawer->DrawRectangle(rect, color); + drawer->WaitForPendingDraws(); + for (size_t j = 0; j < capturers.size(); j++) { + if (capturers[j] == nullptr) { + // DesktopCapturer should return an empty updated_region() if no + // update detected. So we won't test it again if it has captured the + // rectangle we drew. + continue; + } + std::unique_ptr<DesktopFrame> frame = CaptureFrame(capturers[j]); + if (!frame) { + // CaptureFrame() has triggered an assertion failure already, we only + // need to return here. + return; + } + + if (ArePixelsColoredBy(*frame, rect, color, + drawer->MayDrawIncompleteShapes())) { + capturers[j] = nullptr; + succeeded_capturers++; + } + // The following else if statement is for debugging purpose only, which + // should be removed after flaky of ScreenCapturerIntegrationTest has + // been resolved. + else if (i == wait_capture_round - 1) { + std::string result; + rtc::Base64::EncodeFromArray( + frame->data(), frame->size().height() * frame->stride(), &result); + std::cout << frame->size().width() << " x " << frame->size().height() + << std::endl; + // Split the entire string (can be over 4M) into several lines to + // avoid browser from sticking. + static const size_t kLineLength = 32768; + const char* result_end = result.c_str() + result.length(); + for (const char* it = result.c_str(); it < result_end; + it += kLineLength) { + const size_t max_length = result_end - it; + std::cout << std::string(it, std::min(kLineLength, max_length)) + << std::endl; + } + std::cout << "Failed to capture rectangle " << rect.left() << " x " + << rect.top() << " - " << rect.right() << " x " + << rect.bottom() << " with color (" + << static_cast<int>(color.red) << ", " + << static_cast<int>(color.green) << ", " + << static_cast<int>(color.blue) << ", " + << static_cast<int>(color.alpha) << ")" << std::endl; + ASSERT_TRUE(false) << "ScreenCapturerIntegrationTest may be flaky. " + "Please kindly FYI the broken link to " + "zijiehe@chromium.org for investigation. If " + "the failure continually happens, but I have " + "not responded as quick as expected, disable " + "*all* tests in " + "screen_capturer_integration_test.cc to " + "unblock other developers."; + } + } + + if (succeeded_capturers == capturers.size()) { + break; + } + } + + ASSERT_EQ(succeeded_capturers, capturers.size()); + } + + // Expects `capturer` to successfully capture a frame, and returns it. + std::unique_ptr<DesktopFrame> CaptureFrame(DesktopCapturer* capturer) { + for (int i = 0; i < 10; i++) { + std::unique_ptr<DesktopFrame> frame; + DesktopCapturer::Result result; + EXPECT_CALL(callback_, OnCaptureResultPtr(_, _)) + .WillOnce(SaveCaptureResult(&result, &frame)); + capturer->CaptureFrame(); + ::testing::Mock::VerifyAndClearExpectations(&callback_); + if (result == DesktopCapturer::Result::SUCCESS) { + EXPECT_TRUE(frame); + return frame; + } else { + EXPECT_FALSE(frame); + } + } + + EXPECT_TRUE(false); + return nullptr; + } +}; + +#if defined(WEBRTC_WIN) +// ScreenCapturerWinGdi randomly returns blank screen, the root cause is still +// unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843. +#define MAYBE_CaptureUpdatedRegion DISABLED_CaptureUpdatedRegion +#else +#define MAYBE_CaptureUpdatedRegion CaptureUpdatedRegion +#endif +TEST_F(ScreenCapturerIntegrationTest, MAYBE_CaptureUpdatedRegion) { + TestCaptureUpdatedRegion(); +} + +#if defined(WEBRTC_WIN) +// ScreenCapturerWinGdi randomly returns blank screen, the root cause is still +// unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843. +#define MAYBE_TwoCapturers DISABLED_TwoCapturers +#else +#define MAYBE_TwoCapturers TwoCapturers +#endif +TEST_F(ScreenCapturerIntegrationTest, MAYBE_TwoCapturers) { + std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_); + SetUp(); + TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()}); +} + +#if defined(WEBRTC_WIN) + +// Windows cannot capture contents on VMs hosted in GCE. See bug +// https://bugs.chromium.org/p/webrtc/issues/detail?id=8153. +TEST_F(ScreenCapturerIntegrationTest, + DISABLED_CaptureUpdatedRegionWithDirectxCapturer) { + if (!CreateDirectxCapturer()) { + return; + } + + TestCaptureUpdatedRegion(); +} + +TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoDirectxCapturers) { + if (!CreateDirectxCapturer()) { + return; + } + + std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_); + RTC_CHECK(CreateDirectxCapturer()); + TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()}); +} + +TEST_F(ScreenCapturerIntegrationTest, + DISABLED_CaptureUpdatedRegionWithMagnifierCapturer) { + // On Windows 8 or later, magnifier APIs return a frame with a border on test + // environment, so disable these tests. + // Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844 + // TODO(zijiehe): Find the root cause of the border and failure, which cannot + // reproduce on my dev machine. + if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) { + return; + } + CreateMagnifierCapturer(); + TestCaptureUpdatedRegion(); +} + +TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoMagnifierCapturers) { + // On Windows 8 or later, magnifier APIs return a frame with a border on test + // environment, so disable these tests. + // Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844 + // TODO(zijiehe): Find the root cause of the border and failure, which cannot + // reproduce on my dev machine. + if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) { + return; + } + CreateMagnifierCapturer(); + std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_); + CreateMagnifierCapturer(); + TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()}); +} + +TEST_F(ScreenCapturerIntegrationTest, + DISABLED_MaybeCaptureUpdatedRegionWithDirectxCapturer) { + if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8) { + // ScreenCapturerWinGdi randomly returns blank screen, the root cause is + // still unknown. Bug, + // https://bugs.chromium.org/p/webrtc/issues/detail?id=6843. + // On Windows 7 or early version, MaybeCreateDirectxCapturer() always + // creates GDI capturer. + return; + } + // Even DirectX capturer is not supported in current system, we should be able + // to select a usable capturer. + MaybeCreateDirectxCapturer(); + TestCaptureUpdatedRegion(); +} + +#endif // defined(WEBRTC_WIN) + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_linux.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_linux.cc new file mode 100644 index 0000000000..d9f2795130 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_linux.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" + +#if defined(WEBRTC_USE_PIPEWIRE) +#if defined(WEBRTC_MOZILLA_BUILD) +#include "modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h" +#else +#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h" +#endif +#endif // defined(WEBRTC_USE_PIPEWIRE) + +#if defined(WEBRTC_USE_X11) +#include "modules/desktop_capture/linux/x11/screen_capturer_x11.h" +#endif // defined(WEBRTC_USE_X11) + +namespace webrtc { + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { +#if defined(WEBRTC_USE_PIPEWIRE) + if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) { +#if defined(WEBRTC_MOZILLA_BUILD) + return BaseCapturerPipeWire::CreateRawCapturer(options); +#else + return std::make_unique<BaseCapturerPipeWire>(options); +#endif + } +#endif // defined(WEBRTC_USE_PIPEWIRE) + +#if defined(WEBRTC_USE_X11) + return ScreenCapturerX11::CreateRawScreenCapturer(options); +#else + return nullptr; +#endif // defined(WEBRTC_USE_X11) +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac.mm b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac.mm new file mode 100644 index 0000000000..285086ffa6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac.mm @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <stddef.h> + +#include <memory> +#include <set> +#include <utility> + +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <CoreGraphics/CoreGraphics.h> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/scoped_pixel_buffer_object.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/constructormagic.h" +#include "rtc_base/logging.h" +#include "rtc_base/macutils.h" +#include "rtc_base/timeutils.h" + +namespace webrtc { + +namespace { + +// CGDisplayStreamRefs need to be destroyed asynchronously after receiving a +// kCGDisplayStreamFrameStatusStopped callback from CoreGraphics. This may +// happen after the ScreenCapturerMac has been destroyed. DisplayStreamManager +// is responsible for destroying all extant CGDisplayStreamRefs, and will +// destroy itself once it's done. +class DisplayStreamManager { + public: + int GetUniqueId() { return ++unique_id_generator_; } + void DestroyStream(int unique_id) { + auto it = display_stream_wrappers_.find(unique_id); + RTC_CHECK(it != display_stream_wrappers_.end()); + RTC_CHECK(!it->second.active); + CFRelease(it->second.stream); + display_stream_wrappers_.erase(it); + + if (ready_for_self_destruction_ && display_stream_wrappers_.empty()) + delete this; + } + + void SaveStream(int unique_id, + CGDisplayStreamRef stream) { + RTC_CHECK(unique_id <= unique_id_generator_); + DisplayStreamWrapper wrapper; + wrapper.stream = stream; + display_stream_wrappers_[unique_id] = wrapper; + } + + void UnregisterActiveStreams() { + for (auto& pair : display_stream_wrappers_) { + DisplayStreamWrapper& wrapper = pair.second; + if (wrapper.active) { + wrapper.active = false; + CFRunLoopSourceRef source = + CGDisplayStreamGetRunLoopSource(wrapper.stream); + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, + kCFRunLoopCommonModes); + CGDisplayStreamStop(wrapper.stream); + } + } + } + + void PrepareForSelfDestruction() { + ready_for_self_destruction_ = true; + + if (display_stream_wrappers_.empty()) + delete this; + } + + // Once the DisplayStreamManager is ready for destruction, the + // ScreenCapturerMac is no longer present. Any updates should be ignored. + bool ShouldIgnoreUpdates() { return ready_for_self_destruction_; } + + private: + struct DisplayStreamWrapper { + // The registered CGDisplayStreamRef. + CGDisplayStreamRef stream = nullptr; + + // Set to false when the stream has been stopped. An asynchronous callback + // from CoreGraphics will let us destroy the CGDisplayStreamRef. + bool active = true; + }; + + std::map<int, DisplayStreamWrapper> display_stream_wrappers_; + int unique_id_generator_ = 0; + bool ready_for_self_destruction_ = false; +}; + +// Standard Mac displays have 72dpi, but we report 96dpi for +// consistency with Windows and Linux. +const int kStandardDPI = 96; + +// Scales all coordinates of a rect by a specified factor. +DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { + return DesktopRect::MakeLTRB( + static_cast<int>(floor(rect.origin.x * scale)), + static_cast<int>(floor(rect.origin.y * scale)), + static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)), + static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale))); +} + +// Copy pixels in the |rect| from |src_place| to |dest_plane|. |rect| should be +// relative to the origin of |src_plane| and |dest_plane|. +void CopyRect(const uint8_t* src_plane, + int src_plane_stride, + uint8_t* dest_plane, + int dest_plane_stride, + int bytes_per_pixel, + const DesktopRect& rect) { + // Get the address of the starting point. + const int src_y_offset = src_plane_stride * rect.top(); + const int dest_y_offset = dest_plane_stride * rect.top(); + const int x_offset = bytes_per_pixel * rect.left(); + src_plane += src_y_offset + x_offset; + dest_plane += dest_y_offset + x_offset; + + // Copy pixels in the rectangle line by line. + const int bytes_per_line = bytes_per_pixel * rect.width(); + const int height = rect.height(); + for (int i = 0 ; i < height; ++i) { + memcpy(dest_plane, src_plane, bytes_per_line); + src_plane += src_plane_stride; + dest_plane += dest_plane_stride; + } +} + +// Returns an array of CGWindowID for all the on-screen windows except +// |window_to_exclude|, or NULL if the window is not found or it fails. The +// caller should release the returned CFArrayRef. +CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) { + if (!window_to_exclude) + return nullptr; + + CFArrayRef all_windows = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + if (!all_windows) + return nullptr; + + CFMutableArrayRef returned_array = + CFArrayCreateMutable(nullptr, CFArrayGetCount(all_windows), nullptr); + + bool found = false; + for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(all_windows, i)); + + CFNumberRef id_ref = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + + CGWindowID id; + CFNumberGetValue(id_ref, kCFNumberIntType, &id); + if (id == window_to_exclude) { + found = true; + continue; + } + CFArrayAppendValue(returned_array, reinterpret_cast<void *>(id)); + } + CFRelease(all_windows); + + if (!found) { + CFRelease(returned_array); + returned_array = nullptr; + } + return returned_array; +} + +// Returns the bounds of |window| in physical pixels, enlarged by a small amount +// on four edges to take account of the border/shadow effects. +DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, + float dip_to_pixel_scale) { + // The amount of pixels to add to the actual window bounds to take into + // account of the border/shadow effects. + static const int kBorderEffectSize = 20; + CGRect rect; + CGWindowID ids[1]; + ids[0] = window; + + CFArrayRef window_id_array = + CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + + if (CFArrayGetCount(window_array) > 0) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0)); + CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect); + } + + CFRelease(window_id_array); + CFRelease(window_array); + + rect.origin.x -= kBorderEffectSize; + rect.origin.y -= kBorderEffectSize; + rect.size.width += kBorderEffectSize * 2; + rect.size.height += kBorderEffectSize * 2; + // |rect| is in DIP, so convert to physical pixels. + return ScaleAndRoundCGRect(rect, dip_to_pixel_scale); +} + +// Create an image of the given region using the given |window_list|. +// |pixel_bounds| should be in the primary display's coordinate in physical +// pixels. The caller should release the returned CGImageRef and CFDataRef. +CGImageRef CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds, + float dip_to_pixel_scale, + CFArrayRef window_list) { + CGRect window_bounds; + // The origin is in DIP while the size is in physical pixels. That's what + // CGWindowListCreateImageFromArray expects. + window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale; + window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale; + window_bounds.size.width = pixel_bounds.width(); + window_bounds.size.height = pixel_bounds.height(); + + return CGWindowListCreateImageFromArray( + window_bounds, window_list, kCGWindowImageDefault); +} + +// A class to perform video frame capturing for mac. +class ScreenCapturerMac : public DesktopCapturer { + public: + explicit ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region); + ~ScreenCapturerMac() override; + + bool Init(); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + + private: + // Returns false if the selected screen is no longer valid. + bool CgBlit(const DesktopFrame& frame, const DesktopRegion& region); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + bool RegisterRefreshAndMoveHandlers(); + void UnregisterRefreshAndMoveHandlers(); + + void ScreenRefresh(CGRectCount count, + const CGRect *rect_array, + DesktopVector display_origin); + void ReleaseBuffers(); + + std::unique_ptr<DesktopFrame> CreateFrame(); + + const bool detect_updated_region_; + + Callback* callback_ = nullptr; + + ScopedPixelBufferObject pixel_buffer_object_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + // Current display configuration. + MacDesktopConfiguration desktop_config_; + + // Currently selected display, or 0 if the full desktop is selected. On OS X + // 10.6 and before, this is always 0. + CGDirectDisplayID current_display_ = 0; + + // The physical pixel bounds of the current screen. + DesktopRect screen_pixel_bounds_; + + // The dip to physical pixel scale of the current screen. + float dip_to_pixel_scale_ = 1.0f; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Contains an invalid region from the previous capture. + DesktopRegion last_invalid_region_; + + // Monitoring display reconfiguration. + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_; + + CGWindowID excluded_window_ = 0; + + // A self-owned object that will destroy itself after ScreenCapturerMac and + // all display streams have been destroyed.. + DisplayStreamManager* display_stream_manager_; + + // Used to force CaptureFrame to update it's screen configuration + // and reregister event handlers. This ensure that this + // occurs on the ScreenCapture thread. Read and written from + // both the VideoCapture thread and ScreenCapture thread. + // Protected by desktop_config_monitor_. + bool update_screen_configuration_ = false; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); +}; + +// DesktopFrame wrapper that flips wrapped frame upside down by inverting +// stride. +class InvertedDesktopFrame : public DesktopFrame { + public: + InvertedDesktopFrame(std::unique_ptr<DesktopFrame> frame) + : DesktopFrame( + frame->size(), + -frame->stride(), + frame->data() + (frame->size().height() - 1) * frame->stride(), + frame->shared_memory()) { + original_frame_ = std::move(frame); + MoveFrameInfoFrom(original_frame_.get()); + } + ~InvertedDesktopFrame() override {} + + private: + std::unique_ptr<DesktopFrame> original_frame_; + + RTC_DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame); +}; + +ScreenCapturerMac::ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor, + bool detect_updated_region) + : detect_updated_region_(detect_updated_region), + desktop_config_monitor_(desktop_config_monitor) { + display_stream_manager_ = new DisplayStreamManager; +} + +ScreenCapturerMac::~ScreenCapturerMac() { + ReleaseBuffers(); + UnregisterRefreshAndMoveHandlers(); + display_stream_manager_->PrepareForSelfDestruction(); +} + +bool ScreenCapturerMac::Init() { + desktop_config_monitor_->Lock(); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + desktop_config_monitor_->Unlock(); + if (!RegisterRefreshAndMoveHandlers()) { + return false; + } + ScreenConfigurationChanged(); + return true; +} + +void ScreenCapturerMac::ReleaseBuffers() { + // The buffers might be in use by the encoder, so don't delete them here. + // Instead, mark them as "needs update"; next time the buffers are used by + // the capturer, they will be recreated if necessary. + queue_.Reset(); +} + +void ScreenCapturerMac::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; + desktop_config_monitor_->Lock(); + update_screen_configuration_ = true; + desktop_config_monitor_->Unlock(); +} + +void ScreenCapturerMac::CaptureFrame() { + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + // Spin RunLoop for 1/100th of a second, handling at most one source + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, true); + + queue_.MoveToNextFrame(); + RTC_DCHECK(!queue_.current_frame() || !queue_.current_frame()->IsShared()); + + desktop_config_monitor_->Lock(); + MacDesktopConfiguration new_config = + desktop_config_monitor_->desktop_configuration(); + if (update_screen_configuration_ || !desktop_config_.Equals(new_config)) { + update_screen_configuration_ = false; + desktop_config_ = new_config; + // If the display configuraiton has changed then refresh capturer data + // structures. Occasionally, the refresh and move handlers are lost when + // the screen mode changes, so re-register them here. + UnregisterRefreshAndMoveHandlers(); + RegisterRefreshAndMoveHandlers(); + ScreenConfigurationChanged(); + } + + DesktopRegion region; + helper_.TakeInvalidRegion(®ion); + + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(CreateFrame())); + + DesktopFrame* current_frame = queue_.current_frame(); + + if (!CgBlit(*current_frame, region)) { + desktop_config_monitor_->Unlock(); + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share(); + if (detect_updated_region_) { + *new_frame->mutable_updated_region() = region; + } else { + new_frame->mutable_updated_region()->AddRect( + DesktopRect::MakeSize(new_frame->size())); + } + + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + new_frame->set_top_left(config->bounds.top_left().subtract( + desktop_config_.bounds.top_left())); + } + } + + helper_.set_size_most_recent(new_frame->size()); + + // Signal that we are done capturing data from the display framebuffer, + // and accessing display structures. + desktop_config_monitor_->Unlock(); + + new_frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(Result::SUCCESS, std::move(new_frame)); +} + +void ScreenCapturerMac::SetExcludedWindow(WindowId window) { + excluded_window_ = window; +} + +bool ScreenCapturerMac::GetSourceList(SourceList* screens) { + assert(screens->size() == 0); + + for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin(); + it != desktop_config_.displays.end(); ++it) { + screens->push_back({it->id}); + } + return true; +} + +bool ScreenCapturerMac::SelectSource(SourceId id) { + if (id == kFullDesktopScreenId) { + current_display_ = 0; + } else { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById( + static_cast<CGDirectDisplayID>(id)); + if (!config) + return false; + current_display_ = config->id; + } + + ScreenConfigurationChanged(); + return true; +} + +bool ScreenCapturerMac::CgBlit(const DesktopFrame& frame, const DesktopRegion& region) { + // Copy the entire contents of the previous capture buffer, to capture over. + // TODO(wez): Get rid of this as per crbug.com/145064, or implement + // crbug.com/92354. + if (queue_.previous_frame()) { + memcpy(frame.data(), queue_.previous_frame()->data(), + frame.stride() * frame.size().height()); + } + + MacDisplayConfigurations displays_to_capture; + if (current_display_) { + // Capturing a single screen. Note that the screen id may change when + // screens are added or removed. + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + if (config) { + displays_to_capture.push_back(*config); + } else { + RTC_LOG(LS_ERROR) << "The selected screen cannot be found for capturing."; + return false; + } + } else { + // Capturing the whole desktop. + displays_to_capture = desktop_config_.displays; + } + + // Create the window list once for all displays. + CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_); + + for (size_t i = 0; i < displays_to_capture.size(); ++i) { + const MacDisplayConfiguration& display_config = displays_to_capture[i]; + + // Capturing mixed-DPI on one surface is hard, so we only return displays + // that match the "primary" display's DPI. The primary display is always + // the first in the list. + if (i > 0 && display_config.dip_to_pixel_scale != + displays_to_capture[0].dip_to_pixel_scale) { + continue; + } + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-screen_pixel_bounds_.left(), + -screen_pixel_bounds_.top()); + + // Determine which parts of the blit region, if any, lay within the monitor. + DesktopRegion copy_region = region; + copy_region.IntersectWith(display_bounds); + if (copy_region.is_empty()) + continue; + + // Translate the region to be copied into display-relative coordinates. + copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + + DesktopRect excluded_window_bounds; + CGImageRef excluded_image = nullptr; + if (excluded_window_ && window_list) { + // Get the region of the excluded window relative the primary display. + excluded_window_bounds = GetExcludedWindowPixelBounds( + excluded_window_, display_config.dip_to_pixel_scale); + excluded_window_bounds.IntersectWith(display_config.pixel_bounds); + + // Create the image under the excluded window first, because it's faster + // than captuing the whole display. + if (!excluded_window_bounds.is_empty()) { + excluded_image = CreateExcludedWindowRegionImage( + excluded_window_bounds, display_config.dip_to_pixel_scale, + window_list); + } + } + + // Create an image containing a snapshot of the display. + CGImageRef image = CGDisplayCreateImage(display_config.id); + if (!image) { + if (excluded_image) + CFRelease(excluded_image); + continue; + } + + // Verify that the image has 32-bit depth. + int bits_per_pixel = CGImageGetBitsPerPixel(image); + if (bits_per_pixel / 8 != DesktopFrame::kBytesPerPixel) { + RTC_LOG(LS_ERROR) << "CGDisplayCreateImage() returned imaged with " << bits_per_pixel + << " bits per pixel. Only 32-bit depth is supported."; + CFRelease(image); + if (excluded_image) + CFRelease(excluded_image); + return false; + } + + // Request access to the raw pixel data via the image's DataProvider. + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef data = CGDataProviderCopyData(provider); + assert(data); + + const uint8_t* display_base_address = CFDataGetBytePtr(data); + int src_bytes_per_row = CGImageGetBytesPerRow(image); + + // |image| size may be different from display_bounds in case the screen was + // resized recently. + copy_region.IntersectWith( + DesktopRect::MakeWH(CGImageGetWidth(image), CGImageGetHeight(image))); + + // Copy the dirty region from the display buffer into our desktop buffer. + uint8_t* out_ptr = frame.GetFrameDataAtPos(display_bounds.top_left()); + for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { + CopyRect(display_base_address, src_bytes_per_row, out_ptr, frame.stride(), + DesktopFrame::kBytesPerPixel, i.rect()); + } + + CFRelease(data); + CFRelease(image); + + if (excluded_image) { + CGDataProviderRef provider = CGImageGetDataProvider(excluded_image); + CFDataRef excluded_image_data = CGDataProviderCopyData(provider); + assert(excluded_image_data); + display_base_address = CFDataGetBytePtr(excluded_image_data); + src_bytes_per_row = CGImageGetBytesPerRow(excluded_image); + + // Translate the bounds relative to the desktop, because |frame| data + // starts from the desktop top-left corner. + DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds); + window_bounds_relative_to_desktop.Translate(-screen_pixel_bounds_.left(), + -screen_pixel_bounds_.top()); + + DesktopRect rect_to_copy = + DesktopRect::MakeSize(excluded_window_bounds.size()); + rect_to_copy.IntersectWith(DesktopRect::MakeWH( + CGImageGetWidth(excluded_image), CGImageGetHeight(excluded_image))); + + if (CGImageGetBitsPerPixel(excluded_image) / 8 == + DesktopFrame::kBytesPerPixel) { + CopyRect(display_base_address, src_bytes_per_row, + frame.GetFrameDataAtPos( + window_bounds_relative_to_desktop.top_left()), + frame.stride(), DesktopFrame::kBytesPerPixel, rect_to_copy); + } + + CFRelease(excluded_image_data); + CFRelease(excluded_image); + } + } + if (window_list) + CFRelease(window_list); + return true; +} + +void ScreenCapturerMac::ScreenConfigurationChanged() { + if (current_display_) { + const MacDisplayConfiguration* config = + desktop_config_.FindDisplayConfigurationById(current_display_); + screen_pixel_bounds_ = config ? config->pixel_bounds : DesktopRect(); + dip_to_pixel_scale_ = config ? config->dip_to_pixel_scale : 1.0f; + } else { + screen_pixel_bounds_ = desktop_config_.pixel_bounds; + dip_to_pixel_scale_ = desktop_config_.dip_to_pixel_scale; + } + + // Release existing buffers, which will be of the wrong size. + ReleaseBuffers(); + + // Clear the dirty region, in case the display is down-sizing. + helper_.ClearInvalidRegion(); + + // Re-mark the entire desktop as dirty. + helper_.InvalidateScreen(screen_pixel_bounds_.size()); + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); +} + +bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + for (const auto& config : desktop_config_.displays) { + size_t pixel_width = config.pixel_bounds.width(); + size_t pixel_height = config.pixel_bounds.height(); + if (pixel_width == 0 || pixel_height == 0) + continue; + // Using a local variable forces the block to capture the raw pointer. + DisplayStreamManager* manager = display_stream_manager_; + int unique_id = manager->GetUniqueId(); + CGDirectDisplayID display_id = config.id; + DesktopVector display_origin = config.pixel_bounds.top_left(); + + CGDisplayStreamFrameAvailableHandler handler = + ^(CGDisplayStreamFrameStatus status, uint64_t display_time, + IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef updateRef) { + if (status == kCGDisplayStreamFrameStatusStopped) { + manager->DestroyStream(unique_id); + return; + } + + if (manager->ShouldIgnoreUpdates()) + return; + + // Only pay attention to frame updates. + if (status != kCGDisplayStreamFrameStatusFrameComplete) + return; + + size_t count = 0; + const CGRect* rects = CGDisplayStreamUpdateGetRects( + updateRef, kCGDisplayStreamUpdateDirtyRects, &count); + if (count != 0) { + // According to CGDisplayStream.h, it's safe to call + // CGDisplayStreamStop() from within the callback. + ScreenRefresh(count, rects, display_origin); + } + }; + CGDisplayStreamRef display_stream = CGDisplayStreamCreate( + display_id, pixel_width, pixel_height, 'BGRA', nullptr, handler); + + if (display_stream) { + CGError error = CGDisplayStreamStart(display_stream); + if (error != kCGErrorSuccess) + return false; + + CFRunLoopSourceRef source = + CGDisplayStreamGetRunLoopSource(display_stream); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); + display_stream_manager_->SaveStream(unique_id, display_stream); + } + } + + return true; +} + +void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { + display_stream_manager_->UnregisterActiveStreams(); +} + +void ScreenCapturerMac::ScreenRefresh(CGRectCount count, + const CGRect* rect_array, + DesktopVector display_origin) { + if (screen_pixel_bounds_.is_empty()) + ScreenConfigurationChanged(); + + // The refresh rects are in display coordinates. We want to translate to + // framebuffer coordinates. If a specific display is being captured, then no + // change is necessary. If all displays are being captured, then we want to + // translate by the origin of the display. + DesktopVector translate_vector; + if (!current_display_) + translate_vector = display_origin; + + DesktopRegion region; + for (CGRectCount i = 0; i < count; ++i) { + // All rects are already in physical pixel coordinates. + DesktopRect rect = DesktopRect::MakeXYWH( + rect_array[i].origin.x, rect_array[i].origin.y, + rect_array[i].size.width, rect_array[i].size.height); + + rect.Translate(translate_vector); + + region.AddRect(rect); + } + + helper_.InvalidateRegion(region); +} + +std::unique_ptr<DesktopFrame> ScreenCapturerMac::CreateFrame() { + std::unique_ptr<DesktopFrame> frame( + new BasicDesktopFrame(screen_pixel_bounds_.size())); + frame->set_dpi(DesktopVector(kStandardDPI * dip_to_pixel_scale_, + kStandardDPI * dip_to_pixel_scale_)); + return frame; +} + +} // namespace + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + if (!options.configuration_monitor()) + return nullptr; + + std::unique_ptr<ScreenCapturerMac> capturer(new ScreenCapturerMac( + options.configuration_monitor(), options.detect_updated_region())); + if (!capturer.get()->Init()) { + return nullptr; + } + + return capturer; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc new file mode 100644 index 0000000000..96e844066a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <ApplicationServices/ApplicationServices.h> + +#include <memory> +#include <ostream> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mock_desktop_capturer_callback.h" +#include "test/gtest.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; + +namespace webrtc { + +class ScreenCapturerMacTest : public ::testing::Test { + public: + // Verifies that the whole screen is initially dirty. + void CaptureDoneCallback1(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame); + + // Verifies that a rectangle explicitly marked as dirty is propagated + // correctly. + void CaptureDoneCallback2(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame); + + protected: + void SetUp() override { + capturer_ = DesktopCapturer::CreateScreenCapturer( + DesktopCaptureOptions::CreateDefault()); + } + + std::unique_ptr<DesktopCapturer> capturer_; + MockDesktopCapturerCallback callback_; +}; + +void ScreenCapturerMacTest::CaptureDoneCallback1( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame) { + EXPECT_EQ(result, DesktopCapturer::Result::SUCCESS); + + MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::BottomLeftOrigin); + + // Verify that the region contains full frame. + DesktopRegion::Iterator it((*frame)->updated_region()); + EXPECT_TRUE(!it.IsAtEnd() && it.rect().equals(config.pixel_bounds)); +} + +void ScreenCapturerMacTest::CaptureDoneCallback2( + DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame>* frame) { + EXPECT_EQ(result, DesktopCapturer::Result::SUCCESS); + + MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::BottomLeftOrigin); + int width = config.pixel_bounds.width(); + int height = config.pixel_bounds.height(); + + EXPECT_EQ(width, (*frame)->size().width()); + EXPECT_EQ(height, (*frame)->size().height()); + EXPECT_TRUE((*frame)->data() != NULL); + // Depending on the capture method, the screen may be flipped or not, so + // the stride may be positive or negative. + EXPECT_EQ(static_cast<int>(sizeof(uint32_t) * width), + abs((*frame)->stride())); +} + +TEST_F(ScreenCapturerMacTest, Capture) { + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .Times(2) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback1)) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback2)); + + SCOPED_TRACE(""); + capturer_->Start(&callback_); + + // Check that we get an initial full-screen updated. + capturer_->CaptureFrame(); + + // Check that subsequent dirty rects are propagated correctly. + capturer_->CaptureFrame(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_null.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_null.cc new file mode 100644 index 0000000000..6b1ccb322e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_null.cc @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer.h" + +namespace webrtc { + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + return nullptr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_unittest.cc new file mode 100644 index 0000000000..8f5fe631f1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mock_desktop_capturer_callback.h" +#include "rtc_base/logging.h" +#include "test/gmock.h" +#include "test/gtest.h" + +#if defined(WEBRTC_WIN) +#include "modules/desktop_capture/win/screen_capturer_win_directx.h" +#endif // defined(WEBRTC_WIN) + +using ::testing::_; + +const int kTestSharedMemoryId = 123; + +namespace webrtc { + +class ScreenCapturerTest : public ::testing::Test { + public: + void SetUp() override { + capturer_ = DesktopCapturer::CreateScreenCapturer( + DesktopCaptureOptions::CreateDefault()); + ASSERT_TRUE(capturer_); + } + + protected: +#if defined(WEBRTC_WIN) + // Enable allow_directx_capturer in DesktopCaptureOptions, but let + // DesktopCapturer::CreateScreenCapturer to decide whether a DirectX capturer + // should be used. + void MaybeCreateDirectxCapturer() { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_directx_capturer(true); + capturer_ = DesktopCapturer::CreateScreenCapturer(options); + } + + bool CreateDirectxCapturer() { + if (!ScreenCapturerWinDirectx::IsSupported()) { + RTC_LOG(LS_WARNING) << "Directx capturer is not supported"; + return false; + } + + MaybeCreateDirectxCapturer(); + return true; + } + + void CreateMagnifierCapturer() { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_use_magnification_api(true); + capturer_ = DesktopCapturer::CreateScreenCapturer(options); + } +#endif // defined(WEBRTC_WIN) + + std::unique_ptr<DesktopCapturer> capturer_; + MockDesktopCapturerCallback callback_; +}; + +class FakeSharedMemory : public SharedMemory { + public: + FakeSharedMemory(char* buffer, size_t size) + : SharedMemory(buffer, size, 0, kTestSharedMemoryId), buffer_(buffer) {} + ~FakeSharedMemory() override { delete[] buffer_; } + + FakeSharedMemory(const FakeSharedMemory&) = delete; + FakeSharedMemory& operator=(const FakeSharedMemory&) = delete; + + private: + char* buffer_; +}; + +class FakeSharedMemoryFactory : public SharedMemoryFactory { + public: + FakeSharedMemoryFactory() {} + ~FakeSharedMemoryFactory() override {} + + FakeSharedMemoryFactory(const FakeSharedMemoryFactory&) = delete; + FakeSharedMemoryFactory& operator=(const FakeSharedMemoryFactory&) = delete; + + std::unique_ptr<SharedMemory> CreateSharedMemory(size_t size) override { + return std::unique_ptr<SharedMemory>( + new FakeSharedMemory(new char[size], size)); + } +}; + +ACTION_P(SaveUniquePtrArg, dest) { + *dest = std::move(*arg1); +} + +// TODO(bugs.webrtc.org/12950): Re-enable when libc++ issue is fixed. +#if defined(WEBRTC_LINUX) && defined(MEMORY_SANITIZER) +#define MAYBE_GetScreenListAndSelectScreen DISABLED_GetScreenListAndSelectScreen +#else +#define MAYBE_GetScreenListAndSelectScreen GetScreenListAndSelectScreen +#endif +TEST_F(ScreenCapturerTest, MAYBE_GetScreenListAndSelectScreen) { + webrtc::DesktopCapturer::SourceList screens; + EXPECT_TRUE(capturer_->GetSourceList(&screens)); + for (const auto& screen : screens) { + EXPECT_TRUE(capturer_->SelectSource(screen.id)); + } +} + +// Flaky on Linux. See: crbug.com/webrtc/7830 +#if defined(WEBRTC_LINUX) +#define MAYBE_StartCapturer DISABLED_StartCaptuerer +#else +#define MAYBE_StartCapturer StartCapturer +#endif +TEST_F(ScreenCapturerTest, MAYBE_StartCapturer) { + capturer_->Start(&callback_); +} + +#if defined(WEBRTC_LINUX) +#define MAYBE_Capture DISABLED_Capture +#else +#define MAYBE_Capture Capture +#endif +TEST_F(ScreenCapturerTest, MAYBE_Capture) { + // Assume that Start() treats the screen as invalid initially. + std::unique_ptr<DesktopFrame> frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->CaptureFrame(); + + ASSERT_TRUE(frame); + EXPECT_GT(frame->size().width(), 0); + EXPECT_GT(frame->size().height(), 0); + EXPECT_GE(frame->stride(), + frame->size().width() * DesktopFrame::kBytesPerPixel); + EXPECT_TRUE(frame->shared_memory() == NULL); + + // Verify that the region contains whole screen. + EXPECT_FALSE(frame->updated_region().is_empty()); + DesktopRegion::Iterator it(frame->updated_region()); + ASSERT_TRUE(!it.IsAtEnd()); + EXPECT_TRUE(it.rect().equals(DesktopRect::MakeSize(frame->size()))); + it.Advance(); + EXPECT_TRUE(it.IsAtEnd()); +} + +#if defined(WEBRTC_WIN) + +TEST_F(ScreenCapturerTest, UseSharedBuffers) { + std::unique_ptr<DesktopFrame> frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory>(new FakeSharedMemoryFactory())); + capturer_->CaptureFrame(); + + ASSERT_TRUE(frame); + ASSERT_TRUE(frame->shared_memory()); + EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId); +} + +TEST_F(ScreenCapturerTest, UseMagnifier) { + CreateMagnifierCapturer(); + std::unique_ptr<DesktopFrame> frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->CaptureFrame(); + ASSERT_TRUE(frame); +} + +TEST_F(ScreenCapturerTest, UseDirectxCapturer) { + if (!CreateDirectxCapturer()) { + return; + } + + std::unique_ptr<DesktopFrame> frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->CaptureFrame(); + ASSERT_TRUE(frame); +} + +TEST_F(ScreenCapturerTest, UseDirectxCapturerWithSharedBuffers) { + if (!CreateDirectxCapturer()) { + return; + } + + std::unique_ptr<DesktopFrame> frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory>(new FakeSharedMemoryFactory())); + capturer_->CaptureFrame(); + ASSERT_TRUE(frame); + ASSERT_TRUE(frame->shared_memory()); + EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId); +} + +#endif // defined(WEBRTC_WIN) + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_win.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_win.cc new file mode 100644 index 0000000000..b5935dc316 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_win.cc @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/rgba_color.h" +#include "modules/desktop_capture/win/screen_capturer_win_directx.h" +#include "modules/desktop_capture/win/screen_capturer_win_gdi.h" +#include "modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +namespace webrtc { + +namespace { + +std::unique_ptr<DesktopCapturer> CreateScreenCapturerWinDirectx() { + std::unique_ptr<DesktopCapturer> capturer(new ScreenCapturerWinDirectx()); + capturer.reset(new BlankDetectorDesktopCapturerWrapper( + std::move(capturer), RgbaColor(0, 0, 0, 0))); + return capturer; +} + +} // namespace + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr<DesktopCapturer> capturer(new ScreenCapturerWinGdi(options)); + if (options.allow_directx_capturer()) { + // `dxgi_duplicator_controller` should be alive in this scope to ensure it + // won't unload DxgiDuplicatorController. + auto dxgi_duplicator_controller = DxgiDuplicatorController::Instance(); + if (ScreenCapturerWinDirectx::IsSupported()) { + capturer.reset(new FallbackDesktopCapturerWrapper( + CreateScreenCapturerWinDirectx(), std::move(capturer))); + } + } + + if (options.allow_use_magnification_api()) { + // ScreenCapturerWinMagnifier cannot work on Windows XP or earlier, as well + // as 64-bit only Windows, and it may randomly crash on multi-screen + // systems. So we may need to fallback to use original capturer. + capturer.reset(new FallbackDesktopCapturerWrapper( + std::unique_ptr<DesktopCapturer>(new ScreenCapturerWinMagnifier()), + std::move(capturer))); + } + + return capturer; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer.cc new file mode 100644 index 0000000000..6460f19f65 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer.cc @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_drawer.h" + +namespace webrtc { + +namespace { +std::unique_ptr<ScreenDrawerLock> g_screen_drawer_lock; +} // namespace + +ScreenDrawerLock::ScreenDrawerLock() = default; +ScreenDrawerLock::~ScreenDrawerLock() = default; + +ScreenDrawer::ScreenDrawer() { + g_screen_drawer_lock = ScreenDrawerLock::Create(); +} + +ScreenDrawer::~ScreenDrawer() { + g_screen_drawer_lock.reset(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer.h b/third_party/libwebrtc/modules/desktop_capture/screen_drawer.h new file mode 100644 index 0000000000..ad7c0ad8d1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_H_ + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/rgba_color.h" + +namespace webrtc { + +// A cross-process lock to ensure only one ScreenDrawer can be used at a certain +// time. +class ScreenDrawerLock { + public: + virtual ~ScreenDrawerLock(); + + static std::unique_ptr<ScreenDrawerLock> Create(); + + protected: + ScreenDrawerLock(); +}; + +// A set of basic platform dependent functions to draw various shapes on the +// screen. +class ScreenDrawer { + public: + // Creates a ScreenDrawer for the current platform, returns nullptr if no + // ScreenDrawer implementation available. + // If the implementation cannot guarantee two ScreenDrawer instances won't + // impact each other, this function may block current thread until another + // ScreenDrawer has been destroyed. + static std::unique_ptr<ScreenDrawer> Create(); + + ScreenDrawer(); + virtual ~ScreenDrawer(); + + // Returns the region inside which DrawRectangle() function are expected to + // work, in capturer coordinates (assuming ScreenCapturer::SelectScreen has + // not been called). This region may exclude regions of the screen reserved by + // the OS for things like menu bars or app launchers. The DesktopRect is in + // system coordinate, i.e. the primary monitor always starts from (0, 0). + virtual DesktopRect DrawableRegion() = 0; + + // Draws a rectangle to cover `rect` with `color`. Note, rect.bottom() and + // rect.right() two lines are not included. The part of `rect` which is out of + // DrawableRegion() will be ignored. + virtual void DrawRectangle(DesktopRect rect, RgbaColor color) = 0; + + // Clears all content on the screen by filling the area with black. + virtual void Clear() = 0; + + // Blocks current thread until OS finishes previous DrawRectangle() actions. + // ScreenCapturer should be able to capture the changes after this function + // finish. + virtual void WaitForPendingDraws() = 0; + + // Returns true if incomplete shapes previous actions required may be drawn on + // the screen after a WaitForPendingDraws() call. i.e. Though the complete + // shapes will eventually be drawn on the screen, due to some OS limitations, + // these shapes may be partially appeared sometimes. + virtual bool MayDrawIncompleteShapes() = 0; + + // Returns the id of the drawer window. This function returns kNullWindowId if + // the implementation does not draw on a window of the system. + virtual WindowId window_id() const = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_linux.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_linux.cc new file mode 100644 index 0000000000..fce036b4aa --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_linux.cc @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <string.h> + +#include <memory> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/rgba_color.h" +#include "modules/desktop_capture/screen_drawer.h" +#include "modules/desktop_capture/screen_drawer_lock_posix.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/sleep.h" + +namespace webrtc { + +namespace { + +// A ScreenDrawer implementation for X11. +class ScreenDrawerLinux : public ScreenDrawer { + public: + ScreenDrawerLinux(); + ~ScreenDrawerLinux() override; + + // ScreenDrawer interface. + DesktopRect DrawableRegion() override; + void DrawRectangle(DesktopRect rect, RgbaColor color) override; + void Clear() override; + void WaitForPendingDraws() override; + bool MayDrawIncompleteShapes() override; + WindowId window_id() const override; + + private: + // Bring the window to the front, this can help to avoid the impact from other + // windows or shadow effect. + void BringToFront(); + + rtc::scoped_refptr<SharedXDisplay> display_; + int screen_num_; + DesktopRect rect_; + Window window_; + GC context_; + Colormap colormap_; +}; + +ScreenDrawerLinux::ScreenDrawerLinux() { + display_ = SharedXDisplay::CreateDefault(); + RTC_CHECK(display_.get()); + screen_num_ = DefaultScreen(display_->display()); + XWindowAttributes root_attributes; + if (!XGetWindowAttributes(display_->display(), + RootWindow(display_->display(), screen_num_), + &root_attributes)) { + RTC_DCHECK_NOTREACHED() << "Failed to get root window size."; + } + window_ = XCreateSimpleWindow( + display_->display(), RootWindow(display_->display(), screen_num_), 0, 0, + root_attributes.width, root_attributes.height, 0, + BlackPixel(display_->display(), screen_num_), + BlackPixel(display_->display(), screen_num_)); + XSelectInput(display_->display(), window_, StructureNotifyMask); + XMapWindow(display_->display(), window_); + while (true) { + XEvent event; + XNextEvent(display_->display(), &event); + if (event.type == MapNotify) { + break; + } + } + XFlush(display_->display()); + Window child; + int x, y; + if (!XTranslateCoordinates(display_->display(), window_, + RootWindow(display_->display(), screen_num_), 0, 0, + &x, &y, &child)) { + RTC_DCHECK_NOTREACHED() << "Failed to get window position."; + } + // Some window manager does not allow a window to cover two or more monitors. + // So if the window is on the first monitor of a two-monitor system, the + // second half won't be able to show up without changing configurations of WM, + // and its DrawableRegion() is not accurate. + rect_ = DesktopRect::MakeLTRB(x, y, root_attributes.width, + root_attributes.height); + context_ = DefaultGC(display_->display(), screen_num_); + colormap_ = DefaultColormap(display_->display(), screen_num_); + BringToFront(); + // Wait for window animations. + SleepMs(200); +} + +ScreenDrawerLinux::~ScreenDrawerLinux() { + XUnmapWindow(display_->display(), window_); + XDestroyWindow(display_->display(), window_); +} + +DesktopRect ScreenDrawerLinux::DrawableRegion() { + return rect_; +} + +void ScreenDrawerLinux::DrawRectangle(DesktopRect rect, RgbaColor color) { + rect.Translate(-rect_.left(), -rect_.top()); + XColor xcolor; + // X11 does not support Alpha. + // X11 uses 16 bits for each primary color, so we need to slightly normalize + // a 8 bits channel to 16 bits channel, by setting the low 8 bits as its high + // 8 bits to avoid a mismatch of color returned by capturer. + xcolor.red = (color.red << 8) + color.red; + xcolor.green = (color.green << 8) + color.green; + xcolor.blue = (color.blue << 8) + color.blue; + xcolor.flags = DoRed | DoGreen | DoBlue; + XAllocColor(display_->display(), colormap_, &xcolor); + XSetForeground(display_->display(), context_, xcolor.pixel); + XFillRectangle(display_->display(), window_, context_, rect.left(), + rect.top(), rect.width(), rect.height()); + XFlush(display_->display()); +} + +void ScreenDrawerLinux::Clear() { + DrawRectangle(rect_, RgbaColor(0, 0, 0)); +} + +// TODO(zijiehe): Find the right signal from X11 to indicate the finish of all +// pending paintings. +void ScreenDrawerLinux::WaitForPendingDraws() { + SleepMs(50); +} + +bool ScreenDrawerLinux::MayDrawIncompleteShapes() { + return true; +} + +WindowId ScreenDrawerLinux::window_id() const { + return window_; +} + +void ScreenDrawerLinux::BringToFront() { + Atom state_above = XInternAtom(display_->display(), "_NET_WM_STATE_ABOVE", 1); + Atom window_state = XInternAtom(display_->display(), "_NET_WM_STATE", 1); + if (state_above == None || window_state == None) { + // Fallback to use XRaiseWindow, it's not reliable if two windows are both + // raise itself to the top. + XRaiseWindow(display_->display(), window_); + return; + } + + XEvent event; + memset(&event, 0, sizeof(event)); + event.type = ClientMessage; + event.xclient.window = window_; + event.xclient.message_type = window_state; + event.xclient.format = 32; + event.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + event.xclient.data.l[1] = state_above; + XSendEvent(display_->display(), RootWindow(display_->display(), screen_num_), + False, SubstructureRedirectMask | SubstructureNotifyMask, &event); +} + +} // namespace + +// static +std::unique_ptr<ScreenDrawerLock> ScreenDrawerLock::Create() { + return std::make_unique<ScreenDrawerLockPosix>(); +} + +// static +std::unique_ptr<ScreenDrawer> ScreenDrawer::Create() { + if (SharedXDisplay::CreateDefault().get()) { + return std::make_unique<ScreenDrawerLinux>(); + } + return nullptr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.cc new file mode 100644 index 0000000000..28cb501fe7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_drawer_lock_posix.h" + +#include <fcntl.h> +#include <sys/stat.h> + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +// A uuid as the name of semaphore. +static constexpr char kSemaphoreName[] = "GSDL54fe5552804711e6a7253f429a"; + +} // namespace + +ScreenDrawerLockPosix::ScreenDrawerLockPosix() + : ScreenDrawerLockPosix(kSemaphoreName) {} + +ScreenDrawerLockPosix::ScreenDrawerLockPosix(const char* name) { + semaphore_ = sem_open(name, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 1); + if (semaphore_ == SEM_FAILED) { + RTC_LOG_ERRNO(LS_ERROR) << "Failed to create named semaphore with " << name; + RTC_DCHECK_NOTREACHED(); + } + + sem_wait(semaphore_); +} + +ScreenDrawerLockPosix::~ScreenDrawerLockPosix() { + if (semaphore_ == SEM_FAILED) { + return; + } + + sem_post(semaphore_); + sem_close(semaphore_); + // sem_unlink a named semaphore won't wait until other clients to release the + // sem_t. So if a new process starts, it will sem_open a different kernel + // object with the same name and eventually breaks the cross-process lock. +} + +// static +void ScreenDrawerLockPosix::Unlink(absl::string_view name) { + sem_unlink(std::string(name).c_str()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.h b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.h new file mode 100644 index 0000000000..13899b2d75 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_lock_posix.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_LOCK_POSIX_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_LOCK_POSIX_H_ + +#include <semaphore.h> + +#include "absl/strings/string_view.h" +#include "modules/desktop_capture/screen_drawer.h" + +namespace webrtc { + +class ScreenDrawerLockPosix final : public ScreenDrawerLock { + public: + ScreenDrawerLockPosix(); + // Provides a name other than the default one for test only. + explicit ScreenDrawerLockPosix(const char* name); + ~ScreenDrawerLockPosix() override; + + // Unlinks the named semaphore actively. This will remove the sem_t object in + // the system and allow others to create a different sem_t object with the + // same/ name. + static void Unlink(absl::string_view name); + + private: + sem_t* semaphore_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_DRAWER_LOCK_POSIX_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_mac.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_mac.cc new file mode 100644 index 0000000000..17719e4439 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_mac.cc @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// TODO(zijiehe): Implement ScreenDrawerMac + +#include <memory> + +#include "modules/desktop_capture/screen_drawer.h" +#include "modules/desktop_capture/screen_drawer_lock_posix.h" + +namespace webrtc { + +// static +std::unique_ptr<ScreenDrawerLock> ScreenDrawerLock::Create() { + return std::make_unique<ScreenDrawerLockPosix>(); +} + +// static +std::unique_ptr<ScreenDrawer> ScreenDrawer::Create() { + return nullptr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_unittest.cc new file mode 100644 index 0000000000..584770dbf8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_unittest.cc @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_drawer.h" + +#include <stdint.h> + +#include <atomic> +#include <memory> + +#include "api/function_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/random.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/sleep.h" +#include "test/gtest.h" + +#if defined(WEBRTC_POSIX) +#include "modules/desktop_capture/screen_drawer_lock_posix.h" +#endif + +namespace webrtc { + +namespace { + +void TestScreenDrawerLock( + rtc::FunctionView<std::unique_ptr<ScreenDrawerLock>()> ctor) { + constexpr int kLockDurationMs = 100; + + std::atomic<bool> created(false); + std::atomic<bool> ready(false); + + class Task { + public: + Task(std::atomic<bool>* created, + const std::atomic<bool>& ready, + rtc::FunctionView<std::unique_ptr<ScreenDrawerLock>()> ctor) + : created_(created), ready_(ready), ctor_(ctor) {} + + ~Task() = default; + + void RunTask() { + std::unique_ptr<ScreenDrawerLock> lock = ctor_(); + ASSERT_TRUE(!!lock); + created_->store(true); + // Wait for the main thread to get the signal of created_. + while (!ready_.load()) { + SleepMs(1); + } + // At this point, main thread should begin to create a second lock. Though + // it's still possible the second lock won't be created before the + // following sleep has been finished, the possibility will be + // significantly reduced. + const int64_t current_ms = rtc::TimeMillis(); + // SleepMs() may return early. See + // https://cs.chromium.org/chromium/src/third_party/webrtc/system_wrappers/include/sleep.h?rcl=4a604c80cecce18aff6fc5e16296d04675312d83&l=20 + // But we need to ensure at least 100 ms has been passed before unlocking + // `lock`. + while (rtc::TimeMillis() - current_ms < kLockDurationMs) { + SleepMs(kLockDurationMs - (rtc::TimeMillis() - current_ms)); + } + } + + private: + std::atomic<bool>* const created_; + const std::atomic<bool>& ready_; + const rtc::FunctionView<std::unique_ptr<ScreenDrawerLock>()> ctor_; + } task(&created, ready, ctor); + + auto lock_thread = rtc::PlatformThread::SpawnJoinable( + [&task] { task.RunTask(); }, "lock_thread"); + + // Wait for the first lock in Task::RunTask() to be created. + // TODO(zijiehe): Find a better solution to wait for the creation of the first + // lock. See + // https://chromium-review.googlesource.com/c/607688/13/webrtc/modules/desktop_capture/screen_drawer_unittest.cc + while (!created.load()) { + SleepMs(1); + } + + const int64_t start_ms = rtc::TimeMillis(); + ready.store(true); + // This is unlikely to fail, but just in case current thread is too laggy and + // cause the SleepMs() in RunTask() to finish before we creating another lock. + ASSERT_GT(kLockDurationMs, rtc::TimeMillis() - start_ms); + ctor(); + ASSERT_LE(kLockDurationMs, rtc::TimeMillis() - start_ms); +} + +} // namespace + +// These are a set of manual test cases, as we do not have an automatical way to +// detect whether a ScreenDrawer on a certain platform works well without +// ScreenCapturer(s). So you may execute these test cases with +// --gtest_also_run_disabled_tests --gtest_filter=ScreenDrawerTest.*. +TEST(ScreenDrawerTest, DISABLED_DrawRectangles) { + std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create(); + if (!drawer) { + RTC_LOG(LS_WARNING) + << "No ScreenDrawer implementation for current platform."; + return; + } + + if (drawer->DrawableRegion().is_empty()) { + RTC_LOG(LS_WARNING) + << "ScreenDrawer of current platform does not provide a " + "non-empty DrawableRegion()."; + return; + } + + DesktopRect rect = drawer->DrawableRegion(); + Random random(rtc::TimeMicros()); + for (int i = 0; i < 100; i++) { + // Make sure we at least draw one pixel. + int left = random.Rand(rect.left(), rect.right() - 2); + int top = random.Rand(rect.top(), rect.bottom() - 2); + drawer->DrawRectangle( + DesktopRect::MakeLTRB(left, top, random.Rand(left + 1, rect.right()), + random.Rand(top + 1, rect.bottom())), + RgbaColor(random.Rand<uint8_t>(), random.Rand<uint8_t>(), + random.Rand<uint8_t>(), random.Rand<uint8_t>())); + + if (i == 50) { + SleepMs(10000); + } + } + + SleepMs(10000); +} + +#if defined(THREAD_SANITIZER) // bugs.webrtc.org/10019 +#define MAYBE_TwoScreenDrawerLocks DISABLED_TwoScreenDrawerLocks +#else +#define MAYBE_TwoScreenDrawerLocks TwoScreenDrawerLocks +#endif +TEST(ScreenDrawerTest, MAYBE_TwoScreenDrawerLocks) { +#if defined(WEBRTC_POSIX) + // ScreenDrawerLockPosix won't be able to unlink the named semaphore. So use a + // different semaphore name here to avoid deadlock. + const char* semaphore_name = "GSDL8784541a812011e788ff67427b"; + ScreenDrawerLockPosix::Unlink(semaphore_name); + + TestScreenDrawerLock([semaphore_name]() { + return std::make_unique<ScreenDrawerLockPosix>(semaphore_name); + }); +#elif defined(WEBRTC_WIN) + TestScreenDrawerLock([]() { return ScreenDrawerLock::Create(); }); +#endif +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_drawer_win.cc b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_win.cc new file mode 100644 index 0000000000..7cf634fe89 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/screen_drawer_win.cc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <windows.h> + +#include <memory> + +#include "modules/desktop_capture/screen_drawer.h" +#include "system_wrappers/include/sleep.h" + +namespace webrtc { + +namespace { + +static constexpr TCHAR kMutexName[] = + TEXT("Local\\ScreenDrawerWin-da834f82-8044-11e6-ac81-73dcdd1c1869"); + +class ScreenDrawerLockWin : public ScreenDrawerLock { + public: + ScreenDrawerLockWin(); + ~ScreenDrawerLockWin() override; + + private: + HANDLE mutex_; +}; + +ScreenDrawerLockWin::ScreenDrawerLockWin() { + while (true) { + mutex_ = CreateMutex(NULL, FALSE, kMutexName); + if (GetLastError() != ERROR_ALREADY_EXISTS && mutex_ != NULL) { + break; + } else { + if (mutex_) { + CloseHandle(mutex_); + } + SleepMs(1000); + } + } +} + +ScreenDrawerLockWin::~ScreenDrawerLockWin() { + CloseHandle(mutex_); +} + +DesktopRect GetScreenRect() { + HDC hdc = GetDC(NULL); + DesktopRect rect = DesktopRect::MakeWH(GetDeviceCaps(hdc, HORZRES), + GetDeviceCaps(hdc, VERTRES)); + ReleaseDC(NULL, hdc); + return rect; +} + +HWND CreateDrawerWindow(DesktopRect rect) { + HWND hwnd = CreateWindowA( + "STATIC", "DrawerWindow", WS_POPUPWINDOW | WS_VISIBLE, rect.left(), + rect.top(), rect.width(), rect.height(), NULL, NULL, NULL, NULL); + SetForegroundWindow(hwnd); + return hwnd; +} + +COLORREF ColorToRef(RgbaColor color) { + // Windows device context does not support alpha. + return RGB(color.red, color.green, color.blue); +} + +// A ScreenDrawer implementation for Windows. +class ScreenDrawerWin : public ScreenDrawer { + public: + ScreenDrawerWin(); + ~ScreenDrawerWin() override; + + // ScreenDrawer interface. + DesktopRect DrawableRegion() override; + void DrawRectangle(DesktopRect rect, RgbaColor color) override; + void Clear() override; + void WaitForPendingDraws() override; + bool MayDrawIncompleteShapes() override; + WindowId window_id() const override; + + private: + // Bring the window to the front, this can help to avoid the impact from other + // windows or shadow effects. + void BringToFront(); + + // Draw a line with `color`. + void DrawLine(DesktopVector start, DesktopVector end, RgbaColor color); + + // Draw a dot with `color`. + void DrawDot(DesktopVector vect, RgbaColor color); + + const DesktopRect rect_; + HWND window_; + HDC hdc_; +}; + +ScreenDrawerWin::ScreenDrawerWin() + : ScreenDrawer(), + rect_(GetScreenRect()), + window_(CreateDrawerWindow(rect_)), + hdc_(GetWindowDC(window_)) { + // We do not need to handle any messages for the `window_`, so disable Windows + // from processing windows ghosting feature. + DisableProcessWindowsGhosting(); + + // Always use stock pen (DC_PEN) and brush (DC_BRUSH). + SelectObject(hdc_, GetStockObject(DC_PEN)); + SelectObject(hdc_, GetStockObject(DC_BRUSH)); + BringToFront(); +} + +ScreenDrawerWin::~ScreenDrawerWin() { + ReleaseDC(NULL, hdc_); + DestroyWindow(window_); + // Unfortunately there is no EnableProcessWindowsGhosting() API. +} + +DesktopRect ScreenDrawerWin::DrawableRegion() { + return rect_; +} + +void ScreenDrawerWin::DrawRectangle(DesktopRect rect, RgbaColor color) { + if (rect.width() == 1 && rect.height() == 1) { + // Rectangle function cannot draw a 1 pixel rectangle. + DrawDot(rect.top_left(), color); + return; + } + + if (rect.width() == 1 || rect.height() == 1) { + // Rectangle function cannot draw a 1 pixel rectangle. + DrawLine(rect.top_left(), DesktopVector(rect.right(), rect.bottom()), + color); + return; + } + + SetDCBrushColor(hdc_, ColorToRef(color)); + SetDCPenColor(hdc_, ColorToRef(color)); + Rectangle(hdc_, rect.left(), rect.top(), rect.right(), rect.bottom()); +} + +void ScreenDrawerWin::Clear() { + DrawRectangle(rect_, RgbaColor(0, 0, 0)); +} + +// TODO(zijiehe): Find the right signal to indicate the finish of all pending +// paintings. +void ScreenDrawerWin::WaitForPendingDraws() { + BringToFront(); + SleepMs(50); +} + +bool ScreenDrawerWin::MayDrawIncompleteShapes() { + return true; +} + +WindowId ScreenDrawerWin::window_id() const { + return reinterpret_cast<WindowId>(window_); +} + +void ScreenDrawerWin::DrawLine(DesktopVector start, + DesktopVector end, + RgbaColor color) { + POINT points[2]; + points[0].x = start.x(); + points[0].y = start.y(); + points[1].x = end.x(); + points[1].y = end.y(); + SetDCPenColor(hdc_, ColorToRef(color)); + Polyline(hdc_, points, 2); +} + +void ScreenDrawerWin::DrawDot(DesktopVector vect, RgbaColor color) { + SetPixel(hdc_, vect.x(), vect.y(), ColorToRef(color)); +} + +void ScreenDrawerWin::BringToFront() { + if (SetWindowPos(window_, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE) != FALSE) { + return; + } + + long ex_style = GetWindowLong(window_, GWL_EXSTYLE); + ex_style |= WS_EX_TOPMOST; + if (SetWindowLong(window_, GWL_EXSTYLE, ex_style) != 0) { + return; + } + + BringWindowToTop(window_); +} + +} // namespace + +// static +std::unique_ptr<ScreenDrawerLock> ScreenDrawerLock::Create() { + return std::unique_ptr<ScreenDrawerLock>(new ScreenDrawerLockWin()); +} + +// static +std::unique_ptr<ScreenDrawer> ScreenDrawer::Create() { + return std::unique_ptr<ScreenDrawer>(new ScreenDrawerWin()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.cc b/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.cc new file mode 100644 index 0000000000..e374038cbc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/shared_desktop_frame.h" + +#include <memory> +#include <type_traits> +#include <utility> + +namespace webrtc { + +SharedDesktopFrame::~SharedDesktopFrame() {} + +// static +std::unique_ptr<SharedDesktopFrame> SharedDesktopFrame::Wrap( + std::unique_ptr<DesktopFrame> desktop_frame) { + return std::unique_ptr<SharedDesktopFrame>(new SharedDesktopFrame( + rtc::scoped_refptr<Core>(new Core(std::move(desktop_frame))))); +} + +SharedDesktopFrame* SharedDesktopFrame::Wrap(DesktopFrame* desktop_frame) { + return Wrap(std::unique_ptr<DesktopFrame>(desktop_frame)).release(); +} + +DesktopFrame* SharedDesktopFrame::GetUnderlyingFrame() { + return core_->get(); +} + +bool SharedDesktopFrame::ShareFrameWith(const SharedDesktopFrame& other) const { + return core_->get() == other.core_->get(); +} + +std::unique_ptr<SharedDesktopFrame> SharedDesktopFrame::Share() { + std::unique_ptr<SharedDesktopFrame> result(new SharedDesktopFrame(core_)); + result->CopyFrameInfoFrom(*this); + return result; +} + +bool SharedDesktopFrame::IsShared() { + return !core_->HasOneRef(); +} + +SharedDesktopFrame::SharedDesktopFrame(rtc::scoped_refptr<Core> core) + : DesktopFrame((*core)->size(), + (*core)->stride(), + (*core)->data(), + (*core)->shared_memory()), + core_(core) { + CopyFrameInfoFrom(*(core_->get())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.h b/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.h new file mode 100644 index 0000000000..c6f52247f4 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/shared_desktop_frame.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ +#define MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ + +#include <memory> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// SharedDesktopFrame is a DesktopFrame that may have multiple instances all +// sharing the same buffer. +class RTC_EXPORT SharedDesktopFrame final : public DesktopFrame { + public: + ~SharedDesktopFrame() override; + + SharedDesktopFrame(const SharedDesktopFrame&) = delete; + SharedDesktopFrame& operator=(const SharedDesktopFrame&) = delete; + + static std::unique_ptr<SharedDesktopFrame> Wrap( + std::unique_ptr<DesktopFrame> desktop_frame); + + // Deprecated. + // TODO(sergeyu): remove this method. + static SharedDesktopFrame* Wrap(DesktopFrame* desktop_frame); + + // Deprecated. Clients do not need to know the underlying DesktopFrame + // instance. + // TODO(zijiehe): Remove this method. + // Returns the underlying instance of DesktopFrame. + DesktopFrame* GetUnderlyingFrame(); + + // Returns whether `this` and `other` share the underlying DesktopFrame. + bool ShareFrameWith(const SharedDesktopFrame& other) const; + + // Creates a clone of this object. + std::unique_ptr<SharedDesktopFrame> Share(); + + // Checks if the frame is currently shared. If it returns false it's + // guaranteed that there are no clones of the object. + bool IsShared(); + + private: + typedef rtc::FinalRefCountedObject<std::unique_ptr<DesktopFrame>> Core; + + SharedDesktopFrame(rtc::scoped_refptr<Core> core); + + const rtc::scoped_refptr<Core> core_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/shared_memory.cc b/third_party/libwebrtc/modules/desktop_capture/shared_memory.cc new file mode 100644 index 0000000000..b4ff78b2a0 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/shared_memory.cc @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +#if defined(WEBRTC_WIN) +const SharedMemory::Handle SharedMemory::kInvalidHandle = NULL; +#else +const SharedMemory::Handle SharedMemory::kInvalidHandle = -1; +#endif + +SharedMemory::SharedMemory(void* data, size_t size, Handle handle, int id) + : data_(data), size_(size), handle_(handle), id_(id) {} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/shared_memory.h b/third_party/libwebrtc/modules/desktop_capture/shared_memory.h new file mode 100644 index 0000000000..a7add4447b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/shared_memory.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ +#define MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ + +#include <stddef.h> + +#if defined(WEBRTC_WIN) +// Forward declare HANDLE in a windows.h compatible way so that we can avoid +// including windows.h. +typedef void* HANDLE; +#endif + +#include <memory> + +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// SharedMemory is a base class for shared memory. It stores all required +// parameters of the buffer, but doesn't have any logic to allocate or destroy +// the actual buffer. DesktopCapturer consumers that need to use shared memory +// for video frames must extend this class with creation and destruction logic +// specific for the target platform and then call +// DesktopCapturer::SetSharedMemoryFactory(). +class RTC_EXPORT SharedMemory { + public: +#if defined(WEBRTC_WIN) + typedef HANDLE Handle; + static const Handle kInvalidHandle; +#else + typedef int Handle; + static const Handle kInvalidHandle; +#endif + + void* data() const { return data_; } + size_t size() const { return size_; } + + // Platform-specific handle of the buffer. + Handle handle() const { return handle_; } + + // Integer identifier that can be used used by consumers of DesktopCapturer + // interface to identify shared memory buffers it created. + int id() const { return id_; } + + virtual ~SharedMemory() {} + + SharedMemory(const SharedMemory&) = delete; + SharedMemory& operator=(const SharedMemory&) = delete; + + protected: + SharedMemory(void* data, size_t size, Handle handle, int id); + + void* const data_; + const size_t size_; + const Handle handle_; + const int id_; +}; + +// Interface used to create SharedMemory instances. +class SharedMemoryFactory { + public: + SharedMemoryFactory() {} + virtual ~SharedMemoryFactory() {} + + SharedMemoryFactory(const SharedMemoryFactory&) = delete; + SharedMemoryFactory& operator=(const SharedMemoryFactory&) = delete; + + virtual std::unique_ptr<SharedMemory> CreateSharedMemory(size_t size) = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/test_utils.cc b/third_party/libwebrtc/modules/desktop_capture/test_utils.cc new file mode 100644 index 0000000000..9483bf41ea --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/test_utils.cc @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/test_utils.h" + +#include <stdint.h> +#include <string.h> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +void ClearDesktopFrame(DesktopFrame* frame) { + RTC_DCHECK(frame); + uint8_t* data = frame->data(); + for (int i = 0; i < frame->size().height(); i++) { + memset(data, 0, frame->size().width() * DesktopFrame::kBytesPerPixel); + data += frame->stride(); + } +} + +bool DesktopFrameDataEquals(const DesktopFrame& left, + const DesktopFrame& right) { + if (!left.size().equals(right.size())) { + return false; + } + + const uint8_t* left_array = left.data(); + const uint8_t* right_array = right.data(); + for (int i = 0; i < left.size().height(); i++) { + if (memcmp(left_array, right_array, + DesktopFrame::kBytesPerPixel * left.size().width()) != 0) { + return false; + } + left_array += left.stride(); + right_array += right.stride(); + } + + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/test_utils.h b/third_party/libwebrtc/modules/desktop_capture/test_utils.h new file mode 100644 index 0000000000..8669fecba3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/test_utils.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_TEST_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_TEST_UTILS_H_ + +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +// Clears a DesktopFrame `frame` by setting its data() into 0. +void ClearDesktopFrame(DesktopFrame* frame); + +// Compares size() and data() of two DesktopFrames `left` and `right`. +bool DesktopFrameDataEquals(const DesktopFrame& left, + const DesktopFrame& right); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_TEST_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/test_utils_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/test_utils_unittest.cc new file mode 100644 index 0000000000..c1326f01cc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/test_utils_unittest.cc @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/test_utils.h" + +#include <stdint.h> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/rgba_color.h" +#include "rtc_base/checks.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +void PaintDesktopFrame(DesktopFrame* frame, + DesktopVector pos, + RgbaColor color) { + ASSERT_TRUE(frame); + ASSERT_TRUE(DesktopRect::MakeSize(frame->size()).Contains(pos)); + *reinterpret_cast<uint32_t*>(frame->GetFrameDataAtPos(pos)) = + color.ToUInt32(); +} + +// A DesktopFrame implementation to store data in heap, but the stide is +// doubled. +class DoubleSizeDesktopFrame : public DesktopFrame { + public: + explicit DoubleSizeDesktopFrame(DesktopSize size); + ~DoubleSizeDesktopFrame() override; +}; + +DoubleSizeDesktopFrame::DoubleSizeDesktopFrame(DesktopSize size) + : DesktopFrame( + size, + kBytesPerPixel * size.width() * 2, + new uint8_t[kBytesPerPixel * size.width() * size.height() * 2], + nullptr) {} + +DoubleSizeDesktopFrame::~DoubleSizeDesktopFrame() { + delete[] data_; +} + +} // namespace + +TEST(TestUtilsTest, BasicDataEqualsCases) { + BasicDesktopFrame frame(DesktopSize(4, 4)); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + PaintDesktopFrame(&frame, DesktopVector(i, j), RgbaColor(4U * j + i)); + } + } + + ASSERT_TRUE(DesktopFrameDataEquals(frame, frame)); + BasicDesktopFrame other(DesktopSize(4, 4)); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + PaintDesktopFrame(&other, DesktopVector(i, j), RgbaColor(4U * j + i)); + } + } + ASSERT_TRUE(DesktopFrameDataEquals(frame, other)); + PaintDesktopFrame(&other, DesktopVector(2, 2), RgbaColor(0U)); + ASSERT_FALSE(DesktopFrameDataEquals(frame, other)); +} + +TEST(TestUtilsTest, DifferentSizeShouldNotEqual) { + BasicDesktopFrame frame(DesktopSize(4, 4)); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + PaintDesktopFrame(&frame, DesktopVector(i, j), RgbaColor(4U * j + i)); + } + } + + BasicDesktopFrame other(DesktopSize(2, 8)); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 8; j++) { + PaintDesktopFrame(&other, DesktopVector(i, j), RgbaColor(2U * j + i)); + } + } + + ASSERT_FALSE(DesktopFrameDataEquals(frame, other)); +} + +TEST(TestUtilsTest, DifferentStrideShouldBeComparable) { + BasicDesktopFrame frame(DesktopSize(4, 4)); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + PaintDesktopFrame(&frame, DesktopVector(i, j), RgbaColor(4U * j + i)); + } + } + + ASSERT_TRUE(DesktopFrameDataEquals(frame, frame)); + DoubleSizeDesktopFrame other(DesktopSize(4, 4)); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + PaintDesktopFrame(&other, DesktopVector(i, j), RgbaColor(4U * j + i)); + } + } + ASSERT_TRUE(DesktopFrameDataEquals(frame, other)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc b/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc new file mode 100644 index 0000000000..1d645098e2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor.cc @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/cursor.h" + +#include <algorithm> +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/win/scoped_gdi_object.h" +#include "rtc_base/logging.h" +#include "rtc_base/system/arch.h" + +namespace webrtc { + +namespace { + +#if defined(WEBRTC_ARCH_LITTLE_ENDIAN) + +#define RGBA(r, g, b, a) \ + ((((a) << 24) & 0xff000000) | (((b) << 16) & 0xff0000) | \ + (((g) << 8) & 0xff00) | ((r)&0xff)) + +#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN) + +#define RGBA(r, g, b, a) \ + ((((r) << 24) & 0xff000000) | (((g) << 16) & 0xff0000) | \ + (((b) << 8) & 0xff00) | ((a)&0xff)) + +#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN) + +const int kBytesPerPixel = DesktopFrame::kBytesPerPixel; + +// Pixel colors used when generating cursor outlines. +const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff); +const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff); +const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0); + +const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff); + +// Expands the cursor shape to add a white outline for visibility against +// dark backgrounds. +void AddCursorOutline(int width, int height, uint32_t* data) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // If this is a transparent pixel (bgr == 0 and alpha = 0), check the + // neighbor pixels to see if this should be changed to an outline pixel. + if (*data == kPixelRgbaTransparent) { + // Change to white pixel if any neighbors (top, bottom, left, right) + // are black. + if ((y > 0 && data[-width] == kPixelRgbaBlack) || + (y < height - 1 && data[width] == kPixelRgbaBlack) || + (x > 0 && data[-1] == kPixelRgbaBlack) || + (x < width - 1 && data[1] == kPixelRgbaBlack)) { + *data = kPixelRgbaWhite; + } + } + data++; + } + } +} + +// Premultiplies RGB components of the pixel data in the given image by +// the corresponding alpha components. +void AlphaMul(uint32_t* data, int width, int height) { + static_assert(sizeof(uint32_t) == kBytesPerPixel, + "size of uint32 should be the number of bytes per pixel"); + + for (uint32_t* data_end = data + width * height; data != data_end; ++data) { + RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data); + RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data); + to->rgbBlue = + (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff; + to->rgbGreen = + (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff; + to->rgbRed = + (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff; + } +} + +// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component. +// Returns true if non-zero alpha is found. `stride` is expressed in pixels. +bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) { + const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (plane->rgbReserved != 0) + return true; + plane += 1; + } + plane += stride - width; + } + + return false; +} + +} // namespace + +MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) { + ICONINFO iinfo; + if (!GetIconInfo(cursor, &iinfo)) { + RTC_LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = " + << GetLastError(); + return NULL; + } + + int hotspot_x = iinfo.xHotspot; + int hotspot_y = iinfo.yHotspot; + + // Make sure the bitmaps will be freed. + win::ScopedBitmap scoped_mask(iinfo.hbmMask); + win::ScopedBitmap scoped_color(iinfo.hbmColor); + bool is_color = iinfo.hbmColor != NULL; + + // Get `scoped_mask` dimensions. + BITMAP bitmap_info; + if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) { + RTC_LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = " + << GetLastError(); + return NULL; + } + + int width = bitmap_info.bmWidth; + int height = bitmap_info.bmHeight; + std::unique_ptr<uint32_t[]> mask_data(new uint32_t[width * height]); + + // Get pixel data from `scoped_mask` converting it to 32bpp along the way. + // GetDIBits() sets the alpha component of every pixel to 0. + BITMAPV5HEADER bmi = {0}; + bmi.bV5Size = sizeof(bmi); + bmi.bV5Width = width; + bmi.bV5Height = -height; // request a top-down bitmap. + bmi.bV5Planes = 1; + bmi.bV5BitCount = kBytesPerPixel * 8; + bmi.bV5Compression = BI_RGB; + bmi.bV5AlphaMask = 0xff000000; + bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE; + bmi.bV5Intent = LCS_GM_BUSINESS; + if (!GetDIBits(dc, scoped_mask, 0, height, mask_data.get(), + reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) { + RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = " + << GetLastError(); + return NULL; + } + + uint32_t* mask_plane = mask_data.get(); + std::unique_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(width, height))); + bool has_alpha = false; + + if (is_color) { + image.reset(new BasicDesktopFrame(DesktopSize(width, height))); + // Get the pixels from the color bitmap. + if (!GetDIBits(dc, scoped_color, 0, height, image->data(), + reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS)) { + RTC_LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = " + << GetLastError(); + return NULL; + } + + // GetDIBits() does not provide any indication whether the bitmap has alpha + // channel, so we use HasAlphaChannel() below to find it out. + has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()), + width, width, height); + } else { + // For non-color cursors, the mask contains both an AND and an XOR mask and + // the height includes both. Thus, the width is correct, but we need to + // divide by 2 to get the correct mask height. + height /= 2; + + image.reset(new BasicDesktopFrame(DesktopSize(width, height))); + + // The XOR mask becomes the color bitmap. + memcpy(image->data(), mask_plane + (width * height), + image->stride() * height); + } + + // Reconstruct transparency from the mask if the color image does not has + // alpha channel. + if (!has_alpha) { + bool add_outline = false; + uint32_t* dst = reinterpret_cast<uint32_t*>(image->data()); + uint32_t* mask = mask_plane; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // The two bitmaps combine as follows: + // mask color Windows Result Our result RGB Alpha + // 0 00 Black Black 00 ff + // 0 ff White White ff ff + // 1 00 Screen Transparent 00 00 + // 1 ff Reverse-screen Black 00 ff + // + // Since we don't support XOR cursors, we replace the "Reverse Screen" + // with black. In this case, we also add an outline around the cursor + // so that it is visible against a dark background. + if (*mask == kPixelRgbWhite) { + if (*dst != 0) { + add_outline = true; + *dst = kPixelRgbaBlack; + } else { + *dst = kPixelRgbaTransparent; + } + } else { + *dst = kPixelRgbaBlack ^ *dst; + } + + ++dst; + ++mask; + } + } + if (add_outline) { + AddCursorOutline(width, height, + reinterpret_cast<uint32_t*>(image->data())); + } + } + + // Pre-multiply the resulting pixels since MouseCursor uses premultiplied + // images. + AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height); + + return new MouseCursor(image.release(), DesktopVector(hotspot_x, hotspot_y)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor.h b/third_party/libwebrtc/modules/desktop_capture/win/cursor.h new file mode 100644 index 0000000000..54d78164a3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_ + +#include <windows.h> + +namespace webrtc { + +class MouseCursor; + +// Converts an HCURSOR into a `MouseCursor` instance. +MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur Binary files differnew file mode 100644 index 0000000000..27702b825c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur Binary files differnew file mode 100644 index 0000000000..7e0d8596da --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur Binary files differnew file mode 100644 index 0000000000..fefb09e1a1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur Binary files differnew file mode 100644 index 0000000000..4f8a094f31 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur Binary files differnew file mode 100644 index 0000000000..ac9cdbfbb3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur Binary files differnew file mode 100644 index 0000000000..efdbee5415 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur Binary files differnew file mode 100644 index 0000000000..9678d55446 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc new file mode 100644 index 0000000000..23f5d89571 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest.cc @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/cursor.h" + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/win/cursor_unittest_resources.h" +#include "modules/desktop_capture/win/scoped_gdi_object.h" +#include "test/gmock.h" + +namespace webrtc { + +namespace { + +// Loads `left` from resources, converts it to a `MouseCursor` instance and +// compares pixels with `right`. Returns true of MouseCursor bits match `right`. +// `right` must be a 32bpp cursor with alpha channel. +bool ConvertToMouseShapeAndCompare(unsigned left, unsigned right) { + HMODULE instance = GetModuleHandle(NULL); + + // Load `left` from the EXE module's resources. + win::ScopedCursor cursor(reinterpret_cast<HCURSOR>( + LoadImage(instance, MAKEINTRESOURCE(left), IMAGE_CURSOR, 0, 0, 0))); + EXPECT_TRUE(cursor != NULL); + + // Convert `cursor` to `mouse_shape`. + HDC dc = GetDC(NULL); + std::unique_ptr<MouseCursor> mouse_shape( + CreateMouseCursorFromHCursor(dc, cursor)); + ReleaseDC(NULL, dc); + + EXPECT_TRUE(mouse_shape.get()); + + // Load `right`. + cursor.Set(reinterpret_cast<HCURSOR>( + LoadImage(instance, MAKEINTRESOURCE(right), IMAGE_CURSOR, 0, 0, 0))); + + ICONINFO iinfo; + EXPECT_TRUE(GetIconInfo(cursor, &iinfo)); + EXPECT_TRUE(iinfo.hbmColor); + + // Make sure the bitmaps will be freed. + win::ScopedBitmap scoped_mask(iinfo.hbmMask); + win::ScopedBitmap scoped_color(iinfo.hbmColor); + + // Get `scoped_color` dimensions. + BITMAP bitmap_info; + EXPECT_TRUE(GetObject(scoped_color, sizeof(bitmap_info), &bitmap_info)); + + int width = bitmap_info.bmWidth; + int height = bitmap_info.bmHeight; + EXPECT_TRUE(DesktopSize(width, height).equals(mouse_shape->image()->size())); + + // Get the pixels from `scoped_color`. + int size = width * height; + std::unique_ptr<uint32_t[]> data(new uint32_t[size]); + EXPECT_TRUE(GetBitmapBits(scoped_color, size * sizeof(uint32_t), data.get())); + + // Compare the 32bpp image in `mouse_shape` with the one loaded from `right`. + return memcmp(data.get(), mouse_shape->image()->data(), + size * sizeof(uint32_t)) == 0; +} + +} // namespace + +TEST(MouseCursorTest, MatchCursors) { + EXPECT_TRUE( + ConvertToMouseShapeAndCompare(IDD_CURSOR1_24BPP, IDD_CURSOR1_32BPP)); + + EXPECT_TRUE( + ConvertToMouseShapeAndCompare(IDD_CURSOR1_8BPP, IDD_CURSOR1_32BPP)); + + EXPECT_TRUE( + ConvertToMouseShapeAndCompare(IDD_CURSOR2_1BPP, IDD_CURSOR2_32BPP)); + + EXPECT_TRUE( + ConvertToMouseShapeAndCompare(IDD_CURSOR3_4BPP, IDD_CURSOR3_32BPP)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h new file mode 100644 index 0000000000..f583554d68 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_ + +#define IDD_CURSOR1_24BPP 101 +#define IDD_CURSOR1_32BPP 102 +#define IDD_CURSOR1_8BPP 103 + +#define IDD_CURSOR2_1BPP 104 +#define IDD_CURSOR2_32BPP 105 + +#define IDD_CURSOR3_4BPP 106 +#define IDD_CURSOR3_32BPP 107 + +#endif // MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc new file mode 100644 index 0000000000..90073791c9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/cursor_unittest_resources.rc @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/cursor_unittest_resources.h" + +// These cursors are matched with their less than 32bpp counterparts below. +IDD_CURSOR1_32BPP CURSOR "cursor_test_data/1_32bpp.cur" +IDD_CURSOR2_32BPP CURSOR "cursor_test_data/2_32bpp.cur" +IDD_CURSOR3_32BPP CURSOR "cursor_test_data/3_32bpp.cur" + +// Matches IDD_CURSOR1_32BPP. +IDD_CURSOR1_24BPP CURSOR "cursor_test_data/1_24bpp.cur" + +// Matches IDD_CURSOR1_32BPP. +IDD_CURSOR1_8BPP CURSOR "cursor_test_data/1_8bpp.cur" + +// Matches IDD_CURSOR2_32BPP. +IDD_CURSOR2_1BPP CURSOR "cursor_test_data/2_1bpp.cur" + +// Matches IDD_CURSOR3_32BPP. +IDD_CURSOR3_4BPP CURSOR "cursor_test_data/3_4bpp.cur" diff --git a/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc new file mode 100644 index 0000000000..3d46117501 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/d3d_device.h" + +#include <utility> + +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +D3dDevice::D3dDevice() = default; +D3dDevice::D3dDevice(const D3dDevice& other) = default; +D3dDevice::D3dDevice(D3dDevice&& other) = default; +D3dDevice::~D3dDevice() = default; + +bool D3dDevice::Initialize(const ComPtr<IDXGIAdapter>& adapter) { + dxgi_adapter_ = adapter; + if (!dxgi_adapter_) { + RTC_LOG(LS_WARNING) << "An empty IDXGIAdapter instance has been received."; + return false; + } + + D3D_FEATURE_LEVEL feature_level; + // Default feature levels contain D3D 9.1 through D3D 11.0. + _com_error error = D3D11CreateDevice( + adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED, + nullptr, 0, D3D11_SDK_VERSION, d3d_device_.GetAddressOf(), &feature_level, + context_.GetAddressOf()); + if (error.Error() != S_OK || !d3d_device_ || !context_) { + RTC_LOG(LS_WARNING) << "D3D11CreateDevice returned: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + if (feature_level < D3D_FEATURE_LEVEL_11_0) { + RTC_LOG(LS_WARNING) + << "D3D11CreateDevice returned an instance without DirectX 11 support, " + << "level " << feature_level << ". Following initialization may fail."; + // D3D_FEATURE_LEVEL_11_0 is not officially documented on MSDN to be a + // requirement of Dxgi duplicator APIs. + } + + error = d3d_device_.As(&dxgi_device_); + if (error.Error() != S_OK || !dxgi_device_) { + RTC_LOG(LS_WARNING) + << "ID3D11Device is not an implementation of IDXGIDevice, " + << "this usually means the system does not support DirectX " + << "11. Error received: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + return true; +} + +// static +std::vector<D3dDevice> D3dDevice::EnumDevices() { + ComPtr<IDXGIFactory1> factory; + _com_error error = + CreateDXGIFactory1(__uuidof(IDXGIFactory1), + reinterpret_cast<void**>(factory.GetAddressOf())); + if (error.Error() != S_OK || !factory) { + RTC_LOG(LS_WARNING) << "Cannot create IDXGIFactory1: " + << desktop_capture::utils::ComErrorToString(error); + return std::vector<D3dDevice>(); + } + + std::vector<D3dDevice> result; + for (int i = 0;; i++) { + ComPtr<IDXGIAdapter> adapter; + error = factory->EnumAdapters(i, adapter.GetAddressOf()); + if (error.Error() == S_OK) { + D3dDevice device; + if (device.Initialize(adapter)) { + result.push_back(std::move(device)); + } + } else if (error.Error() == DXGI_ERROR_NOT_FOUND) { + break; + } else { + RTC_LOG(LS_WARNING) + << "IDXGIFactory1::EnumAdapters returned an unexpected error: " + << desktop_capture::utils::ComErrorToString(error); + } + } + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h new file mode 100644 index 0000000000..aeb9d1823a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/d3d_device.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ + +#include <comdef.h> +#include <d3d11.h> +#include <dxgi.h> +#include <wrl/client.h> + +#include <vector> + +namespace webrtc { + +// A wrapper of ID3D11Device and its corresponding context and IDXGIAdapter. +// This class represents one video card in the system. +class D3dDevice { + public: + D3dDevice(const D3dDevice& other); + D3dDevice(D3dDevice&& other); + ~D3dDevice(); + + ID3D11Device* d3d_device() const { return d3d_device_.Get(); } + + ID3D11DeviceContext* context() const { return context_.Get(); } + + IDXGIDevice* dxgi_device() const { return dxgi_device_.Get(); } + + IDXGIAdapter* dxgi_adapter() const { return dxgi_adapter_.Get(); } + + // Returns all D3dDevice instances on the system. Returns an empty vector if + // anything wrong. + static std::vector<D3dDevice> EnumDevices(); + + private: + // Instances of D3dDevice should only be created by EnumDevices() static + // function. + D3dDevice(); + + // Initializes the D3dDevice from an IDXGIAdapter. + bool Initialize(const Microsoft::WRL::ComPtr<IDXGIAdapter>& adapter); + + Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_; + Microsoft::WRL::ComPtr<ID3D11DeviceContext> context_; + Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device_; + Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc b/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc new file mode 100644 index 0000000000..4a671dd9ae --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop.cc @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/desktop.h" + +#include <vector> + +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" + +namespace webrtc { + +Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) {} + +Desktop::~Desktop() { + if (own_ && desktop_ != NULL) { + if (!::CloseDesktop(desktop_)) { + RTC_LOG(LS_ERROR) << "Failed to close the owned desktop handle: " + << GetLastError(); + } + } +} + +bool Desktop::GetName(std::wstring* desktop_name_out) const { + if (desktop_ == NULL) + return false; + + DWORD length = 0; + int rv = GetUserObjectInformationW(desktop_, UOI_NAME, NULL, 0, &length); + if (rv || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + abort(); + + length /= sizeof(WCHAR); + std::vector<WCHAR> buffer(length); + if (!GetUserObjectInformationW(desktop_, UOI_NAME, &buffer[0], + length * sizeof(WCHAR), &length)) { + RTC_LOG(LS_ERROR) << "Failed to query the desktop name: " << GetLastError(); + return false; + } + + desktop_name_out->assign(&buffer[0], length / sizeof(WCHAR)); + return true; +} + +bool Desktop::IsSame(const Desktop& other) const { + std::wstring name; + if (!GetName(&name)) + return false; + + std::wstring other_name; + if (!other.GetName(&other_name)) + return false; + + return name == other_name; +} + +bool Desktop::SetThreadDesktop() const { + if (!::SetThreadDesktop(desktop_)) { + RTC_LOG(LS_ERROR) << "Failed to assign the desktop to the current thread: " + << GetLastError(); + return false; + } + + return true; +} + +Desktop* Desktop::GetDesktop(const WCHAR* desktop_name) { + ACCESS_MASK desired_access = DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE; + HDESK desktop = OpenDesktopW(desktop_name, 0, FALSE, desired_access); + if (desktop == NULL) { + RTC_LOG(LS_ERROR) << "Failed to open the desktop '" + << rtc::ToUtf8(desktop_name) << "': " << GetLastError(); + return NULL; + } + + return new Desktop(desktop, true); +} + +Desktop* Desktop::GetInputDesktop() { + HDESK desktop = OpenInputDesktop( + 0, FALSE, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); + if (desktop == NULL) + return NULL; + + return new Desktop(desktop, true); +} + +Desktop* Desktop::GetThreadDesktop() { + HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId()); + if (desktop == NULL) { + RTC_LOG(LS_ERROR) + << "Failed to retrieve the handle of the desktop assigned to " + "the current thread: " + << GetLastError(); + return NULL; + } + + return new Desktop(desktop, false); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop.h b/third_party/libwebrtc/modules/desktop_capture/win/desktop.h new file mode 100644 index 0000000000..01bed8592d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ + +#include <windows.h> + +#include <string> + +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class RTC_EXPORT Desktop { + public: + ~Desktop(); + + Desktop(const Desktop&) = delete; + Desktop& operator=(const Desktop&) = delete; + + // Returns the name of the desktop represented by the object. Return false if + // quering the name failed for any reason. + bool GetName(std::wstring* desktop_name_out) const; + + // Returns true if `other` has the same name as this desktop. Returns false + // in any other case including failing Win32 APIs and uninitialized desktop + // handles. + bool IsSame(const Desktop& other) const; + + // Assigns the desktop to the current thread. Returns false is the operation + // failed for any reason. + bool SetThreadDesktop() const; + + // Returns the desktop by its name or NULL if an error occurs. + static Desktop* GetDesktop(const wchar_t* desktop_name); + + // Returns the desktop currently receiving user input or NULL if an error + // occurs. + static Desktop* GetInputDesktop(); + + // Returns the desktop currently assigned to the calling thread or NULL if + // an error occurs. + static Desktop* GetThreadDesktop(); + + private: + Desktop(HDESK desktop, bool own); + + // The desktop handle. + HDESK desktop_; + + // True if `desktop_` must be closed on teardown. + bool own_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc new file mode 100644 index 0000000000..476ddc4aba --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.cc @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/desktop_capture_utils.h" + +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace desktop_capture { +namespace utils { + +// Generates a human-readable string from a COM error. +std::string ComErrorToString(const _com_error& error) { + char buffer[1024]; + rtc::SimpleStringBuilder string_builder(buffer); + // Use _bstr_t to simplify the wchar to char conversion for ErrorMessage(). + _bstr_t error_message(error.ErrorMessage()); + string_builder.AppendFormat("HRESULT: 0x%08X, Message: %s", error.Error(), + static_cast<const char*>(error_message)); + return string_builder.str(); +} + +} // namespace utils +} // namespace desktop_capture +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h new file mode 100644 index 0000000000..ebf31419ce --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/desktop_capture_utils.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_ + +#include <comdef.h> + +#include <string> + +namespace webrtc { +namespace desktop_capture { +namespace utils { + +// Generates a human-readable string from a COM error. +std::string ComErrorToString(const _com_error& error); + +} // namespace utils +} // namespace desktop_capture +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_CAPTURE_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc new file mode 100644 index 0000000000..52d89214d8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.cc @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/display_configuration_monitor.h" + +#include "modules/desktop_capture/win/screen_capture_utils.h" + +namespace webrtc { + +bool DisplayConfigurationMonitor::IsChanged() { + DesktopRect rect = GetFullscreenRect(); + if (!initialized_) { + initialized_ = true; + rect_ = rect; + return false; + } + + if (rect.equals(rect_)) { + return false; + } + + rect_ = rect; + return true; +} + +void DisplayConfigurationMonitor::Reset() { + initialized_ = false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h new file mode 100644 index 0000000000..39c211cfbe --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/display_configuration_monitor.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_ + +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// A passive monitor to detect the change of display configuration on a Windows +// system. +// TODO(zijiehe): Also check for pixel format changes. +class DisplayConfigurationMonitor { + public: + // Checks whether the change of display configuration has happened after last + // IsChanged() call. This function won't return true for the first time after + // constructor or Reset() call. + bool IsChanged(); + + // Resets to the initial state. + void Reset(); + + private: + DesktopRect rect_; + bool initialized_ = false; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DISPLAY_CONFIGURATION_MONITOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc new file mode 100644 index 0000000000..88ec4e25bf --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_adapter_duplicator.h" + +#include <comdef.h> +#include <dxgi.h> + +#include <algorithm> + +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +namespace { + +bool IsValidRect(const RECT& rect) { + return rect.right > rect.left && rect.bottom > rect.top; +} + +} // namespace + +DxgiAdapterDuplicator::DxgiAdapterDuplicator(const D3dDevice& device) + : device_(device) {} +DxgiAdapterDuplicator::DxgiAdapterDuplicator(DxgiAdapterDuplicator&&) = default; +DxgiAdapterDuplicator::~DxgiAdapterDuplicator() = default; + +bool DxgiAdapterDuplicator::Initialize() { + if (DoInitialize()) { + return true; + } + duplicators_.clear(); + return false; +} + +bool DxgiAdapterDuplicator::DoInitialize() { + for (int i = 0;; i++) { + ComPtr<IDXGIOutput> output; + _com_error error = + device_.dxgi_adapter()->EnumOutputs(i, output.GetAddressOf()); + if (error.Error() == DXGI_ERROR_NOT_FOUND) { + break; + } + + if (error.Error() == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) { + RTC_LOG(LS_WARNING) << "IDXGIAdapter::EnumOutputs returned " + << "NOT_CURRENTLY_AVAILABLE. This may happen when " + << "running in session 0."; + break; + } + + if (error.Error() != S_OK || !output) { + RTC_LOG(LS_WARNING) << "IDXGIAdapter::EnumOutputs returned an unexpected " + << "result: " + << desktop_capture::utils::ComErrorToString(error); + continue; + } + + DXGI_OUTPUT_DESC desc; + error = output->GetDesc(&desc); + if (error.Error() == S_OK) { + if (desc.AttachedToDesktop && IsValidRect(desc.DesktopCoordinates)) { + ComPtr<IDXGIOutput1> output1; + error = output.As(&output1); + if (error.Error() != S_OK || !output1) { + RTC_LOG(LS_WARNING) + << "Failed to convert IDXGIOutput to IDXGIOutput1, this usually " + << "means the system does not support DirectX 11"; + continue; + } + DxgiOutputDuplicator duplicator(device_, output1, desc); + if (!duplicator.Initialize()) { + RTC_LOG(LS_WARNING) << "Failed to initialize DxgiOutputDuplicator on " + << "output " << i; + continue; + } + + duplicators_.push_back(std::move(duplicator)); + desktop_rect_.UnionWith(duplicators_.back().desktop_rect()); + } else { + RTC_LOG(LS_ERROR) << (desc.AttachedToDesktop ? "Attached" : "Detached") + << " output " << i << " (" + << desc.DesktopCoordinates.top << ", " + << desc.DesktopCoordinates.left << ") - (" + << desc.DesktopCoordinates.bottom << ", " + << desc.DesktopCoordinates.right << ") is ignored."; + } + } else { + RTC_LOG(LS_WARNING) << "Failed to get output description of device " << i + << ", ignore."; + } + } + + if (duplicators_.empty()) { + RTC_LOG(LS_WARNING) + << "Cannot initialize any DxgiOutputDuplicator instance."; + } + + return !duplicators_.empty(); +} + +void DxgiAdapterDuplicator::Setup(Context* context) { + RTC_DCHECK(context->contexts.empty()); + context->contexts.resize(duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Setup(&context->contexts[i]); + } +} + +void DxgiAdapterDuplicator::Unregister(const Context* const context) { + RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Unregister(&context->contexts[i]); + } +} + +bool DxgiAdapterDuplicator::Duplicate(Context* context, + SharedDesktopFrame* target) { + RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + if (!duplicators_[i].Duplicate(&context->contexts[i], + duplicators_[i].desktop_rect().top_left(), + target)) { + return false; + } + } + return true; +} + +bool DxgiAdapterDuplicator::DuplicateMonitor(Context* context, + int monitor_id, + SharedDesktopFrame* target) { + RTC_DCHECK_GE(monitor_id, 0); + RTC_DCHECK_LT(monitor_id, duplicators_.size()); + RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size()); + return duplicators_[monitor_id].Duplicate(&context->contexts[monitor_id], + DesktopVector(), target); +} + +DesktopRect DxgiAdapterDuplicator::ScreenRect(int id) const { + RTC_DCHECK_GE(id, 0); + RTC_DCHECK_LT(id, duplicators_.size()); + return duplicators_[id].desktop_rect(); +} + +const std::string& DxgiAdapterDuplicator::GetDeviceName(int id) const { + RTC_DCHECK_GE(id, 0); + RTC_DCHECK_LT(id, duplicators_.size()); + return duplicators_[id].device_name(); +} + +int DxgiAdapterDuplicator::screen_count() const { + return static_cast<int>(duplicators_.size()); +} + +int64_t DxgiAdapterDuplicator::GetNumFramesCaptured() const { + int64_t min = INT64_MAX; + for (const auto& duplicator : duplicators_) { + min = std::min(min, duplicator.num_frames_captured()); + } + + return min; +} + +void DxgiAdapterDuplicator::TranslateRect(const DesktopVector& position) { + desktop_rect_.Translate(position); + RTC_DCHECK_GE(desktop_rect_.left(), 0); + RTC_DCHECK_GE(desktop_rect_.top(), 0); + for (auto& duplicator : duplicators_) { + duplicator.TranslateRect(position); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h new file mode 100644 index 0000000000..5931b51f9e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ + +#include <wrl/client.h> + +#include <vector> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/win/d3d_device.h" +#include "modules/desktop_capture/win/dxgi_context.h" +#include "modules/desktop_capture/win/dxgi_output_duplicator.h" + +namespace webrtc { + +// A container of DxgiOutputDuplicators to duplicate monitors attached to a +// single video card. +class DxgiAdapterDuplicator { + public: + using Context = DxgiAdapterContext; + + // Creates an instance of DxgiAdapterDuplicator from a D3dDevice. Only + // DxgiDuplicatorController can create an instance. + explicit DxgiAdapterDuplicator(const D3dDevice& device); + + // Move constructor, to make it possible to store instances of + // DxgiAdapterDuplicator in std::vector<>. + DxgiAdapterDuplicator(DxgiAdapterDuplicator&& other); + + ~DxgiAdapterDuplicator(); + + // Initializes the DxgiAdapterDuplicator from a D3dDevice. + bool Initialize(); + + // Sequentially calls Duplicate function of all the DxgiOutputDuplicator + // instances owned by this instance, and writes into `target`. + bool Duplicate(Context* context, SharedDesktopFrame* target); + + // Captures one monitor and writes into `target`. `monitor_id` should be + // between [0, screen_count()). + bool DuplicateMonitor(Context* context, + int monitor_id, + SharedDesktopFrame* target); + + // Returns desktop rect covered by this DxgiAdapterDuplicator. + DesktopRect desktop_rect() const { return desktop_rect_; } + + // Returns the size of one screen owned by this DxgiAdapterDuplicator. `id` + // should be between [0, screen_count()). + DesktopRect ScreenRect(int id) const; + + // Returns the device name of one screen owned by this DxgiAdapterDuplicator + // in utf8 encoding. `id` should be between [0, screen_count()). + const std::string& GetDeviceName(int id) const; + + // Returns the count of screens owned by this DxgiAdapterDuplicator. These + // screens can be retrieved by an interger in the range of + // [0, screen_count()). + int screen_count() const; + + void Setup(Context* context); + + void Unregister(const Context* const context); + + // The minimum num_frames_captured() returned by `duplicators_`. + int64_t GetNumFramesCaptured() const; + + // Moves `desktop_rect_` and all underlying `duplicators_`. See + // DxgiDuplicatorController::TranslateRect(). + void TranslateRect(const DesktopVector& position); + + private: + bool DoInitialize(); + + const D3dDevice device_; + std::vector<DxgiOutputDuplicator> duplicators_; + DesktopRect desktop_rect_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc new file mode 100644 index 0000000000..c18b238f03 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.cc @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_context.h" + +#include "modules/desktop_capture/win/dxgi_duplicator_controller.h" + +namespace webrtc { + +DxgiAdapterContext::DxgiAdapterContext() = default; +DxgiAdapterContext::DxgiAdapterContext(const DxgiAdapterContext& context) = + default; +DxgiAdapterContext::~DxgiAdapterContext() = default; + +DxgiFrameContext::DxgiFrameContext() = default; + +DxgiFrameContext::~DxgiFrameContext() { + Reset(); +} + +void DxgiFrameContext::Reset() { + DxgiDuplicatorController::Instance()->Unregister(this); + controller_id = 0; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h new file mode 100644 index 0000000000..59c2112db5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_context.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_ + +#include <vector> + +#include "modules/desktop_capture/desktop_region.h" + +namespace webrtc { + +// A DxgiOutputContext stores the status of a single DxgiFrame of +// DxgiOutputDuplicator. +struct DxgiOutputContext final { + // The updated region DxgiOutputDuplicator::DetectUpdatedRegion() output + // during last Duplicate() function call. It's always relative to the (0, 0). + DesktopRegion updated_region; +}; + +// A DxgiAdapterContext stores the status of a single DxgiFrame of +// DxgiAdapterDuplicator. +struct DxgiAdapterContext final { + DxgiAdapterContext(); + DxgiAdapterContext(const DxgiAdapterContext& other); + ~DxgiAdapterContext(); + + // Child DxgiOutputContext belongs to this AdapterContext. + std::vector<DxgiOutputContext> contexts; +}; + +// A DxgiFrameContext stores the status of a single DxgiFrame of +// DxgiDuplicatorController. +struct DxgiFrameContext final { + public: + DxgiFrameContext(); + // Unregister this Context instance from DxgiDuplicatorController during + // destructing. + ~DxgiFrameContext(); + + // Reset current Context, so it will be reinitialized next time. + void Reset(); + + // A Context will have an exactly same `controller_id` as + // DxgiDuplicatorController, to ensure it has been correctly setted up after + // each DxgiDuplicatorController::Initialize(). + int controller_id = 0; + + // Child DxgiAdapterContext belongs to this DxgiFrameContext. + std::vector<DxgiAdapterContext> contexts; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_CONTEXT_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc new file mode 100644 index 0000000000..a776896f6c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_duplicator_controller.h" + +#include <windows.h> + +#include <algorithm> +#include <string> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/win/dxgi_frame.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/sleep.h" + +namespace webrtc { + +namespace { + +constexpr DWORD kInvalidSessionId = 0xFFFFFFFF; + +DWORD GetCurrentSessionId() { + DWORD session_id = kInvalidSessionId; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) { + RTC_LOG(LS_WARNING) + << "Failed to retrieve current session Id, current binary " + "may not have required priviledge."; + } + return session_id; +} + +bool IsConsoleSession() { + return WTSGetActiveConsoleSessionId() == GetCurrentSessionId(); +} + +} // namespace + +// static +std::string DxgiDuplicatorController::ResultName( + DxgiDuplicatorController::Result result) { + switch (result) { + case Result::SUCCEEDED: + return "Succeeded"; + case Result::UNSUPPORTED_SESSION: + return "Unsupported session"; + case Result::FRAME_PREPARE_FAILED: + return "Frame preparation failed"; + case Result::INITIALIZATION_FAILED: + return "Initialization failed"; + case Result::DUPLICATION_FAILED: + return "Duplication failed"; + case Result::INVALID_MONITOR_ID: + return "Invalid monitor id"; + default: + return "Unknown error"; + } +} + +// static +rtc::scoped_refptr<DxgiDuplicatorController> +DxgiDuplicatorController::Instance() { + // The static instance won't be deleted to ensure it can be used by other + // threads even during program exiting. + static DxgiDuplicatorController* instance = new DxgiDuplicatorController(); + return rtc::scoped_refptr<DxgiDuplicatorController>(instance); +} + +// static +bool DxgiDuplicatorController::IsCurrentSessionSupported() { + DWORD current_session_id = GetCurrentSessionId(); + return current_session_id != kInvalidSessionId && current_session_id != 0; +} + +DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {} + +void DxgiDuplicatorController::AddRef() { + int refcount = (++refcount_); + RTC_DCHECK(refcount > 0); +} + +void DxgiDuplicatorController::Release() { + int refcount = (--refcount_); + RTC_DCHECK(refcount >= 0); + if (refcount == 0) { + RTC_LOG(LS_WARNING) << "Count of references reaches zero, " + "DxgiDuplicatorController will be unloaded."; + Unload(); + } +} + +bool DxgiDuplicatorController::IsSupported() { + MutexLock lock(&mutex_); + return Initialize(); +} + +bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) { + bool result = false; + { + MutexLock lock(&mutex_); + result = Initialize(); + *info = d3d_info_; + } + if (!result) { + RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo " + "retrieved may not accurate or out of date."; + } + return result; +} + +DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate( + DxgiFrame* frame) { + return DoDuplicate(frame, -1); +} + +DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor( + DxgiFrame* frame, + int monitor_id) { + RTC_DCHECK_GE(monitor_id, 0); + return DoDuplicate(frame, monitor_id); +} + +DesktopVector DxgiDuplicatorController::dpi() { + MutexLock lock(&mutex_); + if (Initialize()) { + return dpi_; + } + return DesktopVector(); +} + +int DxgiDuplicatorController::ScreenCount() { + MutexLock lock(&mutex_); + if (Initialize()) { + return ScreenCountUnlocked(); + } + return 0; +} + +bool DxgiDuplicatorController::GetDeviceNames( + std::vector<std::string>* output) { + MutexLock lock(&mutex_); + if (Initialize()) { + GetDeviceNamesUnlocked(output); + return true; + } + return false; +} + +DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate( + DxgiFrame* frame, + int monitor_id) { + RTC_DCHECK(frame); + MutexLock lock(&mutex_); + + // The dxgi components and APIs do not update the screen resolution without + // a reinitialization. So we use the GetDC() function to retrieve the screen + // resolution to decide whether dxgi components need to be reinitialized. + // If the screen resolution changed, it's very likely the next Duplicate() + // function call will fail because of a missing monitor or the frame size is + // not enough to store the output. So we reinitialize dxgi components in-place + // to avoid a capture failure. + // But there is no guarantee GetDC() function returns the same resolution as + // dxgi APIs, we still rely on dxgi components to return the output frame + // size. + // TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and + // IDXGIOutputDuplication::GetDesc() can detect the resolution change without + // reinitialization. + if (display_configuration_monitor_.IsChanged()) { + Deinitialize(); + } + + if (!Initialize()) { + if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) { + RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI " + "components cannot be initialized."; + return Result::UNSUPPORTED_SESSION; + } + + // Cannot initialize COM components now, display mode may be changing. + return Result::INITIALIZATION_FAILED; + } + + if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) { + return Result::FRAME_PREPARE_FAILED; + } + + frame->frame()->mutable_updated_region()->Clear(); + + if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) { + succeeded_duplications_++; + return Result::SUCCEEDED; + } + if (monitor_id >= ScreenCountUnlocked()) { + // It's a user error to provide a `monitor_id` larger than screen count. We + // do not need to deinitialize. + return Result::INVALID_MONITOR_ID; + } + + // If the `monitor_id` is valid, but DoDuplicateUnlocked() failed, something + // must be wrong from capturer APIs. We should Deinitialize(). + Deinitialize(); + return Result::DUPLICATION_FAILED; +} + +void DxgiDuplicatorController::Unload() { + MutexLock lock(&mutex_); + Deinitialize(); +} + +void DxgiDuplicatorController::Unregister(const Context* const context) { + MutexLock lock(&mutex_); + if (ContextExpired(context)) { + // The Context has not been setup after a recent initialization, so it + // should not been registered in duplicators. + return; + } + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Unregister(&context->contexts[i]); + } +} + +bool DxgiDuplicatorController::Initialize() { + if (!duplicators_.empty()) { + return true; + } + + if (DoInitialize()) { + return true; + } + Deinitialize(); + return false; +} + +bool DxgiDuplicatorController::DoInitialize() { + RTC_DCHECK(desktop_rect_.is_empty()); + RTC_DCHECK(duplicators_.empty()); + + d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0); + d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0); + + std::vector<D3dDevice> devices = D3dDevice::EnumDevices(); + if (devices.empty()) { + RTC_LOG(LS_WARNING) << "No D3dDevice found."; + return false; + } + + for (size_t i = 0; i < devices.size(); i++) { + D3D_FEATURE_LEVEL feature_level = + devices[i].d3d_device()->GetFeatureLevel(); + if (d3d_info_.max_feature_level == 0 || + feature_level > d3d_info_.max_feature_level) { + d3d_info_.max_feature_level = feature_level; + } + if (d3d_info_.min_feature_level == 0 || + feature_level < d3d_info_.min_feature_level) { + d3d_info_.min_feature_level = feature_level; + } + + DxgiAdapterDuplicator duplicator(devices[i]); + // There may be several video cards on the system, some of them may not + // support IDXGOutputDuplication. But they should not impact others from + // taking effect, so we should continually try other adapters. This usually + // happens when a non-official virtual adapter is installed on the system. + if (!duplicator.Initialize()) { + RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on " + "adapter " + << i; + continue; + } + RTC_DCHECK(!duplicator.desktop_rect().is_empty()); + duplicators_.push_back(std::move(duplicator)); + + desktop_rect_.UnionWith(duplicators_.back().desktop_rect()); + } + TranslateRect(); + + HDC hdc = GetDC(nullptr); + // Use old DPI value if failed. + if (hdc) { + dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); + ReleaseDC(nullptr, hdc); + } + + identity_++; + + if (duplicators_.empty()) { + RTC_LOG(LS_WARNING) + << "Cannot initialize any DxgiAdapterDuplicator instance."; + } + + return !duplicators_.empty(); +} + +void DxgiDuplicatorController::Deinitialize() { + desktop_rect_ = DesktopRect(); + duplicators_.clear(); + display_configuration_monitor_.Reset(); +} + +bool DxgiDuplicatorController::ContextExpired( + const Context* const context) const { + RTC_DCHECK(context); + return context->controller_id != identity_ || + context->contexts.size() != duplicators_.size(); +} + +void DxgiDuplicatorController::Setup(Context* context) { + if (ContextExpired(context)) { + RTC_DCHECK(context); + context->contexts.clear(); + context->contexts.resize(duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Setup(&context->contexts[i]); + } + context->controller_id = identity_; + } +} + +bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context, + int monitor_id, + SharedDesktopFrame* target) { + Setup(context); + + if (!EnsureFrameCaptured(context, target)) { + return false; + } + + bool result = false; + if (monitor_id < 0) { + // Capture entire screen. + result = DoDuplicateAll(context, target); + } else { + result = DoDuplicateOne(context, monitor_id, target); + } + + if (result) { + target->set_dpi(dpi_); + return true; + } + + return false; +} + +bool DxgiDuplicatorController::DoDuplicateAll(Context* context, + SharedDesktopFrame* target) { + for (size_t i = 0; i < duplicators_.size(); i++) { + if (!duplicators_[i].Duplicate(&context->contexts[i], target)) { + return false; + } + } + return true; +} + +bool DxgiDuplicatorController::DoDuplicateOne(Context* context, + int monitor_id, + SharedDesktopFrame* target) { + RTC_DCHECK(monitor_id >= 0); + for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size(); + i++) { + if (monitor_id >= duplicators_[i].screen_count()) { + monitor_id -= duplicators_[i].screen_count(); + } else { + if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id, + target)) { + target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left()); + return true; + } + return false; + } + } + return false; +} + +int64_t DxgiDuplicatorController::GetNumFramesCaptured() const { + int64_t min = INT64_MAX; + for (const auto& duplicator : duplicators_) { + min = std::min(min, duplicator.GetNumFramesCaptured()); + } + + return min; +} + +DesktopSize DxgiDuplicatorController::desktop_size() const { + return desktop_rect_.size(); +} + +DesktopRect DxgiDuplicatorController::ScreenRect(int id) const { + RTC_DCHECK(id >= 0); + for (size_t i = 0; i < duplicators_.size(); i++) { + if (id >= duplicators_[i].screen_count()) { + id -= duplicators_[i].screen_count(); + } else { + return duplicators_[i].ScreenRect(id); + } + } + return DesktopRect(); +} + +int DxgiDuplicatorController::ScreenCountUnlocked() const { + int result = 0; + for (auto& duplicator : duplicators_) { + result += duplicator.screen_count(); + } + return result; +} + +void DxgiDuplicatorController::GetDeviceNamesUnlocked( + std::vector<std::string>* output) const { + RTC_DCHECK(output); + for (auto& duplicator : duplicators_) { + for (int i = 0; i < duplicator.screen_count(); i++) { + output->push_back(duplicator.GetDeviceName(i)); + } + } +} + +DesktopSize DxgiDuplicatorController::SelectedDesktopSize( + int monitor_id) const { + if (monitor_id < 0) { + return desktop_size(); + } + + return ScreenRect(monitor_id).size(); +} + +bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context, + SharedDesktopFrame* target) { + // On a modern system, the FPS / monitor refresh rate is usually larger than + // or equal to 60. So 17 milliseconds is enough to capture at least one frame. + const int64_t ms_per_frame = 17; + // Skip frames to ensure a full frame refresh has occurred and the DXGI + // machinery is producing frames before this function returns. + int64_t frames_to_skip = 1; + // The total time out milliseconds for this function. If we cannot get enough + // frames during this time interval, this function returns false, and cause + // the DXGI components to be reinitialized. This usually should not happen + // unless the system is switching display mode when this function is being + // called. 500 milliseconds should be enough for ~30 frames. + const int64_t timeout_ms = 500; + + if (GetNumFramesCaptured() == 0 && !IsConsoleSession()) { + // When capturing a console session, waiting for a single frame is + // sufficient to ensure that DXGI output duplication is working. When the + // session is not attached to the console, it has been observed that DXGI + // may produce up to 4 frames (typically 1-2 though) before stopping. When + // this condition occurs, no errors are returned from the output duplication + // API, it simply appears that nothing is changing on the screen. Thus for + // detached sessions, we need to capture a few extra frames before we can be + // confident that output duplication was initialized properly. + frames_to_skip = 5; + } + + if (GetNumFramesCaptured() >= frames_to_skip) { + return true; + } + + std::unique_ptr<SharedDesktopFrame> fallback_frame; + SharedDesktopFrame* shared_frame = nullptr; + if (target->size().width() >= desktop_size().width() && + target->size().height() >= desktop_size().height()) { + // `target` is large enough to cover entire screen, we do not need to use + // `fallback_frame`. + shared_frame = target; + } else { + fallback_frame = SharedDesktopFrame::Wrap( + std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(desktop_size()))); + shared_frame = fallback_frame.get(); + } + + const int64_t start_ms = rtc::TimeMillis(); + while (GetNumFramesCaptured() < frames_to_skip) { + if (!DoDuplicateAll(context, shared_frame)) { + return false; + } + + // Calling DoDuplicateAll() may change the number of frames captured. + if (GetNumFramesCaptured() >= frames_to_skip) { + break; + } + + if (rtc::TimeMillis() - start_ms > timeout_ms) { + RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip + << " frames " + "within " + << timeout_ms << " milliseconds."; + return false; + } + + // Sleep `ms_per_frame` before attempting to capture the next frame to + // ensure the video adapter has time to update the screen. + webrtc::SleepMs(ms_per_frame); + } + return true; +} + +void DxgiDuplicatorController::TranslateRect() { + const DesktopVector position = + DesktopVector().subtract(desktop_rect_.top_left()); + desktop_rect_.Translate(position); + for (auto& duplicator : duplicators_) { + duplicator.TranslateRect(position); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h new file mode 100644 index 0000000000..88c2939187 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ + +#include <d3dcommon.h> + +#include <atomic> +#include <string> +#include <vector> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/win/d3d_device.h" +#include "modules/desktop_capture/win/display_configuration_monitor.h" +#include "modules/desktop_capture/win/dxgi_adapter_duplicator.h" +#include "modules/desktop_capture/win/dxgi_context.h" +#include "modules/desktop_capture/win/dxgi_frame.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// A controller for all the objects we need to call Windows DirectX capture APIs +// It's a singleton because only one IDXGIOutputDuplication instance per monitor +// is allowed per application. +// +// Consumers should create a DxgiDuplicatorController::Context and keep it +// throughout their lifetime, and pass it when calling Duplicate(). Consumers +// can also call IsSupported() to determine whether the system supports DXGI +// duplicator or not. If a previous IsSupported() function call returns true, +// but a later Duplicate() returns false, this usually means the display mode is +// changing. Consumers should retry after a while. (Typically 50 milliseconds, +// but according to hardware performance, this time may vary.) +// The underyling DxgiOutputDuplicators may take an additional reference on the +// frame passed in to the Duplicate methods so that they can guarantee delivery +// of new frames when requested; since if there have been no updates to the +// surface, they may be unable to capture a frame. +class RTC_EXPORT DxgiDuplicatorController { + public: + using Context = DxgiFrameContext; + + // A collection of D3d information we are interested on, which may impact + // capturer performance or reliability. + struct D3dInfo { + // Each video adapter has its own D3D_FEATURE_LEVEL, so this structure + // contains the minimum and maximium D3D_FEATURE_LEVELs current system + // supports. + // Both fields can be 0, which is the default value to indicate no valid + // D3D_FEATURE_LEVEL has been retrieved from underlying OS APIs. + D3D_FEATURE_LEVEL min_feature_level; + D3D_FEATURE_LEVEL max_feature_level; + + // TODO(zijiehe): Add more fields, such as manufacturer name, mode, driver + // version. + }; + + enum class Result { + SUCCEEDED, + UNSUPPORTED_SESSION, + FRAME_PREPARE_FAILED, + INITIALIZATION_FAILED, + DUPLICATION_FAILED, + INVALID_MONITOR_ID, + }; + + // Converts `result` into user-friendly string representation. The return + // value should not be used to identify error types. + static std::string ResultName(Result result); + + // Returns the singleton instance of DxgiDuplicatorController. + static rtc::scoped_refptr<DxgiDuplicatorController> Instance(); + + // See ScreenCapturerWinDirectx::IsCurrentSessionSupported(). + static bool IsCurrentSessionSupported(); + + // All the following public functions implicitly call Initialize() function. + + // Detects whether the system supports DXGI based capturer. + bool IsSupported(); + + // Returns a copy of D3dInfo composed by last Initialize() function call. This + // function always copies the latest information into `info`. But once the + // function returns false, the information in `info` may not accurate. + bool RetrieveD3dInfo(D3dInfo* info); + + // Captures current screen and writes into `frame`. May retain a reference to + // `frame`'s underlying |SharedDesktopFrame|. + // TODO(zijiehe): Windows cannot guarantee the frames returned by each + // IDXGIOutputDuplication are synchronized. But we are using a totally + // different threading model than the way Windows suggested, it's hard to + // synchronize them manually. We should find a way to do it. + Result Duplicate(DxgiFrame* frame); + + // Captures one monitor and writes into target. `monitor_id` should >= 0. If + // `monitor_id` is greater than the total screen count of all the Duplicators, + // this function returns false. May retain a reference to `frame`'s underlying + // |SharedDesktopFrame|. + Result DuplicateMonitor(DxgiFrame* frame, int monitor_id); + + // Returns dpi of current system. Returns an empty DesktopVector if system + // does not support DXGI based capturer. + DesktopVector dpi(); + + // Returns the count of screens on the system. These screens can be retrieved + // by an integer in the range of [0, ScreenCount()). If system does not + // support DXGI based capturer, this function returns 0. + int ScreenCount(); + + // Returns the device names of all screens on the system in utf8 encoding. + // These screens can be retrieved by an integer in the range of + // [0, output->size()). If system does not support DXGI based capturer, this + // function returns false. + bool GetDeviceNames(std::vector<std::string>* output); + + private: + // DxgiFrameContext calls private Unregister(Context*) function in Reset(). + friend void DxgiFrameContext::Reset(); + + // scoped_refptr<DxgiDuplicatorController> accesses private AddRef() and + // Release() functions. + friend class rtc::scoped_refptr<DxgiDuplicatorController>; + + // A private constructor to ensure consumers to use + // DxgiDuplicatorController::Instance(). + DxgiDuplicatorController(); + + // Not implemented: The singleton DxgiDuplicatorController instance should not + // be deleted. + ~DxgiDuplicatorController(); + + // RefCountedInterface implementations. + void AddRef(); + void Release(); + + // Does the real duplication work. Setting `monitor_id` < 0 to capture entire + // screen. This function calls Initialize(). And if the duplication failed, + // this function calls Deinitialize() to ensure the Dxgi components can be + // reinitialized next time. + Result DoDuplicate(DxgiFrame* frame, int monitor_id); + + // Unload all the DXGI components and releases the resources. This function + // wraps Deinitialize() with `mutex_`. + void Unload(); + + // Unregisters Context from this instance and all DxgiAdapterDuplicator(s) + // it owns. + void Unregister(const Context* const context); + + // All functions below should be called in `mutex_` locked scope and should be + // after a successful Initialize(). + + // If current instance has not been initialized, executes DoInitialize() + // function, and returns initialize result. Otherwise directly returns true. + // This function may calls Deinitialize() if initialization failed. + bool Initialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Does the real initialization work, this function should only be called in + // Initialize(). + bool DoInitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Clears all COM components referred by this instance. So next Duplicate() + // call will eventually initialize this instance again. + void Deinitialize() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // A helper function to check whether a Context has been expired. + bool ContextExpired(const Context* const context) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Updates Context if needed. + void Setup(Context* context) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool DoDuplicateUnlocked(Context* context, + int monitor_id, + SharedDesktopFrame* target) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Captures all monitors. + bool DoDuplicateAll(Context* context, SharedDesktopFrame* target) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Captures one monitor. + bool DoDuplicateOne(Context* context, + int monitor_id, + SharedDesktopFrame* target) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // The minimum GetNumFramesCaptured() returned by `duplicators_`. + int64_t GetNumFramesCaptured() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns a DesktopSize to cover entire `desktop_rect_`. + DesktopSize desktop_size() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns the size of one screen. `id` should be >= 0. If system does not + // support DXGI based capturer, or `id` is greater than the total screen count + // of all the Duplicators, this function returns an empty DesktopRect. + DesktopRect ScreenRect(int id) const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + int ScreenCountUnlocked() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void GetDeviceNamesUnlocked(std::vector<std::string>* output) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns the desktop size of the selected screen `monitor_id`. Setting + // `monitor_id` < 0 to return the entire screen size. + DesktopSize SelectedDesktopSize(int monitor_id) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Retries DoDuplicateAll() for several times until GetNumFramesCaptured() is + // large enough. Returns false if DoDuplicateAll() returns false, or + // GetNumFramesCaptured() has never reached the requirement. + // According to http://crbug.com/682112, dxgi capturer returns a black frame + // during first several capture attempts. + bool EnsureFrameCaptured(Context* context, SharedDesktopFrame* target) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Moves `desktop_rect_` and all underlying `duplicators_`, putting top left + // corner of the desktop at (0, 0). This is necessary because DXGI_OUTPUT_DESC + // may return negative coordinates. Called from DoInitialize() after all + // DxgiAdapterDuplicator and DxgiOutputDuplicator instances are initialized. + void TranslateRect() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // The count of references which are now "living". + std::atomic_int refcount_; + + // This lock must be locked whenever accessing any of the following objects. + Mutex mutex_; + + // A self-incremented integer to compare with the one in Context. It ensures + // a Context instance is always initialized after DxgiDuplicatorController. + int identity_ RTC_GUARDED_BY(mutex_) = 0; + DesktopRect desktop_rect_ RTC_GUARDED_BY(mutex_); + DesktopVector dpi_ RTC_GUARDED_BY(mutex_); + std::vector<DxgiAdapterDuplicator> duplicators_ RTC_GUARDED_BY(mutex_); + D3dInfo d3d_info_ RTC_GUARDED_BY(mutex_); + DisplayConfigurationMonitor display_configuration_monitor_ + RTC_GUARDED_BY(mutex_); + // A number to indicate how many succeeded duplications have been performed. + uint32_t succeeded_duplications_ RTC_GUARDED_BY(mutex_) = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc new file mode 100644 index 0000000000..13d5b4b62e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.cc @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_frame.h" + +#include <string.h> + +#include <utility> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/win/dxgi_duplicator_controller.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +DxgiFrame::DxgiFrame(SharedMemoryFactory* factory) : factory_(factory) {} + +DxgiFrame::~DxgiFrame() = default; + +bool DxgiFrame::Prepare(DesktopSize size, DesktopCapturer::SourceId source_id) { + if (source_id != source_id_) { + // Once the source has been changed, the entire source should be copied. + source_id_ = source_id; + context_.Reset(); + } + + if (resolution_tracker_.SetResolution(size)) { + // Once the output size changed, recreate the SharedDesktopFrame. + frame_.reset(); + } + + if (!frame_) { + std::unique_ptr<DesktopFrame> frame; + if (factory_) { + frame = SharedMemoryDesktopFrame::Create(size, factory_); + + if (!frame) { + RTC_LOG(LS_WARNING) << "DxgiFrame cannot create a new DesktopFrame."; + return false; + } + + // DirectX capturer won't paint each pixel in the frame due to its one + // capturer per monitor design. So once the new frame is created, we + // should clear it to avoid the legacy image to be remained on it. See + // http://crbug.com/708766. + RTC_DCHECK_EQ(frame->stride(), + frame->size().width() * DesktopFrame::kBytesPerPixel); + memset(frame->data(), 0, frame->stride() * frame->size().height()); + } else { + frame.reset(new BasicDesktopFrame(size)); + } + + frame_ = SharedDesktopFrame::Wrap(std::move(frame)); + } + + return !!frame_; +} + +SharedDesktopFrame* DxgiFrame::frame() const { + RTC_DCHECK(frame_); + return frame_.get(); +} + +DxgiFrame::Context* DxgiFrame::context() { + RTC_DCHECK(frame_); + return &context_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h new file mode 100644 index 0000000000..6a9ce868a7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_frame.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_ + +#include <memory> +#include <vector> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/resolution_tracker.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/shared_memory.h" +#include "modules/desktop_capture/win/dxgi_context.h" + +namespace webrtc { + +class DxgiDuplicatorController; + +// A pair of a SharedDesktopFrame and a DxgiDuplicatorController::Context for +// the client of DxgiDuplicatorController. +class DxgiFrame final { + public: + using Context = DxgiFrameContext; + + // DxgiFrame does not take ownership of `factory`, consumers should ensure it + // outlives this instance. nullptr is acceptable. + explicit DxgiFrame(SharedMemoryFactory* factory); + ~DxgiFrame(); + + // Should not be called if Prepare() is not executed or returns false. + SharedDesktopFrame* frame() const; + + private: + // Allows DxgiDuplicatorController to access Prepare() and context() function + // as well as Context class. + friend class DxgiDuplicatorController; + + // Prepares current instance with desktop size and source id. + bool Prepare(DesktopSize size, DesktopCapturer::SourceId source_id); + + // Should not be called if Prepare() is not executed or returns false. + Context* context(); + + SharedMemoryFactory* const factory_; + ResolutionTracker resolution_tracker_; + DesktopCapturer::SourceId source_id_ = kFullDesktopScreenId; + std::unique_ptr<SharedDesktopFrame> frame_; + Context context_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_FRAME_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc new file mode 100644 index 0000000000..caad6f43d1 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_output_duplicator.h" + +#include <dxgi.h> +#include <dxgiformat.h> +#include <string.h> +#include <unknwn.h> +#include <windows.h> + +#include <algorithm> + +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "modules/desktop_capture/win/dxgi_texture_mapping.h" +#include "modules/desktop_capture/win/dxgi_texture_staging.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/win32.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +namespace { + +// Timeout for AcquireNextFrame() call. +// DxgiDuplicatorController leverages external components to do the capture +// scheduling. So here DxgiOutputDuplicator does not need to actively wait for a +// new frame. +const int kAcquireTimeoutMs = 0; + +DesktopRect RECTToDesktopRect(const RECT& rect) { + return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); +} + +Rotation DxgiRotationToRotation(DXGI_MODE_ROTATION rotation) { + switch (rotation) { + case DXGI_MODE_ROTATION_IDENTITY: + case DXGI_MODE_ROTATION_UNSPECIFIED: + return Rotation::CLOCK_WISE_0; + case DXGI_MODE_ROTATION_ROTATE90: + return Rotation::CLOCK_WISE_90; + case DXGI_MODE_ROTATION_ROTATE180: + return Rotation::CLOCK_WISE_180; + case DXGI_MODE_ROTATION_ROTATE270: + return Rotation::CLOCK_WISE_270; + } + + RTC_DCHECK_NOTREACHED(); + return Rotation::CLOCK_WISE_0; +} + +} // namespace + +DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device, + const ComPtr<IDXGIOutput1>& output, + const DXGI_OUTPUT_DESC& desc) + : device_(device), + output_(output), + device_name_(rtc::ToUtf8(desc.DeviceName)), + desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) { + RTC_DCHECK(output_); + RTC_DCHECK(!desktop_rect_.is_empty()); + RTC_DCHECK_GT(desktop_rect_.width(), 0); + RTC_DCHECK_GT(desktop_rect_.height(), 0); +} + +DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) = + default; + +DxgiOutputDuplicator::~DxgiOutputDuplicator() { + if (duplication_) { + duplication_->ReleaseFrame(); + } + texture_.reset(); +} + +bool DxgiOutputDuplicator::Initialize() { + if (DuplicateOutput()) { + if (desc_.DesktopImageInSystemMemory) { + texture_.reset(new DxgiTextureMapping(duplication_.Get())); + } else { + texture_.reset(new DxgiTextureStaging(device_)); + } + return true; + } else { + duplication_.Reset(); + return false; + } +} + +bool DxgiOutputDuplicator::DuplicateOutput() { + RTC_DCHECK(!duplication_); + _com_error error = + output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()), + duplication_.GetAddressOf()); + if (error.Error() != S_OK || !duplication_) { + RTC_LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + memset(&desc_, 0, sizeof(desc_)); + duplication_->GetDesc(&desc_); + if (desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { + RTC_LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " + << "format, which is required by downstream components, " + << "format is " << desc_.ModeDesc.Format; + return false; + } + + if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() || + static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) { + RTC_LOG(LS_ERROR) + << "IDXGIDuplicateOutput does not return a same size as its " + << "IDXGIOutput1, size returned by IDXGIDuplicateOutput is " + << desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height + << ", size returned by IDXGIOutput1 is " << desktop_rect_.width() + << " x " << desktop_rect_.height(); + return false; + } + + rotation_ = DxgiRotationToRotation(desc_.Rotation); + unrotated_size_ = RotateSize(desktop_size(), ReverseRotation(rotation_)); + + return true; +} + +bool DxgiOutputDuplicator::ReleaseFrame() { + RTC_DCHECK(duplication_); + _com_error error = duplication_->ReleaseFrame(); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + return true; +} + +bool DxgiOutputDuplicator::Duplicate(Context* context, + DesktopVector offset, + SharedDesktopFrame* target) { + RTC_DCHECK(duplication_); + RTC_DCHECK(texture_); + RTC_DCHECK(target); + if (!DesktopRect::MakeSize(target->size()) + .ContainsRect(GetTranslatedDesktopRect(offset))) { + // target size is not large enough to cover current output region. + return false; + } + + DXGI_OUTDUPL_FRAME_INFO frame_info; + memset(&frame_info, 0, sizeof(frame_info)); + ComPtr<IDXGIResource> resource; + _com_error error = duplication_->AcquireNextFrame( + kAcquireTimeoutMs, &frame_info, resource.GetAddressOf()); + if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) { + RTC_LOG(LS_ERROR) << "Failed to capture frame: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + // We need to merge updated region with the one from context, but only spread + // updated region from current frame. So keeps a copy of updated region from + // context here. The `updated_region` always starts from (0, 0). + DesktopRegion updated_region; + updated_region.Swap(&context->updated_region); + if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0 && resource) { + DetectUpdatedRegion(frame_info, &context->updated_region); + SpreadContextChange(context); + if (!texture_->CopyFrom(frame_info, resource.Get())) { + return false; + } + updated_region.AddRegion(context->updated_region); + // TODO(zijiehe): Figure out why clearing context->updated_region() here + // triggers screen flickering? + + const DesktopFrame& source = texture_->AsDesktopFrame(); + if (rotation_ != Rotation::CLOCK_WISE_0) { + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + // The `updated_region` returned by Windows is rotated, but the `source` + // frame is not. So we need to rotate it reversely. + const DesktopRect source_rect = + RotateRect(it.rect(), desktop_size(), ReverseRotation(rotation_)); + RotateDesktopFrame(source, source_rect, rotation_, offset, target); + } + } else { + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + // The DesktopRect in `target`, starts from offset. + DesktopRect dest_rect = it.rect(); + dest_rect.Translate(offset); + target->CopyPixelsFrom(source, it.rect().top_left(), dest_rect); + } + } + last_frame_ = target->Share(); + last_frame_offset_ = offset; + updated_region.Translate(offset.x(), offset.y()); + target->mutable_updated_region()->AddRegion(updated_region); + num_frames_captured_++; + return texture_->Release() && ReleaseFrame(); + } + + if (last_frame_) { + // No change since last frame or AcquireNextFrame() timed out, we will + // export last frame to the target. + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + // The DesktopRect in `source`, starts from last_frame_offset_. + DesktopRect source_rect = it.rect(); + // The DesktopRect in `target`, starts from offset. + DesktopRect target_rect = source_rect; + source_rect.Translate(last_frame_offset_); + target_rect.Translate(offset); + target->CopyPixelsFrom(*last_frame_, source_rect.top_left(), target_rect); + } + updated_region.Translate(offset.x(), offset.y()); + target->mutable_updated_region()->AddRegion(updated_region); + } else { + // If we were at the very first frame, and capturing failed, the + // context->updated_region should be kept unchanged for next attempt. + context->updated_region.Swap(&updated_region); + } + // If AcquireNextFrame() failed with timeout error, we do not need to release + // the frame. + return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame(); +} + +DesktopRect DxgiOutputDuplicator::GetTranslatedDesktopRect( + DesktopVector offset) const { + DesktopRect result(DesktopRect::MakeSize(desktop_size())); + result.Translate(offset); + return result; +} + +DesktopRect DxgiOutputDuplicator::GetUntranslatedDesktopRect() const { + return DesktopRect::MakeSize(desktop_size()); +} + +void DxgiOutputDuplicator::DetectUpdatedRegion( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region) { + if (DoDetectUpdatedRegion(frame_info, updated_region)) { + // Make sure even a region returned by Windows API is out of the scope of + // desktop_rect_, we still won't export it to the target DesktopFrame. + updated_region->IntersectWith(GetUntranslatedDesktopRect()); + } else { + updated_region->SetRect(GetUntranslatedDesktopRect()); + } +} + +bool DxgiOutputDuplicator::DoDetectUpdatedRegion( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region) { + RTC_DCHECK(updated_region); + updated_region->Clear(); + if (frame_info.TotalMetadataBufferSize == 0) { + // This should not happen, since frame_info.AccumulatedFrames > 0. + RTC_LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " + << "but TotalMetadataBufferSize == 0"; + return false; + } + + if (metadata_.size() < frame_info.TotalMetadataBufferSize) { + metadata_.clear(); // Avoid data copy + metadata_.resize(frame_info.TotalMetadataBufferSize); + } + + UINT buff_size = 0; + DXGI_OUTDUPL_MOVE_RECT* move_rects = + reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata_.data()); + size_t move_rects_count = 0; + _com_error error = duplication_->GetFrameMoveRects( + static_cast<UINT>(metadata_.size()), move_rects, &buff_size); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "Failed to get move rectangles: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); + + RECT* dirty_rects = reinterpret_cast<RECT*>(metadata_.data() + buff_size); + size_t dirty_rects_count = 0; + error = duplication_->GetFrameDirtyRects( + static_cast<UINT>(metadata_.size()) - buff_size, dirty_rects, &buff_size); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "Failed to get dirty rectangles: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + dirty_rects_count = buff_size / sizeof(RECT); + + while (move_rects_count > 0) { + // DirectX capturer API may randomly return unmoved move_rects, which should + // be skipped to avoid unnecessary wasting of differing and encoding + // resources. + // By using testing application it2me_standalone_host_main, this check + // reduces average capture time by 0.375% (4.07 -> 4.055), and average + // encode time by 0.313% (8.042 -> 8.016) without other impacts. + if (move_rects->SourcePoint.x != move_rects->DestinationRect.left || + move_rects->SourcePoint.y != move_rects->DestinationRect.top) { + updated_region->AddRect( + RotateRect(DesktopRect::MakeXYWH(move_rects->SourcePoint.x, + move_rects->SourcePoint.y, + move_rects->DestinationRect.right - + move_rects->DestinationRect.left, + move_rects->DestinationRect.bottom - + move_rects->DestinationRect.top), + unrotated_size_, rotation_)); + updated_region->AddRect( + RotateRect(DesktopRect::MakeLTRB(move_rects->DestinationRect.left, + move_rects->DestinationRect.top, + move_rects->DestinationRect.right, + move_rects->DestinationRect.bottom), + unrotated_size_, rotation_)); + } else { + RTC_LOG(LS_INFO) << "Unmoved move_rect detected, [" + << move_rects->DestinationRect.left << ", " + << move_rects->DestinationRect.top << "] - [" + << move_rects->DestinationRect.right << ", " + << move_rects->DestinationRect.bottom << "]."; + } + move_rects++; + move_rects_count--; + } + + while (dirty_rects_count > 0) { + updated_region->AddRect(RotateRect( + DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top, + dirty_rects->right, dirty_rects->bottom), + unrotated_size_, rotation_)); + dirty_rects++; + dirty_rects_count--; + } + + return true; +} + +void DxgiOutputDuplicator::Setup(Context* context) { + RTC_DCHECK(context->updated_region.is_empty()); + // Always copy entire monitor during the first Duplicate() function call. + context->updated_region.AddRect(GetUntranslatedDesktopRect()); + RTC_DCHECK(std::find(contexts_.begin(), contexts_.end(), context) == + contexts_.end()); + contexts_.push_back(context); +} + +void DxgiOutputDuplicator::Unregister(const Context* const context) { + auto it = std::find(contexts_.begin(), contexts_.end(), context); + RTC_DCHECK(it != contexts_.end()); + contexts_.erase(it); +} + +void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) { + for (Context* dest : contexts_) { + RTC_DCHECK(dest); + if (dest != source) { + dest->updated_region.AddRegion(source->updated_region); + } + } +} + +DesktopSize DxgiOutputDuplicator::desktop_size() const { + return desktop_rect_.size(); +} + +int64_t DxgiOutputDuplicator::num_frames_captured() const { +#if !defined(NDEBUG) + RTC_DCHECK_EQ(!!last_frame_, num_frames_captured_ > 0); +#endif + return num_frames_captured_; +} + +void DxgiOutputDuplicator::TranslateRect(const DesktopVector& position) { + desktop_rect_.Translate(position); + RTC_DCHECK_GE(desktop_rect_.left(), 0); + RTC_DCHECK_GE(desktop_rect_.top(), 0); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h new file mode 100644 index 0000000000..df15fe566e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_output_duplicator.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ + +#include <comdef.h> +#include <dxgi.h> +#include <dxgi1_2.h> +#include <wrl/client.h> + +#include <memory> +#include <string> +#include <vector> + +#include "modules/desktop_capture/desktop_frame_rotation.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/win/d3d_device.h" +#include "modules/desktop_capture/win/dxgi_context.h" +#include "modules/desktop_capture/win/dxgi_texture.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +// Duplicates the content on one IDXGIOutput, i.e. one monitor attached to one +// video card. None of functions in this class is thread-safe. +class DxgiOutputDuplicator { + public: + using Context = DxgiOutputContext; + + // Creates an instance of DxgiOutputDuplicator from a D3dDevice and one of its + // IDXGIOutput1. Caller must maintain the lifetime of device, to make sure it + // outlives this instance. Only DxgiAdapterDuplicator can create an instance. + DxgiOutputDuplicator(const D3dDevice& device, + const Microsoft::WRL::ComPtr<IDXGIOutput1>& output, + const DXGI_OUTPUT_DESC& desc); + + // To allow this class to work with vector. + DxgiOutputDuplicator(DxgiOutputDuplicator&& other); + + // Destructs this instance. We need to make sure texture_ has been released + // before duplication_. + ~DxgiOutputDuplicator(); + + // Initializes duplication_ object. + bool Initialize(); + + // Copies the content of current IDXGIOutput to the `target`. To improve the + // performance, this function copies only regions merged from + // `context`->updated_region and DetectUpdatedRegion(). The `offset` decides + // the offset in the `target` where the content should be copied to. i.e. this + // function copies the content to the rectangle of (offset.x(), offset.y()) to + // (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()). + // Returns false in case of a failure. + // May retain a reference to `target` so that a "captured" frame can be + // returned in the event that a new frame is not ready to be captured yet. + // (Or in other words, if the call to IDXGIOutputDuplication::AcquireNextFrame + // indicates that there is not yet a new frame, this is usually because no + // updates have occurred to the frame). + bool Duplicate(Context* context, + DesktopVector offset, + SharedDesktopFrame* target); + + // Returns the desktop rect covered by this DxgiOutputDuplicator. + DesktopRect desktop_rect() const { return desktop_rect_; } + + // Returns the device name from DXGI_OUTPUT_DESC in utf8 encoding. + const std::string& device_name() const { return device_name_; } + + void Setup(Context* context); + + void Unregister(const Context* const context); + + // How many frames have been captured by this DxigOutputDuplicator. + int64_t num_frames_captured() const; + + // Moves `desktop_rect_`. See DxgiDuplicatorController::TranslateRect(). + void TranslateRect(const DesktopVector& position); + + private: + // Calls DoDetectUpdatedRegion(). If it fails, this function sets the + // `updated_region` as entire UntranslatedDesktopRect(). + void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region); + + // Returns untranslated updated region, which are directly returned by Windows + // APIs. Returns false in case of a failure. + bool DoDetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region); + + bool ReleaseFrame(); + + // Initializes duplication_ instance. Expects duplication_ is in empty status. + // Returns false if system does not support IDXGIOutputDuplication. + bool DuplicateOutput(); + + // Returns a DesktopRect with the same size of desktop_size(), but translated + // by offset. + DesktopRect GetTranslatedDesktopRect(DesktopVector offset) const; + + // Returns a DesktopRect with the same size of desktop_size(), but starts from + // (0, 0). + DesktopRect GetUntranslatedDesktopRect() const; + + // Spreads changes from `context` to other registered Context(s) in + // contexts_. + void SpreadContextChange(const Context* const context); + + // Returns the size of desktop rectangle current instance representing. + DesktopSize desktop_size() const; + + const D3dDevice device_; + const Microsoft::WRL::ComPtr<IDXGIOutput1> output_; + const std::string device_name_; + DesktopRect desktop_rect_; + Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_; + DXGI_OUTDUPL_DESC desc_; + std::vector<uint8_t> metadata_; + std::unique_ptr<DxgiTexture> texture_; + Rotation rotation_; + DesktopSize unrotated_size_; + + // After each AcquireNextFrame() function call, updated_region_(s) of all + // active Context(s) need to be updated. Since they have missed the + // change this time. And during next Duplicate() function call, their + // updated_region_ will be merged and copied. + std::vector<Context*> contexts_; + + // The last full frame of this output and its offset. If on AcquireNextFrame() + // failed because of timeout, i.e. no update, we can copy content from + // `last_frame_`. + std::unique_ptr<SharedDesktopFrame> last_frame_; + DesktopVector last_frame_offset_; + + int64_t num_frames_captured_ = 0; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc new file mode 100644 index 0000000000..b8f5b81f90 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.cc @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_texture.h" + +#include <comdef.h> +#include <d3d11.h> +#include <wrl/client.h> + +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { + +namespace { + +class DxgiDesktopFrame : public DesktopFrame { + public: + explicit DxgiDesktopFrame(const DxgiTexture& texture) + : DesktopFrame(texture.desktop_size(), + texture.pitch(), + texture.bits(), + nullptr) {} + + ~DxgiDesktopFrame() override = default; +}; + +} // namespace + +DxgiTexture::DxgiTexture() = default; +DxgiTexture::~DxgiTexture() = default; + +bool DxgiTexture::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource) { + RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0); + RTC_DCHECK(resource); + ComPtr<ID3D11Texture2D> texture; + _com_error error = resource->QueryInterface( + __uuidof(ID3D11Texture2D), + reinterpret_cast<void**>(texture.GetAddressOf())); + if (error.Error() != S_OK || !texture) { + RTC_LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + D3D11_TEXTURE2D_DESC desc = {0}; + texture->GetDesc(&desc); + desktop_size_.set(desc.Width, desc.Height); + + return CopyFromTexture(frame_info, texture.Get()); +} + +const DesktopFrame& DxgiTexture::AsDesktopFrame() { + if (!frame_) { + frame_.reset(new DxgiDesktopFrame(*this)); + } + return *frame_; +} + +bool DxgiTexture::Release() { + frame_.reset(); + return DoRelease(); +} + +DXGI_MAPPED_RECT* DxgiTexture::rect() { + return &rect_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h new file mode 100644 index 0000000000..a663b95a04 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ + +#include <d3d11.h> +#include <dxgi1_2.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class DesktopRegion; + +// A texture copied or mapped from a DXGI_OUTDUPL_FRAME_INFO and IDXGIResource. +class DxgiTexture { + public: + // Creates a DxgiTexture instance, which represents the `desktop_size` area of + // entire screen -- usually a monitor on the system. + DxgiTexture(); + + virtual ~DxgiTexture(); + + // Copies selected regions of a frame represented by frame_info and resource. + // Returns false if anything wrong. + bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource); + + const DesktopSize& desktop_size() const { return desktop_size_; } + + uint8_t* bits() const { return static_cast<uint8_t*>(rect_.pBits); } + + int pitch() const { return static_cast<int>(rect_.Pitch); } + + // Releases the resource currently holds by this instance. Returns false if + // anything wrong, and this instance should be deprecated in this state. bits, + // pitch and AsDesktopFrame are only valid after a success CopyFrom() call, + // but before Release() call. + bool Release(); + + // Returns a DesktopFrame snapshot of a DxgiTexture instance. This + // DesktopFrame is used to copy a DxgiTexture content to another DesktopFrame + // only. And it should not outlive its DxgiTexture instance. + const DesktopFrame& AsDesktopFrame(); + + protected: + DXGI_MAPPED_RECT* rect(); + + virtual bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + ID3D11Texture2D* texture) = 0; + + virtual bool DoRelease() = 0; + + private: + DXGI_MAPPED_RECT rect_ = {0}; + DesktopSize desktop_size_; + std::unique_ptr<DesktopFrame> frame_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc new file mode 100644 index 0000000000..7ecf1adc61 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_texture_mapping.h" + +#include <comdef.h> +#include <dxgi.h> +#include <dxgi1_2.h> + +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +DxgiTextureMapping::DxgiTextureMapping(IDXGIOutputDuplication* duplication) + : duplication_(duplication) { + RTC_DCHECK(duplication_); +} + +DxgiTextureMapping::~DxgiTextureMapping() = default; + +bool DxgiTextureMapping::CopyFromTexture( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + ID3D11Texture2D* texture) { + RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0); + RTC_DCHECK(texture); + *rect() = {0}; + _com_error error = duplication_->MapDesktopSurface(rect()); + if (error.Error() != S_OK) { + *rect() = {0}; + RTC_LOG(LS_ERROR) + << "Failed to map the IDXGIOutputDuplication to a bitmap: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + return true; +} + +bool DxgiTextureMapping::DoRelease() { + _com_error error = duplication_->UnMapDesktopSurface(); + if (error.Error() != S_OK) { + RTC_LOG(LS_ERROR) << "Failed to unmap the IDXGIOutputDuplication: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h new file mode 100644 index 0000000000..71f00b99ab --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_mapping.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ + +#include <d3d11.h> +#include <dxgi1_2.h> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/win/dxgi_texture.h" + +namespace webrtc { + +// A DxgiTexture which directly maps bitmap from IDXGIResource. This class is +// used when DXGI_OUTDUPL_DESC.DesktopImageInSystemMemory is true. (This usually +// means the video card shares main memory with CPU, instead of having its own +// individual memory.) +class DxgiTextureMapping : public DxgiTexture { + public: + // Creates a DxgiTextureMapping instance. Caller must maintain the lifetime + // of input `duplication` to make sure it outlives this instance. + explicit DxgiTextureMapping(IDXGIOutputDuplication* duplication); + + ~DxgiTextureMapping() override; + + protected: + bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + ID3D11Texture2D* texture) override; + + bool DoRelease() override; + + private: + IDXGIOutputDuplication* const duplication_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc new file mode 100644 index 0000000000..17e8518a7d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/dxgi_texture_staging.h" + +#include <comdef.h> +#include <dxgi.h> +#include <dxgi1_2.h> +#include <unknwn.h> + +#include "modules/desktop_capture/win/desktop_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/metrics.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { + +DxgiTextureStaging::DxgiTextureStaging(const D3dDevice& device) + : device_(device) {} + +DxgiTextureStaging::~DxgiTextureStaging() = default; + +bool DxgiTextureStaging::InitializeStage(ID3D11Texture2D* texture) { + RTC_DCHECK(texture); + D3D11_TEXTURE2D_DESC desc = {0}; + texture->GetDesc(&desc); + + desc.ArraySize = 1; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MipLevels = 1; + desc.MiscFlags = 0; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_STAGING; + if (stage_) { + AssertStageAndSurfaceAreSameObject(); + D3D11_TEXTURE2D_DESC current_desc; + stage_->GetDesc(¤t_desc); + const bool recreate_needed = + (memcmp(&desc, ¤t_desc, sizeof(D3D11_TEXTURE2D_DESC)) != 0); + RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.StagingTextureRecreate", + recreate_needed); + if (!recreate_needed) { + return true; + } + + // The descriptions are not consistent, we need to create a new + // ID3D11Texture2D instance. + stage_.Reset(); + surface_.Reset(); + } else { + RTC_DCHECK(!surface_); + } + + _com_error error = device_.d3d_device()->CreateTexture2D( + &desc, nullptr, stage_.GetAddressOf()); + if (error.Error() != S_OK || !stage_) { + RTC_LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + error = stage_.As(&surface_); + if (error.Error() != S_OK || !surface_) { + RTC_LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + return true; +} + +void DxgiTextureStaging::AssertStageAndSurfaceAreSameObject() { + ComPtr<IUnknown> left; + ComPtr<IUnknown> right; + bool left_result = SUCCEEDED(stage_.As(&left)); + bool right_result = SUCCEEDED(surface_.As(&right)); + RTC_DCHECK(left_result); + RTC_DCHECK(right_result); + RTC_DCHECK(left.Get() == right.Get()); +} + +bool DxgiTextureStaging::CopyFromTexture( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + ID3D11Texture2D* texture) { + RTC_DCHECK_GT(frame_info.AccumulatedFrames, 0); + RTC_DCHECK(texture); + + // AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to + // copy it to a CPU accessible staging ID3D11Texture2D. + if (!InitializeStage(texture)) { + return false; + } + + device_.context()->CopyResource(static_cast<ID3D11Resource*>(stage_.Get()), + static_cast<ID3D11Resource*>(texture)); + + *rect() = {0}; + _com_error error = surface_->Map(rect(), DXGI_MAP_READ); + if (error.Error() != S_OK) { + *rect() = {0}; + RTC_LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap: " + << desktop_capture::utils::ComErrorToString(error); + return false; + } + + return true; +} + +bool DxgiTextureStaging::DoRelease() { + _com_error error = surface_->Unmap(); + if (error.Error() != S_OK) { + stage_.Reset(); + surface_.Reset(); + } + // If using staging mode, we only need to recreate ID3D11Texture2D instance. + // This will happen during next CopyFrom call. So this function always returns + // true. + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h new file mode 100644 index 0000000000..e8c2af6662 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/dxgi_texture_staging.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ + +#include <d3d11.h> +#include <dxgi1_2.h> +#include <wrl/client.h> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/win/d3d_device.h" +#include "modules/desktop_capture/win/dxgi_texture.h" + +namespace webrtc { + +// A pair of an ID3D11Texture2D and an IDXGISurface. We need an ID3D11Texture2D +// instance to copy GPU texture to RAM, but an IDXGISurface instance to map the +// texture into a bitmap buffer. These two instances are pointing to a same +// object. +// +// An ID3D11Texture2D is created by an ID3D11Device, so a DxgiTexture cannot be +// shared between two DxgiAdapterDuplicators. +class DxgiTextureStaging : public DxgiTexture { + public: + // Creates a DxgiTextureStaging instance. Caller must maintain the lifetime + // of input device to make sure it outlives this instance. + explicit DxgiTextureStaging(const D3dDevice& device); + + ~DxgiTextureStaging() override; + + protected: + // Copies selected regions of a frame represented by frame_info and texture. + // Returns false if anything wrong. + bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + ID3D11Texture2D* texture) override; + + bool DoRelease() override; + + private: + // Initializes stage_ from a CPU inaccessible IDXGIResource. Returns false if + // it failed to execute Windows APIs, or the size of the texture is not + // consistent with desktop_rect. + bool InitializeStage(ID3D11Texture2D* texture); + + // Makes sure stage_ and surface_ are always pointing to a same object. + // We need an ID3D11Texture2D instance for + // ID3D11DeviceContext::CopySubresourceRegion, but an IDXGISurface for + // IDXGISurface::Map. + void AssertStageAndSurfaceAreSameObject(); + + const DesktopRect desktop_rect_; + const D3dDevice device_; + Microsoft::WRL::ComPtr<ID3D11Texture2D> stage_; + Microsoft::WRL::ComPtr<IDXGISurface> surface_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc new file mode 100644 index 0000000000..c6143ef785 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.cc @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/full_screen_win_application_handler.h" + +#include <algorithm> +#if defined(WEBRTC_MOZILLA_BUILD) +#include <cwctype> +#endif +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/logging.h" // For RTC_LOG_GLE +#include "rtc_base/string_utils.h" + +namespace webrtc { +namespace { + +// Utility function to verify that `window` has class name equal to `class_name` +bool CheckWindowClassName(HWND window, const wchar_t* class_name) { + const size_t classNameLength = wcslen(class_name); + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa + // says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't + // need to have a buffer bigger than that. + constexpr size_t kMaxClassNameLength = 256; + WCHAR buffer[kMaxClassNameLength]; + + const int length = ::GetClassNameW(window, buffer, kMaxClassNameLength); + if (length <= 0) + return false; + + if (static_cast<size_t>(length) != classNameLength) + return false; + return wcsncmp(buffer, class_name, classNameLength) == 0; +} + +std::string WindowText(HWND window) { + size_t len = ::GetWindowTextLength(window); + if (len == 0) + return std::string(); + + std::vector<wchar_t> buffer(len + 1, 0); + size_t copied = ::GetWindowTextW(window, buffer.data(), buffer.size()); + if (copied == 0) + return std::string(); + return rtc::ToUtf8(buffer.data(), copied); +} + +DWORD WindowProcessId(HWND window) { + DWORD dwProcessId = 0; + ::GetWindowThreadProcessId(window, &dwProcessId); + return dwProcessId; +} + +std::wstring FileNameFromPath(const std::wstring& path) { + auto found = path.rfind(L"\\"); + if (found == std::string::npos) + return path; + return path.substr(found + 1); +} + +// Returns windows which belong to given process id +// `sources` is a full list of available windows +// `processId` is a process identifier (window owner) +// `window_to_exclude` is a window to be exluded from result +DesktopCapturer::SourceList GetProcessWindows( + const DesktopCapturer::SourceList& sources, + DWORD processId, + HWND window_to_exclude) { + DesktopCapturer::SourceList result; + std::copy_if(sources.begin(), sources.end(), std::back_inserter(result), + [&](DesktopCapturer::Source source) { + const HWND source_hwnd = reinterpret_cast<HWND>(source.id); + return window_to_exclude != source_hwnd && + WindowProcessId(source_hwnd) == processId; + }); + return result; +} + +class FullScreenPowerPointHandler : public FullScreenApplicationHandler { + public: + explicit FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId) + : FullScreenApplicationHandler(sourceId) {} + + ~FullScreenPowerPointHandler() override {} + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& window_list, + int64_t timestamp) const override { + if (window_list.empty()) + return 0; + + HWND original_window = reinterpret_cast<HWND>(GetSourceId()); + DWORD process_id = WindowProcessId(original_window); + + DesktopCapturer::SourceList powerpoint_windows = + GetProcessWindows(window_list, process_id, original_window); + + if (powerpoint_windows.empty()) + return 0; + + if (GetWindowType(original_window) != WindowType::kEditor) + return 0; + + const auto original_document = GetDocumentFromEditorTitle(original_window); + + for (const auto& source : powerpoint_windows) { + HWND window = reinterpret_cast<HWND>(source.id); + + // Looking for slide show window for the same document + if (GetWindowType(window) != WindowType::kSlideShow || + GetDocumentFromSlideShowTitle(window) != original_document) { + continue; + } + + return source.id; + } + + return 0; + } + + private: + enum class WindowType { kEditor, kSlideShow, kOther }; + + WindowType GetWindowType(HWND window) const { + if (IsEditorWindow(window)) + return WindowType::kEditor; + else if (IsSlideShowWindow(window)) + return WindowType::kSlideShow; + else + return WindowType::kOther; + } + + constexpr static char kDocumentTitleSeparator[] = " - "; + + std::string GetDocumentFromEditorTitle(HWND window) const { + std::string title = WindowText(window); + auto position = title.find(kDocumentTitleSeparator); + return std::string(absl::StripAsciiWhitespace( + absl::string_view(title).substr(0, position))); + } + + std::string GetDocumentFromSlideShowTitle(HWND window) const { + std::string title = WindowText(window); + auto left_pos = title.find(kDocumentTitleSeparator); + auto right_pos = title.rfind(kDocumentTitleSeparator); + constexpr size_t kSeparatorLength = arraysize(kDocumentTitleSeparator) - 1; + if (left_pos == std::string::npos || right_pos == std::string::npos) + return title; + + if (right_pos > left_pos + kSeparatorLength) { + auto result_len = right_pos - left_pos - kSeparatorLength; + auto document = absl::string_view(title).substr( + left_pos + kSeparatorLength, result_len); + return std::string(absl::StripAsciiWhitespace(document)); + } else { + auto document = absl::string_view(title).substr( + left_pos + kSeparatorLength, std::wstring::npos); + return std::string(absl::StripAsciiWhitespace(document)); + } + } + + bool IsEditorWindow(HWND window) const { + return CheckWindowClassName(window, L"PPTFrameClass"); + } + + bool IsSlideShowWindow(HWND window) const { + const LONG style = ::GetWindowLong(window, GWL_STYLE); + const bool min_box = WS_MINIMIZEBOX & style; + const bool max_box = WS_MAXIMIZEBOX & style; + return !min_box && !max_box; + } +}; + +class OpenOfficeApplicationHandler : public FullScreenApplicationHandler { + public: + explicit OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId) + : FullScreenApplicationHandler(sourceId) {} + + DesktopCapturer::SourceId FindFullScreenWindow( + const DesktopCapturer::SourceList& window_list, + int64_t timestamp) const override { + if (window_list.empty()) + return 0; + + DWORD process_id = WindowProcessId(reinterpret_cast<HWND>(GetSourceId())); + + DesktopCapturer::SourceList app_windows = + GetProcessWindows(window_list, process_id, nullptr); + + DesktopCapturer::SourceList document_windows; + std::copy_if( + app_windows.begin(), app_windows.end(), + std::back_inserter(document_windows), + [this](const DesktopCapturer::Source& x) { return IsEditorWindow(x); }); + + // Check if we have only one document window, otherwise it's not possible + // to securely match a document window and a slide show window which has + // empty title. + if (document_windows.size() != 1) { + return 0; + } + + // Check if document window has been selected as a source + if (document_windows.front().id != GetSourceId()) { + return 0; + } + + // Check if we have a slide show window. + auto slide_show_window = + std::find_if(app_windows.begin(), app_windows.end(), + [this](const DesktopCapturer::Source& x) { + return IsSlideShowWindow(x); + }); + + if (slide_show_window == app_windows.end()) + return 0; + + return slide_show_window->id; + } + + private: + bool IsEditorWindow(const DesktopCapturer::Source& source) const { + if (source.title.empty()) { + return false; + } + + return CheckWindowClassName(reinterpret_cast<HWND>(source.id), L"SALFRAME"); + } + + bool IsSlideShowWindow(const DesktopCapturer::Source& source) const { + // Check title size to filter out a Presenter Control window which shares + // window class with Slide Show window but has non empty title. + if (!source.title.empty()) { + return false; + } + + return CheckWindowClassName(reinterpret_cast<HWND>(source.id), + L"SALTMPSUBFRAME"); + } +}; + +std::wstring GetPathByWindowId(HWND window_id) { + DWORD process_id = WindowProcessId(window_id); + HANDLE process = + ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id); + if (process == NULL) + return L""; + DWORD path_len = MAX_PATH; + WCHAR path[MAX_PATH]; + std::wstring result; + if (::QueryFullProcessImageNameW(process, 0, path, &path_len)) + result = std::wstring(path, path_len); + else + RTC_LOG_GLE(LS_ERROR) << "QueryFullProcessImageName failed."; + + ::CloseHandle(process); + return result; +} + +} // namespace + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id) { + std::unique_ptr<FullScreenApplicationHandler> result; + HWND hwnd = reinterpret_cast<HWND>(source_id); + std::wstring exe_path = GetPathByWindowId(hwnd); + std::wstring file_name = FileNameFromPath(exe_path); + std::transform(file_name.begin(), file_name.end(), file_name.begin(), + std::towupper); + + if (file_name == L"POWERPNT.EXE") { + result = std::make_unique<FullScreenPowerPointHandler>(source_id); + } else if (file_name == L"SOFFICE.BIN" && + absl::EndsWith(WindowText(hwnd), "OpenOffice Impress")) { + result = std::make_unique<OpenOfficeApplicationHandler>(source_id); + } + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h new file mode 100644 index 0000000000..c97cbe252b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/full_screen_win_application_handler.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_ + +#include <memory> +#include "modules/desktop_capture/full_screen_application_handler.h" + +namespace webrtc { + +std::unique_ptr<FullScreenApplicationHandler> +CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId sourceId); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_FULL_SCREEN_WIN_APPLICATION_HANDLER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h b/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h new file mode 100644 index 0000000000..2b01941e20 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_gdi_object.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ + +#include <windows.h> + +namespace webrtc { +namespace win { + +// Scoper for GDI objects. +template <class T, class Traits> +class ScopedGDIObject { + public: + ScopedGDIObject() : handle_(NULL) {} + explicit ScopedGDIObject(T object) : handle_(object) {} + + ~ScopedGDIObject() { Traits::Close(handle_); } + + ScopedGDIObject(const ScopedGDIObject&) = delete; + ScopedGDIObject& operator=(const ScopedGDIObject&) = delete; + + T Get() { return handle_; } + + void Set(T object) { + if (handle_ && object != handle_) + Traits::Close(handle_); + handle_ = object; + } + + ScopedGDIObject& operator=(T object) { + Set(object); + return *this; + } + + T release() { + T object = handle_; + handle_ = NULL; + return object; + } + + operator T() { return handle_; } + + private: + T handle_; +}; + +// The traits class that uses DeleteObject() to close a handle. +template <typename T> +class DeleteObjectTraits { + public: + DeleteObjectTraits() = delete; + DeleteObjectTraits(const DeleteObjectTraits&) = delete; + DeleteObjectTraits& operator=(const DeleteObjectTraits&) = delete; + + // Closes the handle. + static void Close(T handle) { + if (handle) + DeleteObject(handle); + } +}; + +// The traits class that uses DestroyCursor() to close a handle. +class DestroyCursorTraits { + public: + DestroyCursorTraits() = delete; + DestroyCursorTraits(const DestroyCursorTraits&) = delete; + DestroyCursorTraits& operator=(const DestroyCursorTraits&) = delete; + + // Closes the handle. + static void Close(HCURSOR handle) { + if (handle) + DestroyCursor(handle); + } +}; + +typedef ScopedGDIObject<HBITMAP, DeleteObjectTraits<HBITMAP> > ScopedBitmap; +typedef ScopedGDIObject<HCURSOR, DestroyCursorTraits> ScopedCursor; + +} // namespace win +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc new file mode 100644 index 0000000000..22e8e7bc8f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.cc @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/scoped_thread_desktop.h" + +#include "modules/desktop_capture/win/desktop.h" + +namespace webrtc { + +ScopedThreadDesktop::ScopedThreadDesktop() + : initial_(Desktop::GetThreadDesktop()) {} + +ScopedThreadDesktop::~ScopedThreadDesktop() { + Revert(); +} + +bool ScopedThreadDesktop::IsSame(const Desktop& desktop) { + if (assigned_.get() != NULL) { + return assigned_->IsSame(desktop); + } else { + return initial_->IsSame(desktop); + } +} + +void ScopedThreadDesktop::Revert() { + if (assigned_.get() != NULL) { + initial_->SetThreadDesktop(); + assigned_.reset(); + } +} + +bool ScopedThreadDesktop::SetThreadDesktop(Desktop* desktop) { + Revert(); + + std::unique_ptr<Desktop> scoped_desktop(desktop); + + if (initial_->IsSame(*desktop)) + return true; + + if (!desktop->SetThreadDesktop()) + return false; + + assigned_.reset(scoped_desktop.release()); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h new file mode 100644 index 0000000000..98f151a46c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/scoped_thread_desktop.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ + +#include <windows.h> + +#include <memory> + +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +class Desktop; + +class RTC_EXPORT ScopedThreadDesktop { + public: + ScopedThreadDesktop(); + ~ScopedThreadDesktop(); + + ScopedThreadDesktop(const ScopedThreadDesktop&) = delete; + ScopedThreadDesktop& operator=(const ScopedThreadDesktop&) = delete; + + // Returns true if `desktop` has the same desktop name as the currently + // assigned desktop (if assigned) or as the initial desktop (if not assigned). + // Returns false in any other case including failing Win32 APIs and + // uninitialized desktop handles. + bool IsSame(const Desktop& desktop); + + // Reverts the calling thread to use the initial desktop. + void Revert(); + + // Assigns `desktop` to be the calling thread. Returns true if the thread has + // been switched to `desktop` successfully. Takes ownership of `desktop`. + bool SetThreadDesktop(Desktop* desktop); + + private: + // The desktop handle assigned to the calling thread by Set + std::unique_ptr<Desktop> assigned_; + + // The desktop handle assigned to the calling thread at creation. + std::unique_ptr<Desktop> initial_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc new file mode 100644 index 0000000000..1dc2918d08 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.cc @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capture_utils.h" + +#include <windows.h> + +#include <string> +#include <vector> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/win32.h" + +namespace webrtc { + +bool HasActiveDisplay() { + DesktopCapturer::SourceList screens; + + return GetScreenList(&screens) && !screens.empty(); +} + +bool GetScreenList(DesktopCapturer::SourceList* screens, + std::vector<std::string>* device_names /* = nullptr */) { + RTC_DCHECK(screens->empty()); + RTC_DCHECK(!device_names || device_names->empty()); + + BOOL enum_result = TRUE; + for (int device_index = 0;; ++device_index) { + DISPLAY_DEVICEW device; + device.cb = sizeof(device); + enum_result = EnumDisplayDevicesW(NULL, device_index, &device, 0); + + // `enum_result` is 0 if we have enumerated all devices. + if (!enum_result) { + break; + } + + // We only care about active displays. + if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) { + continue; + } + + screens->push_back({device_index, 0, std::string()}); + if (device_names) { + device_names->push_back(rtc::ToUtf8(device.DeviceName)); + } + } + return true; +} + +bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index, + HMONITOR* hmonitor) { + // A device index of `kFullDesktopScreenId` or -1 represents all screens, an + // HMONITOR of 0 indicates the same. + if (device_index == kFullDesktopScreenId) { + *hmonitor = 0; + return true; + } + + std::wstring device_key; + if (!IsScreenValid(device_index, &device_key)) { + return false; + } + + DesktopRect screen_rect = GetScreenRect(device_index, device_key); + if (screen_rect.is_empty()) { + return false; + } + + RECT rect = {screen_rect.left(), screen_rect.top(), screen_rect.right(), + screen_rect.bottom()}; + + HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL); + if (monitor == NULL) { + RTC_LOG(LS_WARNING) << "No HMONITOR found for supplied device index."; + return false; + } + + *hmonitor = monitor; + return true; +} + +bool IsMonitorValid(const HMONITOR monitor) { + // An HMONITOR of 0 refers to a virtual monitor that spans all physical + // monitors. + if (monitor == 0) { + // There is a bug in a Windows OS API that causes a crash when capturing if + // there are no active displays. We must ensure there is an active display + // before returning true. + if (!HasActiveDisplay()) + return false; + + return true; + } + + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(MONITORINFO); + return GetMonitorInfoA(monitor, &monitor_info); +} + +DesktopRect GetMonitorRect(const HMONITOR monitor) { + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(MONITORINFO); + if (!GetMonitorInfoA(monitor, &monitor_info)) { + return DesktopRect(); + } + + return DesktopRect::MakeLTRB( + monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right, monitor_info.rcMonitor.bottom); +} + +bool IsScreenValid(const DesktopCapturer::SourceId screen, + std::wstring* device_key) { + if (screen == kFullDesktopScreenId) { + *device_key = L""; + return true; + } + + DISPLAY_DEVICEW device; + device.cb = sizeof(device); + BOOL enum_result = EnumDisplayDevicesW(NULL, screen, &device, 0); + if (enum_result) { + *device_key = device.DeviceKey; + } + + return !!enum_result; +} + +DesktopRect GetFullscreenRect() { + return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN)); +} + +DesktopRect GetScreenRect(const DesktopCapturer::SourceId screen, + const std::wstring& device_key) { + RTC_DCHECK(IsGUIThread(false)); + if (screen == kFullDesktopScreenId) { + return GetFullscreenRect(); + } + + DISPLAY_DEVICEW device; + device.cb = sizeof(device); + BOOL result = EnumDisplayDevicesW(NULL, screen, &device, 0); + if (!result) { + return DesktopRect(); + } + + // Verifies the device index still maps to the same display device, to make + // sure we are capturing the same device when devices are added or removed. + // DeviceKey is documented as reserved, but it actually contains the registry + // key for the device and is unique for each monitor, while DeviceID is not. + if (device_key != device.DeviceKey) { + return DesktopRect(); + } + + DEVMODEW device_mode; + device_mode.dmSize = sizeof(device_mode); + device_mode.dmDriverExtra = 0; + result = EnumDisplaySettingsExW(device.DeviceName, ENUM_CURRENT_SETTINGS, + &device_mode, 0); + if (!result) { + return DesktopRect(); + } + + return DesktopRect::MakeXYWH( + device_mode.dmPosition.x, device_mode.dmPosition.y, + device_mode.dmPelsWidth, device_mode.dmPelsHeight); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h new file mode 100644 index 0000000000..97bfe816d8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ + +#if defined(WEBRTC_WIN) +// Forward declare HMONITOR in a windows.h compatible way so that we can avoid +// including windows.h. +#define WEBRTC_DECLARE_HANDLE(name) \ +struct name##__; \ +typedef struct name##__* name +WEBRTC_DECLARE_HANDLE(HMONITOR); +#undef WEBRTC_DECLARE_HANDLE +#endif + +#include <string> +#include <vector> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// Returns true if the system has at least one active display. +bool HasActiveDisplay(); + +// Output the list of active screens into `screens`. Returns true if succeeded, +// or false if it fails to enumerate the display devices. If the `device_names` +// is provided, it will be filled with the DISPLAY_DEVICE.DeviceName in UTF-8 +// encoding. If this function returns true, consumers can always assume that +// `screens`[i] and `device_names`[i] indicate the same monitor on the system. +bool GetScreenList(DesktopCapturer::SourceList* screens, + std::vector<std::string>* device_names = nullptr); + +// Converts a device index (which are returned by `GetScreenList`) into an +// HMONITOR. +bool GetHmonitorFromDeviceIndex(DesktopCapturer::SourceId device_index, + HMONITOR* hmonitor); + +// Returns true if `monitor` represents a valid display +// monitor. Consumers should recheck the validity of HMONITORs before use if a +// WM_DISPLAYCHANGE message has been received. +bool IsMonitorValid(HMONITOR monitor); + +// Returns the rect of the monitor identified by `monitor`, relative to the +// primary display's top-left. On failure, returns an empty rect. +DesktopRect GetMonitorRect(HMONITOR monitor); + +// Returns true if `screen` is a valid screen. The screen device key is +// returned through `device_key` if the screen is valid. The device key can be +// used in GetScreenRect to verify the screen matches the previously obtained +// id. +bool IsScreenValid(DesktopCapturer::SourceId screen, std::wstring* device_key); + +// Get the rect of the entire system in system coordinate system. I.e. the +// primary monitor always starts from (0, 0). +DesktopRect GetFullscreenRect(); + +// Get the rect of the screen identified by `screen`, relative to the primary +// display's top-left. If the screen device key does not match `device_key`, or +// the screen does not exist, or any error happens, an empty rect is returned. +RTC_EXPORT DesktopRect GetScreenRect(DesktopCapturer::SourceId screen, + const std::wstring& device_key); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc new file mode 100644 index 0000000000..2e58c6b164 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capture_utils_unittest.cc @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capture_utils.h" + +#include <string> +#include <vector> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "rtc_base/logging.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(ScreenCaptureUtilsTest, GetScreenList) { + DesktopCapturer::SourceList screens; + std::vector<std::string> device_names; + + ASSERT_TRUE(GetScreenList(&screens)); + screens.clear(); + ASSERT_TRUE(GetScreenList(&screens, &device_names)); + + ASSERT_EQ(screens.size(), device_names.size()); +} + +TEST(ScreenCaptureUtilsTest, DeviceIndexToHmonitor) { + DesktopCapturer::SourceList screens; + ASSERT_TRUE(GetScreenList(&screens)); + if (screens.empty()) { + RTC_LOG(LS_INFO) + << "Skip ScreenCaptureUtilsTest on systems with no monitors."; + GTEST_SKIP(); + } + + HMONITOR hmonitor; + ASSERT_TRUE(GetHmonitorFromDeviceIndex(screens[0].id, &hmonitor)); + ASSERT_TRUE(IsMonitorValid(hmonitor)); +} + +TEST(ScreenCaptureUtilsTest, FullScreenDeviceIndexToHmonitor) { + if (!HasActiveDisplay()) { + RTC_LOG(LS_INFO) + << "Skip ScreenCaptureUtilsTest on systems with no monitors."; + GTEST_SKIP(); + } + + HMONITOR hmonitor; + ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor)); + ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0)); + ASSERT_TRUE(IsMonitorValid(hmonitor)); +} + +TEST(ScreenCaptureUtilsTest, NoMonitors) { + if (HasActiveDisplay()) { + RTC_LOG(LS_INFO) << "Skip ScreenCaptureUtilsTest designed specifically for " + "systems with no monitors"; + GTEST_SKIP(); + } + + HMONITOR hmonitor; + ASSERT_TRUE(GetHmonitorFromDeviceIndex(kFullDesktopScreenId, &hmonitor)); + ASSERT_EQ(hmonitor, static_cast<HMONITOR>(0)); + + // The monitor should be invalid since the system has no attached displays. + ASSERT_FALSE(IsMonitorValid(hmonitor)); +} + +TEST(ScreenCaptureUtilsTest, InvalidDeviceIndexToHmonitor) { + HMONITOR hmonitor; + ASSERT_FALSE(GetHmonitorFromDeviceIndex(kInvalidScreenId, &hmonitor)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc new file mode 100644 index 0000000000..efa763993a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capturer_win_directx.h" + +#include <algorithm> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +// static +bool ScreenCapturerWinDirectx::IsSupported() { + // Forwards IsSupported() function call to DxgiDuplicatorController. + return DxgiDuplicatorController::Instance()->IsSupported(); +} + +// static +bool ScreenCapturerWinDirectx::RetrieveD3dInfo(D3dInfo* info) { + // Forwards SupportedFeatureLevels() function call to + // DxgiDuplicatorController. + return DxgiDuplicatorController::Instance()->RetrieveD3dInfo(info); +} + +// static +bool ScreenCapturerWinDirectx::IsCurrentSessionSupported() { + return DxgiDuplicatorController::IsCurrentSessionSupported(); +} + +// static +bool ScreenCapturerWinDirectx::GetScreenListFromDeviceNames( + const std::vector<std::string>& device_names, + DesktopCapturer::SourceList* screens) { + RTC_DCHECK(screens->empty()); + + DesktopCapturer::SourceList gdi_screens; + std::vector<std::string> gdi_names; + if (!GetScreenList(&gdi_screens, &gdi_names)) { + return false; + } + + RTC_DCHECK_EQ(gdi_screens.size(), gdi_names.size()); + + ScreenId max_screen_id = -1; + for (const DesktopCapturer::Source& screen : gdi_screens) { + max_screen_id = std::max(max_screen_id, screen.id); + } + + for (const auto& device_name : device_names) { + const auto it = std::find(gdi_names.begin(), gdi_names.end(), device_name); + if (it == gdi_names.end()) { + // devices_names[i] has not been found in gdi_names, so use max_screen_id. + max_screen_id++; + screens->push_back({max_screen_id}); + } else { + screens->push_back({gdi_screens[it - gdi_names.begin()]}); + } + } + + return true; +} + +// static +int ScreenCapturerWinDirectx::GetIndexFromScreenId( + ScreenId id, + const std::vector<std::string>& device_names) { + DesktopCapturer::SourceList screens; + if (!GetScreenListFromDeviceNames(device_names, &screens)) { + return -1; + } + + RTC_DCHECK_EQ(device_names.size(), screens.size()); + + for (size_t i = 0; i < screens.size(); i++) { + if (screens[i].id == id) { + return static_cast<int>(i); + } + } + + return -1; +} + +ScreenCapturerWinDirectx::ScreenCapturerWinDirectx() + : controller_(DxgiDuplicatorController::Instance()) {} + +ScreenCapturerWinDirectx::~ScreenCapturerWinDirectx() = default; + +void ScreenCapturerWinDirectx::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinDirectx); + + callback_ = callback; +} + +void ScreenCapturerWinDirectx::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); +} + +void ScreenCapturerWinDirectx::CaptureFrame() { + RTC_DCHECK(callback_); + TRACE_EVENT0("webrtc", "ScreenCapturerWinDirectx::CaptureFrame"); + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + // Note that the [] operator will create the ScreenCaptureFrameQueue if it + // doesn't exist, so this is safe. + ScreenCaptureFrameQueue<DxgiFrame>& frames = + frame_queue_map_[current_screen_id_]; + + frames.MoveToNextFrame(); + + if (!frames.current_frame()) { + frames.ReplaceCurrentFrame( + std::make_unique<DxgiFrame>(shared_memory_factory_.get())); + } + + DxgiDuplicatorController::Result result; + if (current_screen_id_ == kFullDesktopScreenId) { + result = controller_->Duplicate(frames.current_frame()); + } else { + result = controller_->DuplicateMonitor(frames.current_frame(), + current_screen_id_); + } + + using DuplicateResult = DxgiDuplicatorController::Result; + if (result != DuplicateResult::SUCCEEDED) { + RTC_LOG(LS_ERROR) << "DxgiDuplicatorController failed to capture desktop, " + "error code " + << DxgiDuplicatorController::ResultName(result); + } + switch (result) { + case DuplicateResult::UNSUPPORTED_SESSION: { + RTC_LOG(LS_ERROR) + << "Current binary is running on a session not supported " + "by DirectX screen capturer."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + break; + } + case DuplicateResult::FRAME_PREPARE_FAILED: { + RTC_LOG(LS_ERROR) << "Failed to allocate a new DesktopFrame."; + // This usually means we do not have enough memory or SharedMemoryFactory + // cannot work correctly. + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + break; + } + case DuplicateResult::INVALID_MONITOR_ID: { + RTC_LOG(LS_ERROR) << "Invalid monitor id " << current_screen_id_; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + break; + } + case DuplicateResult::INITIALIZATION_FAILED: + case DuplicateResult::DUPLICATION_FAILED: { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + break; + } + case DuplicateResult::SUCCEEDED: { + std::unique_ptr<DesktopFrame> frame = + frames.current_frame()->frame()->Share(); + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000( + "WebRTC.DesktopCapture.Win.DirectXCapturerFrameTime", + capture_time_ms); + frame->set_capture_time_ms(capture_time_ms); + frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinDirectx); + + // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on + // the frame, see WindowCapturerMac::CaptureFrame. + + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); + break; + } + } +} + +bool ScreenCapturerWinDirectx::GetSourceList(SourceList* sources) { + std::vector<std::string> device_names; + if (!controller_->GetDeviceNames(&device_names)) { + return false; + } + + return GetScreenListFromDeviceNames(device_names, sources); +} + +bool ScreenCapturerWinDirectx::SelectSource(SourceId id) { + if (id == kFullDesktopScreenId) { + current_screen_id_ = id; + return true; + } + + std::vector<std::string> device_names; + if (!controller_->GetDeviceNames(&device_names)) { + return false; + } + + int index; + index = GetIndexFromScreenId(id, device_names); + if (index == -1) { + return false; + } + + current_screen_id_ = index; + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h new file mode 100644 index 0000000000..801a0632fc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_ + +#include <d3dcommon.h> + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/win/dxgi_duplicator_controller.h" +#include "modules/desktop_capture/win/dxgi_frame.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// ScreenCapturerWinDirectx captures 32bit RGBA using DirectX. +class RTC_EXPORT ScreenCapturerWinDirectx : public DesktopCapturer { + public: + using D3dInfo = DxgiDuplicatorController::D3dInfo; + + // Whether the system supports DirectX based capturing. + static bool IsSupported(); + + // Returns a most recent D3dInfo composed by + // DxgiDuplicatorController::Initialize() function. This function implicitly + // calls DxgiDuplicatorController::Initialize() if it has not been + // initialized. This function returns false and output parameter is kept + // unchanged if DxgiDuplicatorController::Initialize() failed. + // The D3dInfo may change based on hardware configuration even without + // restarting the hardware and software. Refer to https://goo.gl/OOCppq. So + // consumers should not cache the result returned by this function. + static bool RetrieveD3dInfo(D3dInfo* info); + + // Whether current process is running in a Windows session which is supported + // by ScreenCapturerWinDirectx. + // Usually using ScreenCapturerWinDirectx in unsupported sessions will fail. + // But this behavior may vary on different Windows version. So consumers can + // always try IsSupported() function. + static bool IsCurrentSessionSupported(); + + // Maps `device_names` with the result from GetScreenList() and creates a new + // SourceList to include only the ones in `device_names`. If this function + // returns true, consumers can always assume `device_names`.size() equals to + // `screens`->size(), meanwhile `device_names`[i] and `screens`[i] indicate + // the same monitor on the system. + // Public for test only. + static bool GetScreenListFromDeviceNames( + const std::vector<std::string>& device_names, + DesktopCapturer::SourceList* screens); + + // Maps `id` with the result from GetScreenListFromDeviceNames() and returns + // the index of the entity in `device_names`. This function returns -1 if `id` + // cannot be found. + // Public for test only. + static int GetIndexFromScreenId(ScreenId id, + const std::vector<std::string>& device_names); + + explicit ScreenCapturerWinDirectx(); + + ~ScreenCapturerWinDirectx() override; + + ScreenCapturerWinDirectx(const ScreenCapturerWinDirectx&) = delete; + ScreenCapturerWinDirectx& operator=(const ScreenCapturerWinDirectx&) = delete; + + // DesktopCapturer implementation. + void Start(Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + const rtc::scoped_refptr<DxgiDuplicatorController> controller_; + + // The underlying DxgiDuplicators may retain a reference to the frames that + // we ask them to duplicate so that they can continue returning valid frames + // in the event that the target has not been updated. Thus, we need to ensure + // that we have a separate frame queue for each source id, so that these held + // frames don't get overwritten with the data from another Duplicator/monitor. + std::unordered_map<SourceId, ScreenCaptureFrameQueue<DxgiFrame>> + frame_queue_map_; + std::unique_ptr<SharedMemoryFactory> shared_memory_factory_; + Callback* callback_ = nullptr; + SourceId current_screen_id_ = kFullDesktopScreenId; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc new file mode 100644 index 0000000000..c9f46f782c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_directx_unittest.cc @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capturer_win_directx.h" + +#include <string> +#include <vector> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "test/gtest.h" + +namespace webrtc { + +// This test cannot ensure GetScreenListFromDeviceNames() won't reorder the +// devices in its output, since the device name is missing. +TEST(ScreenCaptureUtilsTest, GetScreenListFromDeviceNamesAndGetIndex) { + const std::vector<std::string> device_names = { + "\\\\.\\DISPLAY0", + "\\\\.\\DISPLAY1", + "\\\\.\\DISPLAY2", + }; + DesktopCapturer::SourceList screens; + ASSERT_TRUE(ScreenCapturerWinDirectx::GetScreenListFromDeviceNames( + device_names, &screens)); + ASSERT_EQ(device_names.size(), screens.size()); + + for (size_t i = 0; i < screens.size(); i++) { + ASSERT_EQ(ScreenCapturerWinDirectx::GetIndexFromScreenId(screens[i].id, + device_names), + static_cast<int>(i)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc new file mode 100644 index 0000000000..4ab864b4b9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capturer_win_gdi.h" + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_frame_win.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/win/cursor.h" +#include "modules/desktop_capture/win/desktop.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { + +// Constants from dwmapi.h. +const UINT DWM_EC_DISABLECOMPOSITION = 0; +const UINT DWM_EC_ENABLECOMPOSITION = 1; + +const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; + +} // namespace + +ScreenCapturerWinGdi::ScreenCapturerWinGdi( + const DesktopCaptureOptions& options) { + if (options.disable_effects()) { + // Load dwmapi.dll dynamically since it is not available on XP. + if (!dwmapi_library_) + dwmapi_library_ = LoadLibraryW(kDwmapiLibraryName); + + if (dwmapi_library_) { + composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>( + GetProcAddress(dwmapi_library_, "DwmEnableComposition")); + composition_enabled_func_ = reinterpret_cast<DwmIsCompositionEnabledFunc> + (GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled")); + } + } +} + +ScreenCapturerWinGdi::~ScreenCapturerWinGdi() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); + if (memory_dc_) + DeleteDC(memory_dc_); + + // Restore Aero. + if (composition_func_) + (*composition_func_)(DWM_EC_ENABLECOMPOSITION); + + if (dwmapi_library_) + FreeLibrary(dwmapi_library_); +} + +void ScreenCapturerWinGdi::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); +} + +void ScreenCapturerWinGdi::CaptureFrame() { + TRACE_EVENT0("webrtc", "ScreenCapturerWinGdi::CaptureFrame"); + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; + } + + // Make sure the GDI capture resources are up-to-date. + PrepareCaptureResources(); + + if (!CaptureImage()) { + RTC_LOG(LS_WARNING) << "Failed to capture screen by GDI."; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + // Emit the current frame. + std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000( + "WebRTC.DesktopCapture.Win.ScreenGdiCapturerFrameTime", capture_time_ms); + frame->set_capture_time_ms(capture_time_ms); + frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinGdi); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool ScreenCapturerWinGdi::GetSourceList(SourceList* sources) { + return webrtc::GetScreenList(sources); +} + +bool ScreenCapturerWinGdi::SelectSource(SourceId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + if (valid) + current_screen_id_ = id; + return valid; +} + +void ScreenCapturerWinGdi::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinGdi); + + callback_ = callback; + + // Vote to disable Aero composited desktop effects while capturing. Windows + // will restore Aero automatically if the process exits. This has no effect + // under Windows 8 or higher. See crbug.com/124018. + if (composition_func_) + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); +} + +void ScreenCapturerWinGdi::PrepareCaptureResources() { + // Switch to the desktop receiving user input if different from the current + // one. + std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop()); + if (input_desktop && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = nullptr; + } + + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = nullptr; + } + + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + + // Re-assert our vote to disable Aero. + // See crbug.com/124018 and crbug.com/129906. + if (composition_func_) { + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); + } + } + + // If the display configurations have changed then recreate GDI resources. + if (display_configuration_monitor_.IsChanged()) { + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = nullptr; + } + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = nullptr; + } + } + + if (!desktop_dc_) { + RTC_DCHECK(!memory_dc_); + + // Create GDI device contexts to capture from the desktop into memory. + desktop_dc_ = GetDC(nullptr); + RTC_CHECK(desktop_dc_); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + RTC_CHECK(memory_dc_); + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + } +} + +bool ScreenCapturerWinGdi::CaptureImage() { + RTC_DCHECK(IsGUIThread(false)); + DesktopRect screen_rect = + GetScreenRect(current_screen_id_, current_device_key_); + if (screen_rect.is_empty()) { + RTC_LOG(LS_WARNING) << "Failed to get screen rect."; + return false; + } + + DesktopSize size = screen_rect.size(); + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || + !queue_.current_frame()->size().equals(screen_rect.size())) { + RTC_DCHECK(desktop_dc_); + RTC_DCHECK(memory_dc_); + + std::unique_ptr<DesktopFrame> buffer = DesktopFrameWin::Create( + size, shared_memory_factory_.get(), desktop_dc_); + if (!buffer) { + RTC_LOG(LS_WARNING) << "Failed to create frame buffer."; + return false; + } + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer))); + } + queue_.current_frame()->set_top_left( + screen_rect.top_left().subtract(GetFullscreenRect().top_left())); + + // Select the target bitmap into the memory dc and copy the rect from desktop + // to memory. + DesktopFrameWin* current = static_cast<DesktopFrameWin*>( + queue_.current_frame()->GetUnderlyingFrame()); + HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); + if (!previous_object || previous_object == HGDI_ERROR) { + RTC_LOG(LS_WARNING) << "Failed to select current bitmap into memery dc."; + return false; + } + + bool result = (BitBlt(memory_dc_, 0, 0, screen_rect.width(), + screen_rect.height(), desktop_dc_, screen_rect.left(), + screen_rect.top(), SRCCOPY | CAPTUREBLT) != FALSE); + if (!result) { + RTC_LOG_GLE(LS_WARNING) << "BitBlt failed"; + } + + // Select back the previously selected object to that the device contect + // could be destroyed independently of the bitmap if needed. + SelectObject(memory_dc_, previous_object); + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h new file mode 100644 index 0000000000..87c1ecfc8c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ + +#include <windows.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/win/display_configuration_monitor.h" +#include "modules/desktop_capture/win/scoped_thread_desktop.h" + +namespace webrtc { + +// ScreenCapturerWinGdi captures 32bit RGB using GDI. +// +// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer. +// This class does not detect DesktopFrame::updated_region(), the field is +// always set to the entire frame rectangle. ScreenCapturerDifferWrapper should +// be used if that functionality is necessary. +class ScreenCapturerWinGdi : public DesktopCapturer { + public: + explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options); + ~ScreenCapturerWinGdi() override; + + ScreenCapturerWinGdi(const ScreenCapturerWinGdi&) = delete; + ScreenCapturerWinGdi& operator=(const ScreenCapturerWinGdi&) = delete; + + // Overridden from ScreenCapturer: + void Start(Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + typedef HRESULT(WINAPI* DwmEnableCompositionFunc)(UINT); + typedef HRESULT(WINAPI* DwmIsCompositionEnabledFunc)(BOOL*); + + // Make sure that the device contexts match the screen configuration. + void PrepareCaptureResources(); + + // Captures the current screen contents into the current buffer. Returns true + // if succeeded. + bool CaptureImage(); + + // Capture the current cursor shape. + void CaptureCursor(); + + Callback* callback_ = nullptr; + std::unique_ptr<SharedMemoryFactory> shared_memory_factory_; + SourceId current_screen_id_ = kFullDesktopScreenId; + std::wstring current_device_key_; + + ScopedThreadDesktop desktop_; + + // GDI resources used for screen capture. + HDC desktop_dc_ = NULL; + HDC memory_dc_ = NULL; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + DisplayConfigurationMonitor display_configuration_monitor_; + + HMODULE dwmapi_library_ = NULL; + DwmEnableCompositionFunc composition_func_ = nullptr; + DwmIsCompositionEnabledFunc composition_enabled_func_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 0000000000..ce747e0141 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_frame_win.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/win/cursor.h" +#include "modules/desktop_capture/win/desktop.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { +DWORD GetTlsIndex() { + static const DWORD tls_index = TlsAlloc(); + RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES); + return tls_index; +} + +} // namespace + +// kMagnifierWindowClass has to be "Magnifier" according to the Magnification +// API. The other strings can be anything. +static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost"; +static wchar_t kHostWindowName[] = L"MagnifierHost"; +static wchar_t kMagnifierWindowClass[] = L"Magnifier"; +static wchar_t kMagnifierWindowName[] = L"MagnifierWindow"; + +ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default; +ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() { + // DestroyWindow must be called before MagUninitialize. magnifier_window_ is + // destroyed automatically when host_window_ is destroyed. + if (host_window_) { + DestroyWindow(host_window_); + host_window_ = NULL; + } + + if (magnifier_initialized_) { + mag_uninitialize_func_(); + magnifier_initialized_ = false; + } + + if (mag_lib_handle_) { + FreeLibrary(mag_lib_handle_); + mag_lib_handle_ = NULL; + } + + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } +} + +void ScreenCapturerWinMagnifier::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinMagnifier); + + callback_ = callback; + + if (!InitializeMagnifier()) { + RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed."; + } +} + +void ScreenCapturerWinMagnifier::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); +} + +void ScreenCapturerWinMagnifier::CaptureFrame() { + RTC_DCHECK(callback_); + if (!magnifier_initialized_) { + RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + // Switch to the desktop receiving user input if different from the current + // one. + std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + } + + DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_); + queue_.MoveToNextFrame(); + CreateCurrentFrameIfNecessary(rect.size()); + // CaptureImage may fail in some situations, e.g. windows8 metro mode. So + // defer to the fallback capturer if magnifier capturer did not work. + if (!CaptureImage(rect)) { + RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + // Emit the current frame. + std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000( + "WebRTC.DesktopCapture.Win.MagnifierCapturerFrameTime", capture_time_ms); + frame->set_capture_time_ms(capture_time_ms); + frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinMagnifier); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) { + return webrtc::GetScreenList(sources); +} + +bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) { + if (IsScreenValid(id, ¤t_device_key_)) { + current_screen_id_ = id; + return true; + } + + return false; +} + +void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) { + excluded_window_ = (HWND)excluded_window; + if (excluded_window_ && magnifier_initialized_) { + set_window_filter_list_func_(magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, + &excluded_window_); + } +} + +bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) { + RTC_DCHECK(magnifier_initialized_); + + // Set the magnifier control to cover the captured rect. The content of the + // magnifier control will be the captured image. + BOOL result = SetWindowPos(magnifier_window_, NULL, rect.left(), rect.top(), + rect.width(), rect.height(), 0); + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() + << "}"; + return false; + } + + magnifier_capture_succeeded_ = false; + + RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()}; + + TlsSetValue(GetTlsIndex(), this); + // OnCaptured will be called via OnMagImageScalingCallback and fill in the + // frame before set_window_source_func_ returns. + result = set_window_source_func_(magnifier_window_, native_rect); + + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " + << GetLastError() << ". Rect = {" << rect.left() + << ", " << rect.top() << ", " << rect.right() << ", " + << rect.bottom() << "}"; + return false; + } + + return magnifier_capture_succeeded_; +} + +BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback( + HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty) { + ScreenCapturerWinMagnifier* owner = + reinterpret_cast<ScreenCapturerWinMagnifier*>(TlsGetValue(GetTlsIndex())); + TlsSetValue(GetTlsIndex(), nullptr); + owner->OnCaptured(srcdata, srcheader); + + return TRUE; +} + +// TODO(zijiehe): These functions are available on Windows Vista or upper, so we +// do not need to use LoadLibrary and GetProcAddress anymore. Use regular +// include and function calls instead of a dynamical loaded library. +bool ScreenCapturerWinMagnifier::InitializeMagnifier() { + RTC_DCHECK(!magnifier_initialized_); + + if (GetSystemMetrics(SM_CMONITORS) != 1) { + // Do not try to use the magnifier in multi-screen setup (where the API + // crashes sometimes). + RTC_LOG_F(LS_WARNING) << "Magnifier capturer cannot work on multi-screen " + "system."; + return false; + } + + desktop_dc_ = GetDC(nullptr); + + mag_lib_handle_ = LoadLibraryW(L"Magnification.dll"); + if (!mag_lib_handle_) + return false; + + // Initialize Magnification API function pointers. + mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>( + GetProcAddress(mag_lib_handle_, "MagInitialize")); + mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>( + GetProcAddress(mag_lib_handle_, "MagUninitialize")); + set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>( + GetProcAddress(mag_lib_handle_, "MagSetWindowSource")); + set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>( + GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList")); + set_image_scaling_callback_func_ = + reinterpret_cast<MagSetImageScalingCallbackFunc>( + GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback")); + + if (!mag_initialize_func_ || !mag_uninitialize_func_ || + !set_window_source_func_ || !set_window_filter_list_func_ || + !set_image_scaling_callback_func_) { + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "library functions missing."; + return false; + } + + BOOL result = mag_initialize_func_(); + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagInitialize " + << GetLastError(); + return false; + } + + HMODULE hInstance = nullptr; + result = + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<char*>(&DefWindowProc), &hInstance); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from GetModulehandleExA " + << GetLastError(); + return false; + } + + // Register the host window class. See the MSDN documentation of the + // Magnification API for more infomation. + WNDCLASSEXW wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = &DefWindowProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.lpszClassName = kMagnifierHostClass; + + // Ignore the error which may happen when the class is already registered. + RegisterClassExW(&wcex); + + // Create the host window. + host_window_ = + CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0, + 0, 0, 0, nullptr, nullptr, hInstance, nullptr); + if (!host_window_) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from creating host window " + << GetLastError(); + return false; + } + + // Create the magnifier control. + magnifier_window_ = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName, + WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, + host_window_, nullptr, hInstance, nullptr); + if (!magnifier_window_) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from creating magnifier window " + << GetLastError(); + return false; + } + + // Hide the host window. + ShowWindow(host_window_, SW_HIDE); + + // Set the scaling callback to receive captured image. + result = set_image_scaling_callback_func_( + magnifier_window_, + &ScreenCapturerWinMagnifier::OnMagImageScalingCallback); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagSetImageScalingCallback " + << GetLastError(); + return false; + } + + if (excluded_window_) { + result = set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) + << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagSetWindowFilterList " + << GetLastError(); + return false; + } + } + + magnifier_initialized_ = true; + return true; +} + +void ScreenCapturerWinMagnifier::OnCaptured(void* data, + const MAGIMAGEHEADER& header) { + DesktopFrame* current_frame = queue_.current_frame(); + + // Verify the format. + // TODO(jiayl): support capturing sources with pixel formats other than RGBA. + int captured_bytes_per_pixel = header.cbSize / header.width / header.height; + if (header.format != GUID_WICPixelFormat32bppRGBA || + header.width != static_cast<UINT>(current_frame->size().width()) || + header.height != static_cast<UINT>(current_frame->size().height()) || + header.stride != static_cast<UINT>(current_frame->stride()) || + captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + RTC_LOG_F(LS_WARNING) + << "Output format does not match the captured format: " + "width = " + << header.width + << ", " + "height = " + << header.height + << ", " + "stride = " + << header.stride + << ", " + "bpp = " + << captured_bytes_per_pixel + << ", " + "pixel format RGBA ? " + << (header.format == GUID_WICPixelFormat32bppRGBA) << "."; + return; + } + + // Copy the data into the frame. + current_frame->CopyPixelsFrom( + reinterpret_cast<uint8_t*>(data), header.stride, + DesktopRect::MakeXYWH(0, 0, header.width, header.height)); + + magnifier_capture_succeeded_ = true; +} + +void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary( + const DesktopSize& size) { + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) { + std::unique_ptr<DesktopFrame> frame = + shared_memory_factory_ + ? SharedMemoryDesktopFrame::Create(size, + shared_memory_factory_.get()) + : std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(size)); + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame))); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h new file mode 100644 index 0000000000..07c5b1e9e6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ + +#include <magnification.h> +#include <wincodec.h> +#include <windows.h> + +#include <memory> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/screen_capture_frame_queue.h" +#include "modules/desktop_capture/screen_capturer_helper.h" +#include "modules/desktop_capture/shared_desktop_frame.h" +#include "modules/desktop_capture/win/scoped_thread_desktop.h" + +namespace webrtc { + +class DesktopFrame; +class DesktopRect; + +// Captures the screen using the Magnification API to support window exclusion. +// Each capturer must run on a dedicated thread because it uses thread local +// storage for redirecting the library callback. Also the thread must have a UI +// message loop to handle the window messages for the magnifier window. +// +// This class does not detect DesktopFrame::updated_region(), the field is +// always set to the entire frame rectangle. ScreenCapturerDifferWrapper should +// be used if that functionality is necessary. +class ScreenCapturerWinMagnifier : public DesktopCapturer { + public: + ScreenCapturerWinMagnifier(); + ~ScreenCapturerWinMagnifier() override; + + ScreenCapturerWinMagnifier(const ScreenCapturerWinMagnifier&) = delete; + ScreenCapturerWinMagnifier& operator=(const ScreenCapturerWinMagnifier&) = + delete; + + // Overridden from ScreenCapturer: + void Start(Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + void SetExcludedWindow(WindowId window) override; + + private: + typedef BOOL(WINAPI* MagImageScalingCallback)(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + typedef BOOL(WINAPI* MagInitializeFunc)(void); + typedef BOOL(WINAPI* MagUninitializeFunc)(void); + typedef BOOL(WINAPI* MagSetWindowSourceFunc)(HWND hwnd, RECT rect); + typedef BOOL(WINAPI* MagSetWindowFilterListFunc)(HWND hwnd, + DWORD dwFilterMode, + int count, + HWND* pHWND); + typedef BOOL(WINAPI* MagSetImageScalingCallbackFunc)( + HWND hwnd, + MagImageScalingCallback callback); + + static BOOL WINAPI OnMagImageScalingCallback(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + + // Captures the screen within `rect` in the desktop coordinates. Returns true + // if succeeded. + // It can only capture the primary screen for now. The magnification library + // crashes under some screen configurations (e.g. secondary screen on top of + // primary screen) if it tries to capture a non-primary screen. The caller + // must make sure not calling it on non-primary screens. + bool CaptureImage(const DesktopRect& rect); + + // Helper method for setting up the magnifier control. Returns true if + // succeeded. + bool InitializeMagnifier(); + + // Called by OnMagImageScalingCallback to output captured data. + void OnCaptured(void* data, const MAGIMAGEHEADER& header); + + // Makes sure the current frame exists and matches `size`. + void CreateCurrentFrameIfNecessary(const DesktopSize& size); + + Callback* callback_ = nullptr; + std::unique_ptr<SharedMemoryFactory> shared_memory_factory_; + ScreenId current_screen_id_ = kFullDesktopScreenId; + std::wstring current_device_key_; + HWND excluded_window_ = NULL; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue<SharedDesktopFrame> queue_; + + ScopedThreadDesktop desktop_; + + // Used for getting the screen dpi. + HDC desktop_dc_ = NULL; + + HMODULE mag_lib_handle_ = NULL; + MagInitializeFunc mag_initialize_func_ = nullptr; + MagUninitializeFunc mag_uninitialize_func_ = nullptr; + MagSetWindowSourceFunc set_window_source_func_ = nullptr; + MagSetWindowFilterListFunc set_window_filter_list_func_ = nullptr; + MagSetImageScalingCallbackFunc set_image_scaling_callback_func_ = nullptr; + + // The hidden window hosting the magnifier control. + HWND host_window_ = NULL; + // The magnifier control that captures the screen. + HWND magnifier_window_ = NULL; + + // True if the magnifier control has been successfully initialized. + bool magnifier_initialized_ = false; + + // True if the last OnMagImageScalingCallback was called and handled + // successfully. Reset at the beginning of each CaptureImage call. + bool magnifier_capture_succeeded_ = true; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc new file mode 100644 index 0000000000..398ea1e53a --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/selected_window_context.h" + +namespace webrtc { + +SelectedWindowContext::SelectedWindowContext( + HWND selected_window, + DesktopRect selected_window_rect, + WindowCaptureHelperWin* window_capture_helper) + : selected_window_(selected_window), + selected_window_rect_(selected_window_rect), + window_capture_helper_(window_capture_helper) { + selected_window_thread_id_ = + GetWindowThreadProcessId(selected_window, &selected_window_process_id_); +} + +bool SelectedWindowContext::IsSelectedWindowValid() const { + return selected_window_thread_id_ != 0; +} + +bool SelectedWindowContext::IsWindowOwnedBySelectedWindow(HWND hwnd) const { + // This check works for drop-down menus & dialog pop-up windows. + if (GetAncestor(hwnd, GA_ROOTOWNER) == selected_window_) { + return true; + } + + // Assume that all other windows are unrelated to the selected window. + // This will cause some windows that are actually related to be missed, + // e.g. context menus and tool-tips, but avoids the risk of capturing + // unrelated windows. Using heuristics such as matching the thread and + // process Ids suffers from false-positives, e.g. in multi-document + // applications. + + return false; +} + +bool SelectedWindowContext::IsWindowOverlappingSelectedWindow(HWND hwnd) const { + return window_capture_helper_->AreWindowsOverlapping(hwnd, selected_window_, + selected_window_rect_); +} + +HWND SelectedWindowContext::selected_window() const { + return selected_window_; +} + +WindowCaptureHelperWin* SelectedWindowContext::window_capture_helper() const { + return window_capture_helper_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h new file mode 100644 index 0000000000..99e38e3fa2 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/selected_window_context.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_ + +#include <windows.h> + +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/win/window_capture_utils.h" + +namespace webrtc { + +class SelectedWindowContext { + public: + SelectedWindowContext(HWND selected_window, + DesktopRect selected_window_rect, + WindowCaptureHelperWin* window_capture_helper); + + bool IsSelectedWindowValid() const; + + bool IsWindowOwnedBySelectedWindow(HWND hwnd) const; + bool IsWindowOverlappingSelectedWindow(HWND hwnd) const; + + HWND selected_window() const; + WindowCaptureHelperWin* window_capture_helper() const; + + private: + const HWND selected_window_; + const DesktopRect selected_window_rect_; + WindowCaptureHelperWin* const window_capture_helper_; + DWORD selected_window_thread_id_; + DWORD selected_window_process_id_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_SELECTED_WINDOW_CONTEXT_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc new file mode 100644 index 0000000000..c07ff74aa5 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.cc @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/test_support/test_window.h" + +namespace webrtc { +namespace { + +const WCHAR kWindowClass[] = L"DesktopCaptureTestWindowClass"; +const int kWindowHeight = 200; +const int kWindowWidth = 300; + +LRESULT CALLBACK WindowProc(HWND hwnd, + UINT msg, + WPARAM w_param, + LPARAM l_param) { + switch (msg) { + case WM_PAINT: + PAINTSTRUCT paint_struct; + HDC hdc = BeginPaint(hwnd, &paint_struct); + + // Paint the window so the color is consistent and we can inspect the + // pixels in tests and know what to expect. + FillRect(hdc, &paint_struct.rcPaint, + CreateSolidBrush(RGB(kTestWindowRValue, kTestWindowGValue, + kTestWindowBValue))); + + EndPaint(hwnd, &paint_struct); + } + return DefWindowProc(hwnd, msg, w_param, l_param); +} + +} // namespace + +WindowInfo CreateTestWindow(const WCHAR* window_title, + const int height, + const int width, + const LONG extended_styles) { + WindowInfo info; + ::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCWSTR>(&WindowProc), + &info.window_instance); + + WNDCLASSEXW wcex; + memset(&wcex, 0, sizeof(wcex)); + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.hInstance = info.window_instance; + wcex.lpfnWndProc = &WindowProc; + wcex.lpszClassName = kWindowClass; + info.window_class = ::RegisterClassExW(&wcex); + + // Use the default height and width if the caller did not supply the optional + // height and width parameters, or if they supplied invalid values. + int window_height = height <= 0 ? kWindowHeight : height; + int window_width = width <= 0 ? kWindowWidth : width; + info.hwnd = + ::CreateWindowExW(extended_styles, kWindowClass, window_title, + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + window_width, window_height, /*parent_window=*/nullptr, + /*menu_bar=*/nullptr, info.window_instance, + /*additional_params=*/nullptr); + + ::ShowWindow(info.hwnd, SW_SHOWNORMAL); + ::UpdateWindow(info.hwnd); + return info; +} + +void ResizeTestWindow(const HWND hwnd, const int width, const int height) { + // SWP_NOMOVE results in the x and y params being ignored. + ::SetWindowPos(hwnd, HWND_TOP, /*x-coord=*/0, /*y-coord=*/0, width, height, + SWP_SHOWWINDOW | SWP_NOMOVE); + ::UpdateWindow(hwnd); +} + +void MoveTestWindow(const HWND hwnd, const int x, const int y) { + // SWP_NOSIZE results in the width and height params being ignored. + ::SetWindowPos(hwnd, HWND_TOP, x, y, /*width=*/0, /*height=*/0, + SWP_SHOWWINDOW | SWP_NOSIZE); + ::UpdateWindow(hwnd); +} + +void MinimizeTestWindow(const HWND hwnd) { + ::ShowWindow(hwnd, SW_MINIMIZE); +} + +void UnminimizeTestWindow(const HWND hwnd) { + ::OpenIcon(hwnd); +} + +void DestroyTestWindow(WindowInfo info) { + ::DestroyWindow(info.hwnd); + ::UnregisterClass(MAKEINTATOM(info.window_class), info.window_instance); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h new file mode 100644 index 0000000000..b055da7ccd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/test_support/test_window.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +namespace webrtc { + +typedef unsigned char uint8_t; + +// Define an arbitrary color for the test window with unique R, G, and B values +// so consumers can verify captured content in tests. +const uint8_t kTestWindowRValue = 191; +const uint8_t kTestWindowGValue = 99; +const uint8_t kTestWindowBValue = 12; + +struct WindowInfo { + HWND hwnd; + HINSTANCE window_instance; + ATOM window_class; +}; + +WindowInfo CreateTestWindow(const WCHAR* window_title, + int height = 0, + int width = 0, + LONG extended_styles = 0); + +void ResizeTestWindow(HWND hwnd, int width, int height); + +void MoveTestWindow(HWND hwnd, int x, int y); + +void MinimizeTestWindow(HWND hwnd); + +void UnminimizeTestWindow(HWND hwnd); + +void DestroyTestWindow(WindowInfo info); + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_TEST_SUPPORT_TEST_WINDOW_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc new file mode 100644 index 0000000000..0ff2db27c7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_capture_session.h" + +#include <DispatcherQueue.h> +#include <windows.graphics.capture.interop.h> +#include <windows.graphics.directX.direct3d11.interop.h> +#include <windows.graphics.h> +#include <wrl/client.h> +#include <wrl/event.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/win/wgc_desktop_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/win/create_direct3d_device.h" +#include "rtc_base/win/get_activation_factory.h" +#include "system_wrappers/include/metrics.h" + +using Microsoft::WRL::ComPtr; +namespace WGC = ABI::Windows::Graphics::Capture; + +namespace webrtc { +namespace { + +// We must use a BGRA pixel format that has 4 bytes per pixel, as required by +// the DesktopFrame interface. +constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX:: + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + +// The maximum time `GetFrame` will wait for a frame to arrive, if we don't have +// any in the pool. +constexpr int kMaxWaitForFrameMs = 50; +constexpr int kMaxWaitForFirstFrameMs = 500; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class StartCaptureResult { + kSuccess = 0, + kSourceClosed = 1, + kAddClosedFailed = 2, + kDxgiDeviceCastFailed = 3, + kD3dDelayLoadFailed = 4, + kD3dDeviceCreationFailed = 5, + kFramePoolActivationFailed = 6, + // kFramePoolCastFailed = 7, (deprecated) + // kGetItemSizeFailed = 8, (deprecated) + kCreateFramePoolFailed = 9, + kCreateCaptureSessionFailed = 10, + kStartCaptureFailed = 11, + kMaxValue = kStartCaptureFailed +}; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class GetFrameResult { + kSuccess = 0, + kItemClosed = 1, + kTryGetNextFrameFailed = 2, + kFrameDropped = 3, + kGetSurfaceFailed = 4, + kDxgiInterfaceAccessFailed = 5, + kTexture2dCastFailed = 6, + kCreateMappedTextureFailed = 7, + kMapFrameFailed = 8, + kGetContentSizeFailed = 9, + kResizeMappedTextureFailed = 10, + kRecreateFramePoolFailed = 11, + kMaxValue = kRecreateFramePoolFailed +}; + +void RecordStartCaptureResult(StartCaptureResult error) { + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult", + static_cast<int>(error), static_cast<int>(StartCaptureResult::kMaxValue)); +} + +void RecordGetFrameResult(GetFrameResult error) { + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.DesktopCapture.Win.WgcCaptureSessionGetFrameResult", + static_cast<int>(error), static_cast<int>(GetFrameResult::kMaxValue)); +} + +} // namespace + +WgcCaptureSession::WgcCaptureSession(ComPtr<ID3D11Device> d3d11_device, + ComPtr<WGC::IGraphicsCaptureItem> item, + ABI::Windows::Graphics::SizeInt32 size) + : d3d11_device_(std::move(d3d11_device)), + item_(std::move(item)), + size_(size) {} +WgcCaptureSession::~WgcCaptureSession() { + RemoveEventHandlers(); +} + +HRESULT WgcCaptureSession::StartCapture() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK(!is_capture_started_); + + if (item_closed_) { + RTC_LOG(LS_ERROR) << "The target source has been closed."; + RecordStartCaptureResult(StartCaptureResult::kSourceClosed); + return E_ABORT; + } + + RTC_DCHECK(d3d11_device_); + RTC_DCHECK(item_); + + // Listen for the Closed event, to detect if the source we are capturing is + // closed (e.g. application window is closed or monitor is disconnected). If + // it is, we should abort the capture. + item_closed_token_ = std::make_unique<EventRegistrationToken>(); + auto closed_handler = + Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler< + WGC::GraphicsCaptureItem*, IInspectable*>>( + this, &WgcCaptureSession::OnItemClosed); + HRESULT hr = + item_->add_Closed(closed_handler.Get(), item_closed_token_.get()); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kAddClosedFailed); + return hr; + } + + ComPtr<IDXGIDevice> dxgi_device; + hr = d3d11_device_->QueryInterface(IID_PPV_ARGS(&dxgi_device)); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kDxgiDeviceCastFailed); + return hr; + } + + if (!ResolveCoreWinRTDirect3DDelayload()) { + RecordStartCaptureResult(StartCaptureResult::kD3dDelayLoadFailed); + return E_FAIL; + } + + hr = CreateDirect3DDeviceFromDXGIDevice(dxgi_device.Get(), &direct3d_device_); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kD3dDeviceCreationFailed); + return hr; + } + + ComPtr<WGC::IDirect3D11CaptureFramePoolStatics> frame_pool_statics; + hr = GetActivationFactory< + WGC::IDirect3D11CaptureFramePoolStatics, + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool>( + &frame_pool_statics); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kFramePoolActivationFailed); + return hr; + } + + hr = frame_pool_statics->Create(direct3d_device_.Get(), kPixelFormat, + kNumBuffers, size_, &frame_pool_); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kCreateFramePoolFailed); + return hr; + } + + frames_in_pool_ = 0; + + // Because `WgcCapturerWin` created a `DispatcherQueue`, and we created + // `frame_pool_` via `Create`, the `FrameArrived` event will be delivered on + // the current thread. + frame_arrived_token_ = std::make_unique<EventRegistrationToken>(); + auto frame_arrived_handler = + Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler< + WGC::Direct3D11CaptureFramePool*, IInspectable*>>( + this, &WgcCaptureSession::OnFrameArrived); + hr = frame_pool_->add_FrameArrived(frame_arrived_handler.Get(), + frame_arrived_token_.get()); + + hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_); + if (FAILED(hr)) { + RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed); + return hr; + } + + hr = session_->StartCapture(); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "Failed to start CaptureSession: " << hr; + RecordStartCaptureResult(StartCaptureResult::kStartCaptureFailed); + return hr; + } + + RecordStartCaptureResult(StartCaptureResult::kSuccess); + + is_capture_started_ = true; + return hr; +} + +HRESULT WgcCaptureSession::GetFrame( + std::unique_ptr<DesktopFrame>* output_frame) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + if (item_closed_) { + RTC_LOG(LS_ERROR) << "The target source has been closed."; + RecordGetFrameResult(GetFrameResult::kItemClosed); + return E_ABORT; + } + + RTC_DCHECK(is_capture_started_); + + if (frames_in_pool_ < 1) + wait_for_frame_event_.Wait(first_frame_ ? kMaxWaitForFirstFrameMs + : kMaxWaitForFrameMs); + + ComPtr<WGC::IDirect3D11CaptureFrame> capture_frame; + HRESULT hr = frame_pool_->TryGetNextFrame(&capture_frame); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "TryGetNextFrame failed: " << hr; + RecordGetFrameResult(GetFrameResult::kTryGetNextFrameFailed); + return hr; + } + + if (!capture_frame) { + RecordGetFrameResult(GetFrameResult::kFrameDropped); + return hr; + } + + first_frame_ = false; + --frames_in_pool_; + + // We need to get `capture_frame` as an `ID3D11Texture2D` so that we can get + // the raw image data in the format required by the `DesktopFrame` interface. + ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface> + d3d_surface; + hr = capture_frame->get_Surface(&d3d_surface); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kGetSurfaceFailed); + return hr; + } + + ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> + direct3DDxgiInterfaceAccess; + hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess)); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kDxgiInterfaceAccessFailed); + return hr; + } + + ComPtr<ID3D11Texture2D> texture_2D; + hr = direct3DDxgiInterfaceAccess->GetInterface(IID_PPV_ARGS(&texture_2D)); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kTexture2dCastFailed); + return hr; + } + + if (!mapped_texture_) { + hr = CreateMappedTexture(texture_2D); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed); + return hr; + } + } + + // We need to copy `texture_2D` into `mapped_texture_` as the latter has the + // D3D11_CPU_ACCESS_READ flag set, which lets us access the image data. + // Otherwise it would only be readable by the GPU. + ComPtr<ID3D11DeviceContext> d3d_context; + d3d11_device_->GetImmediateContext(&d3d_context); + + ABI::Windows::Graphics::SizeInt32 new_size; + hr = capture_frame->get_ContentSize(&new_size); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kGetContentSizeFailed); + return hr; + } + + // If the size changed, we must resize `mapped_texture_` and `frame_pool_` to + // fit the new size. This must be done before `CopySubresourceRegion` so that + // the textures are the same size. + if (size_.Height != new_size.Height || size_.Width != new_size.Width) { + hr = CreateMappedTexture(texture_2D, new_size.Width, new_size.Height); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kResizeMappedTextureFailed); + return hr; + } + + hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat, + kNumBuffers, new_size); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed); + return hr; + } + } + + // If the size has changed since the last capture, we must be sure to use + // the smaller dimensions. Otherwise we might overrun our buffer, or + // read stale data from the last frame. + int image_height = std::min(size_.Height, new_size.Height); + int image_width = std::min(size_.Width, new_size.Width); + + D3D11_BOX copy_region; + copy_region.left = 0; + copy_region.top = 0; + copy_region.right = image_width; + copy_region.bottom = image_height; + // Our textures are 2D so we just want one "slice" of the box. + copy_region.front = 0; + copy_region.back = 1; + d3d_context->CopySubresourceRegion(mapped_texture_.Get(), + /*dst_subresource_index=*/0, /*dst_x=*/0, + /*dst_y=*/0, /*dst_z=*/0, texture_2D.Get(), + /*src_subresource_index=*/0, ©_region); + + D3D11_MAPPED_SUBRESOURCE map_info; + hr = d3d_context->Map(mapped_texture_.Get(), /*subresource_index=*/0, + D3D11_MAP_READ, /*D3D11_MAP_FLAG_DO_NOT_WAIT=*/0, + &map_info); + if (FAILED(hr)) { + RecordGetFrameResult(GetFrameResult::kMapFrameFailed); + return hr; + } + + int row_data_length = image_width * DesktopFrame::kBytesPerPixel; + + // Make a copy of the data pointed to by `map_info.pData` so we are free to + // unmap our texture. + uint8_t* src_data = static_cast<uint8_t*>(map_info.pData); + std::vector<uint8_t> image_data; + image_data.resize(image_height * row_data_length); + uint8_t* image_data_ptr = image_data.data(); + for (int i = 0; i < image_height; i++) { + memcpy(image_data_ptr, src_data, row_data_length); + image_data_ptr += row_data_length; + src_data += map_info.RowPitch; + } + + d3d_context->Unmap(mapped_texture_.Get(), 0); + + // Transfer ownership of `image_data` to the output_frame. + DesktopSize size(image_width, image_height); + *output_frame = std::make_unique<WgcDesktopFrame>(size, row_data_length, + std::move(image_data)); + + size_ = new_size; + RecordGetFrameResult(GetFrameResult::kSuccess); + return hr; +} + +HRESULT WgcCaptureSession::CreateMappedTexture( + ComPtr<ID3D11Texture2D> src_texture, + UINT width, + UINT height) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + D3D11_TEXTURE2D_DESC src_desc; + src_texture->GetDesc(&src_desc); + D3D11_TEXTURE2D_DESC map_desc; + map_desc.Width = width == 0 ? src_desc.Width : width; + map_desc.Height = height == 0 ? src_desc.Height : height; + map_desc.MipLevels = src_desc.MipLevels; + map_desc.ArraySize = src_desc.ArraySize; + map_desc.Format = src_desc.Format; + map_desc.SampleDesc = src_desc.SampleDesc; + map_desc.Usage = D3D11_USAGE_STAGING; + map_desc.BindFlags = 0; + map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + map_desc.MiscFlags = 0; + return d3d11_device_->CreateTexture2D(&map_desc, nullptr, &mapped_texture_); +} + +HRESULT WgcCaptureSession::OnFrameArrived( + WGC::IDirect3D11CaptureFramePool* sender, + IInspectable* event_args) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK_LT(frames_in_pool_, kNumBuffers); + ++frames_in_pool_; + wait_for_frame_event_.Set(); + return S_OK; +} + +HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender, + IInspectable* event_args) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + RTC_LOG(LS_INFO) << "Capture target has been closed."; + item_closed_ = true; + is_capture_started_ = false; + + RemoveEventHandlers(); + + mapped_texture_ = nullptr; + session_ = nullptr; + frame_pool_ = nullptr; + direct3d_device_ = nullptr; + item_ = nullptr; + d3d11_device_ = nullptr; + + return S_OK; +} + +void WgcCaptureSession::RemoveEventHandlers() { + HRESULT hr; + if (frame_pool_ && frame_arrived_token_) { + hr = frame_pool_->remove_FrameArrived(*frame_arrived_token_); + frame_arrived_token_.reset(); + if (FAILED(hr)) { + RTC_LOG(LS_WARNING) << "Failed to remove FrameArrived event handler: " + << hr; + } + } + if (item_ && item_closed_token_) { + hr = item_->remove_Closed(*item_closed_token_); + item_closed_token_.reset(); + if (FAILED(hr)) + RTC_LOG(LS_WARNING) << "Failed to remove Closed event handler: " << hr; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h new file mode 100644 index 0000000000..27d412baf9 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_ + +#include <d3d11.h> +#include <windows.graphics.capture.h> +#include <windows.graphics.h> +#include <wrl/client.h> + +#include <memory> + +#include "api/sequence_checker.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/win/wgc_capture_source.h" +#include "rtc_base/event.h" + +namespace webrtc { + +class WgcCaptureSession final { + public: + WgcCaptureSession( + Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device, + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> item, + ABI::Windows::Graphics::SizeInt32 size); + + // Disallow copy and assign. + WgcCaptureSession(const WgcCaptureSession&) = delete; + WgcCaptureSession& operator=(const WgcCaptureSession&) = delete; + + ~WgcCaptureSession(); + + HRESULT StartCapture(); + + // Returns a frame from the frame pool, if any are present. + HRESULT GetFrame(std::unique_ptr<DesktopFrame>* output_frame); + + bool IsCaptureStarted() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return is_capture_started_; + } + + // We keep 2 buffers in the frame pool to balance the staleness of the frame + // with having to wait for frames to arrive too frequently. Too many buffers + // will lead to a high latency, and too few will lead to poor performance. + // We make this public for tests. + static constexpr int kNumBuffers = 2; + + private: + // Initializes `mapped_texture_` with the properties of the `src_texture`, + // overrides the values of some necessary properties like the + // D3D11_CPU_ACCESS_READ flag. Also has optional parameters for what size + // `mapped_texture_` should be, if they aren't provided we will use the size + // of `src_texture`. + HRESULT CreateMappedTexture( + Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture, + UINT width = 0, + UINT height = 0); + + // Event handler for `item_`'s Closed event. + HRESULT OnItemClosed( + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* sender, + IInspectable* event_args); + + // Event handler for `frame_pool_`'s FrameArrived event. + HRESULT OnFrameArrived( + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* sender, + IInspectable* event_args); + + void RemoveEventHandlers(); + + // We wait on this event in `GetFrame` if there are no frames in the pool. + // `OnFrameArrived` will set the event so we can proceed. + rtc::Event wait_for_frame_event_; + int frames_in_pool_; + + // We're willing to wait for a frame a little longer if it's the first one. + bool first_frame_ = true; + + std::unique_ptr<EventRegistrationToken> frame_arrived_token_; + std::unique_ptr<EventRegistrationToken> item_closed_token_; + + // A Direct3D11 Device provided by the caller. We use this to create an + // IDirect3DDevice, and also to create textures that will hold the image data. + Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_; + + // This item represents what we are capturing, we use it to create the + // capture session, and also to listen for the Closed event. + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> + item_; + + // The IDirect3DDevice is necessary to instantiate the frame pool. + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice> + direct3d_device_; + + // The frame pool is where frames are deposited during capture, we retrieve + // them from here with TryGetNextFrame(). + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool> + frame_pool_; + + // This texture holds the final image data. We made it a member so we can + // reuse it, instead of having to create a new texture every time we grab a + // frame. + Microsoft::WRL::ComPtr<ID3D11Texture2D> mapped_texture_; + + // This is the size of `mapped_texture_` and the buffers in `frame_pool_`. We + // store this as a member so we can compare it to the size of incoming frames + // and resize if necessary. + ABI::Windows::Graphics::SizeInt32 size_; + + // The capture session lets us set properties about the capture before it + // starts such as whether to capture the mouse cursor, and it lets us tell WGC + // to start capturing frames. + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureSession> + session_; + + bool item_closed_ = false; + bool is_capture_started_ = false; + + SequenceChecker sequence_checker_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc new file mode 100644 index 0000000000..24e6129ec7 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.cc @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_capture_source.h" + +#include <dwmapi.h> +#include <windows.graphics.capture.interop.h> +#include <windows.h> + +#include <utility> + +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "rtc_base/win/get_activation_factory.h" + +using Microsoft::WRL::ComPtr; +namespace WGC = ABI::Windows::Graphics::Capture; + +namespace webrtc { + +WgcCaptureSource::WgcCaptureSource(DesktopCapturer::SourceId source_id) + : source_id_(source_id) {} +WgcCaptureSource::~WgcCaptureSource() = default; + +bool WgcCaptureSource::IsCapturable() { + // If we can create a capture item, then we can capture it. Unfortunately, + // we can't cache this item because it may be created in a different COM + // apartment than where capture will eventually start from. + ComPtr<WGC::IGraphicsCaptureItem> item; + return SUCCEEDED(CreateCaptureItem(&item)); +} + +bool WgcCaptureSource::FocusOnSource() { + return false; +} + +ABI::Windows::Graphics::SizeInt32 WgcCaptureSource::GetSize() { + if (!item_) + return {0, 0}; + + ABI::Windows::Graphics::SizeInt32 item_size; + HRESULT hr = item_->get_Size(&item_size); + if (FAILED(hr)) + return {0, 0}; + + return item_size; +} + +HRESULT WgcCaptureSource::GetCaptureItem( + ComPtr<WGC::IGraphicsCaptureItem>* result) { + HRESULT hr = S_OK; + if (!item_) + hr = CreateCaptureItem(&item_); + + *result = item_; + return hr; +} + +WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default; + +WgcWindowSourceFactory::WgcWindowSourceFactory() = default; +WgcWindowSourceFactory::~WgcWindowSourceFactory() = default; + +std::unique_ptr<WgcCaptureSource> WgcWindowSourceFactory::CreateCaptureSource( + DesktopCapturer::SourceId source_id) { + return std::make_unique<WgcWindowSource>(source_id); +} + +WgcScreenSourceFactory::WgcScreenSourceFactory() = default; +WgcScreenSourceFactory::~WgcScreenSourceFactory() = default; + +std::unique_ptr<WgcCaptureSource> WgcScreenSourceFactory::CreateCaptureSource( + DesktopCapturer::SourceId source_id) { + return std::make_unique<WgcScreenSource>(source_id); +} + +WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id) + : WgcCaptureSource(source_id) {} +WgcWindowSource::~WgcWindowSource() = default; + +DesktopVector WgcWindowSource::GetTopLeft() { + DesktopRect window_rect; + if (!GetWindowRect(reinterpret_cast<HWND>(GetSourceId()), &window_rect)) + return DesktopVector(); + + return window_rect.top_left(); +} + +ABI::Windows::Graphics::SizeInt32 WgcWindowSource::GetSize() { + RECT window_rect; + HRESULT hr = ::DwmGetWindowAttribute( + reinterpret_cast<HWND>(GetSourceId()), DWMWA_EXTENDED_FRAME_BOUNDS, + reinterpret_cast<void*>(&window_rect), sizeof(window_rect)); + if (FAILED(hr)) + return WgcCaptureSource::GetSize(); + + return {window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top}; +} + +bool WgcWindowSource::IsCapturable() { + if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()))) + return false; + + return WgcCaptureSource::IsCapturable(); +} + +bool WgcWindowSource::FocusOnSource() { + if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId()))) + return false; + + return ::BringWindowToTop(reinterpret_cast<HWND>(GetSourceId())) && + ::SetForegroundWindow(reinterpret_cast<HWND>(GetSourceId())); +} + +HRESULT WgcWindowSource::CreateCaptureItem( + ComPtr<WGC::IGraphicsCaptureItem>* result) { + if (!ResolveCoreWinRTDelayload()) + return E_FAIL; + + ComPtr<IGraphicsCaptureItemInterop> interop; + HRESULT hr = GetActivationFactory< + IGraphicsCaptureItemInterop, + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop); + if (FAILED(hr)) + return hr; + + ComPtr<WGC::IGraphicsCaptureItem> item; + hr = interop->CreateForWindow(reinterpret_cast<HWND>(GetSourceId()), + IID_PPV_ARGS(&item)); + if (FAILED(hr)) + return hr; + + if (!item) + return E_HANDLE; + + *result = std::move(item); + return hr; +} + +WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id) + : WgcCaptureSource(source_id) { + // Getting the HMONITOR could fail if the source_id is invalid. In that case, + // we leave hmonitor_ uninitialized and `IsCapturable()` will fail. + HMONITOR hmon; + if (GetHmonitorFromDeviceIndex(GetSourceId(), &hmon)) + hmonitor_ = hmon; +} + +WgcScreenSource::~WgcScreenSource() = default; + +DesktopVector WgcScreenSource::GetTopLeft() { + if (!hmonitor_) + return DesktopVector(); + + return GetMonitorRect(*hmonitor_).top_left(); +} + +ABI::Windows::Graphics::SizeInt32 WgcScreenSource::GetSize() { + ABI::Windows::Graphics::SizeInt32 size = WgcCaptureSource::GetSize(); + if (!hmonitor_ || (size.Width != 0 && size.Height != 0)) + return size; + + DesktopRect rect = GetMonitorRect(*hmonitor_); + return {rect.width(), rect.height()}; +} + +bool WgcScreenSource::IsCapturable() { + if (!hmonitor_) + return false; + + if (!IsMonitorValid(*hmonitor_)) + return false; + + return WgcCaptureSource::IsCapturable(); +} + +HRESULT WgcScreenSource::CreateCaptureItem( + ComPtr<WGC::IGraphicsCaptureItem>* result) { + if (!hmonitor_) + return E_ABORT; + + if (!ResolveCoreWinRTDelayload()) + return E_FAIL; + + ComPtr<IGraphicsCaptureItemInterop> interop; + HRESULT hr = GetActivationFactory< + IGraphicsCaptureItemInterop, + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop); + if (FAILED(hr)) + return hr; + + // Ensure the monitor is still valid (hasn't disconnected) before trying to + // create the item. On versions of Windows before Win11, `CreateForMonitor` + // will crash if no displays are connected. + if (!IsMonitorValid(hmonitor_.value())) + return E_ABORT; + + ComPtr<WGC::IGraphicsCaptureItem> item; + hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item)); + if (FAILED(hr)) + return hr; + + if (!item) + return E_HANDLE; + + *result = std::move(item); + return hr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h new file mode 100644 index 0000000000..d1275b6168 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_ + +#include <windows.graphics.capture.h> +#include <windows.graphics.h> +#include <wrl/client.h> + +#include <memory> + +#include "absl/types/optional.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Abstract class to represent the source that WGC-based capturers capture +// from. Could represent an application window or a screen. Consumers should use +// the appropriate Wgc*SourceFactory class to create WgcCaptureSource objects +// of the appropriate type. +class WgcCaptureSource { + public: + explicit WgcCaptureSource(DesktopCapturer::SourceId source_id); + virtual ~WgcCaptureSource(); + + virtual DesktopVector GetTopLeft() = 0; + virtual bool IsCapturable(); + virtual bool FocusOnSource(); + virtual ABI::Windows::Graphics::SizeInt32 GetSize(); + HRESULT GetCaptureItem( + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result); + DesktopCapturer::SourceId GetSourceId() { return source_id_; } + + protected: + virtual HRESULT CreateCaptureItem( + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) = 0; + + private: + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> + item_; + const DesktopCapturer::SourceId source_id_; +}; + +class WgcCaptureSourceFactory { + public: + virtual ~WgcCaptureSourceFactory(); + + virtual std::unique_ptr<WgcCaptureSource> CreateCaptureSource( + DesktopCapturer::SourceId) = 0; +}; + +class WgcWindowSourceFactory final : public WgcCaptureSourceFactory { + public: + WgcWindowSourceFactory(); + + // Disallow copy and assign. + WgcWindowSourceFactory(const WgcWindowSourceFactory&) = delete; + WgcWindowSourceFactory& operator=(const WgcWindowSourceFactory&) = delete; + + ~WgcWindowSourceFactory() override; + + std::unique_ptr<WgcCaptureSource> CreateCaptureSource( + DesktopCapturer::SourceId) override; +}; + +class WgcScreenSourceFactory final : public WgcCaptureSourceFactory { + public: + WgcScreenSourceFactory(); + + WgcScreenSourceFactory(const WgcScreenSourceFactory&) = delete; + WgcScreenSourceFactory& operator=(const WgcScreenSourceFactory&) = delete; + + ~WgcScreenSourceFactory() override; + + std::unique_ptr<WgcCaptureSource> CreateCaptureSource( + DesktopCapturer::SourceId) override; +}; + +// Class for capturing application windows. +class WgcWindowSource final : public WgcCaptureSource { + public: + explicit WgcWindowSource(DesktopCapturer::SourceId source_id); + + WgcWindowSource(const WgcWindowSource&) = delete; + WgcWindowSource& operator=(const WgcWindowSource&) = delete; + + ~WgcWindowSource() override; + + DesktopVector GetTopLeft() override; + ABI::Windows::Graphics::SizeInt32 GetSize() override; + bool IsCapturable() override; + bool FocusOnSource() override; + + private: + HRESULT CreateCaptureItem( + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) + override; +}; + +// Class for capturing screens/monitors/displays. +class WgcScreenSource final : public WgcCaptureSource { + public: + explicit WgcScreenSource(DesktopCapturer::SourceId source_id); + + WgcScreenSource(const WgcScreenSource&) = delete; + WgcScreenSource& operator=(const WgcScreenSource&) = delete; + + ~WgcScreenSource() override; + + DesktopVector GetTopLeft() override; + ABI::Windows::Graphics::SizeInt32 GetSize() override; + bool IsCapturable() override; + + private: + HRESULT CreateCaptureItem( + Microsoft::WRL::ComPtr< + ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) + override; + + // To maintain compatibility with other capturers, this class accepts a + // device index as it's SourceId. However, WGC requires we use an HMONITOR to + // describe which screen to capture. So, we internally convert the supplied + // device index into an HMONITOR when `IsCapturable()` is called. + absl::optional<HMONITOR> hmonitor_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc new file mode 100644 index 0000000000..dc37ec2e0d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_source_unittest.cc @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_capture_source.h" + +#include <windows.graphics.capture.h> +#include <wrl/client.h> + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/test_support/test_window.h" +#include "modules/desktop_capture/win/wgc_capturer_win.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/win/scoped_com_initializer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +const WCHAR kWindowTitle[] = L"WGC Capture Source Test Window"; + +const int kFirstXCoord = 25; +const int kFirstYCoord = 50; +const int kSecondXCoord = 50; +const int kSecondYCoord = 75; + +} // namespace + +class WgcCaptureSourceTest : public ::testing::TestWithParam<CaptureType> { + public: + void SetUp() override { + com_initializer_ = + std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); + ASSERT_TRUE(com_initializer_->Succeeded()); + } + + void TearDown() override { + if (window_open_) { + DestroyTestWindow(window_info_); + } + } + + void SetUpForWindowSource() { + window_info_ = CreateTestWindow(kWindowTitle); + window_open_ = true; + source_id_ = reinterpret_cast<DesktopCapturer::SourceId>(window_info_.hwnd); + source_factory_ = std::make_unique<WgcWindowSourceFactory>(); + } + + void SetUpForScreenSource() { + source_id_ = kFullDesktopScreenId; + source_factory_ = std::make_unique<WgcScreenSourceFactory>(); + } + + protected: + std::unique_ptr<ScopedCOMInitializer> com_initializer_; + std::unique_ptr<WgcCaptureSourceFactory> source_factory_; + std::unique_ptr<WgcCaptureSource> source_; + DesktopCapturer::SourceId source_id_; + WindowInfo window_info_; + bool window_open_ = false; +}; + +// Window specific test +TEST_F(WgcCaptureSourceTest, WindowPosition) { + if (!IsWgcSupported(CaptureType::kWindow)) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + + SetUpForWindowSource(); + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_->GetSourceId(), source_id_); + + MoveTestWindow(window_info_.hwnd, kFirstXCoord, kFirstYCoord); + DesktopVector source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), kFirstXCoord); + EXPECT_EQ(source_vector.y(), kFirstYCoord); + + MoveTestWindow(window_info_.hwnd, kSecondXCoord, kSecondYCoord); + source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), kSecondXCoord); + EXPECT_EQ(source_vector.y(), kSecondYCoord); +} + +// Screen specific test +TEST_F(WgcCaptureSourceTest, ScreenPosition) { + if (!IsWgcSupported(CaptureType::kScreen)) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + + SetUpForScreenSource(); + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_id_, source_->GetSourceId()); + + DesktopRect screen_rect = GetFullscreenRect(); + DesktopVector source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), screen_rect.left()); + EXPECT_EQ(source_vector.y(), screen_rect.top()); +} + +// Source agnostic test +TEST_P(WgcCaptureSourceTest, CreateSource) { + if (!IsWgcSupported(GetParam())) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + + if (GetParam() == CaptureType::kWindow) { + SetUpForWindowSource(); + } else { + SetUpForScreenSource(); + } + + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_id_, source_->GetSourceId()); + EXPECT_TRUE(source_->IsCapturable()); + + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> + item; + EXPECT_TRUE(SUCCEEDED(source_->GetCaptureItem(&item))); + EXPECT_TRUE(item); +} + +INSTANTIATE_TEST_SUITE_P(SourceAgnostic, + WgcCaptureSourceTest, + ::testing::Values(CaptureType::kWindow, + CaptureType::kScreen)); + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc new file mode 100644 index 0000000000..ce5eb6b31f --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.cc @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_capturer_win.h" + +#include <DispatcherQueue.h> +#include <windows.foundation.metadata.h> +#include <windows.graphics.capture.h> + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/win/wgc_desktop_frame.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/win/get_activation_factory.h" +#include "rtc_base/win/hstring.h" +#include "rtc_base/win/windows_version.h" +#include "system_wrappers/include/metrics.h" + +namespace WGC = ABI::Windows::Graphics::Capture; +using Microsoft::WRL::ComPtr; + +namespace webrtc { + +namespace { + +constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll"; + +constexpr wchar_t kWgcSessionType[] = + L"Windows.Graphics.Capture.GraphicsCaptureSession"; +constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract"; +constexpr UINT16 kRequiredApiContractVersion = 8; + +enum class WgcCapturerResult { + kSuccess = 0, + kNoDirect3dDevice = 1, + kNoSourceSelected = 2, + kItemCreationFailure = 3, + kSessionStartFailure = 4, + kGetFrameFailure = 5, + kFrameDropped = 6, + kCreateDispatcherQueueFailure = 7, + kMaxValue = kCreateDispatcherQueueFailure +}; + +void RecordWgcCapturerResult(WgcCapturerResult error) { + RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult", + static_cast<int>(error), + static_cast<int>(WgcCapturerResult::kMaxValue)); +} + +} // namespace + +bool IsWgcSupported(CaptureType capture_type) { + if (!HasActiveDisplay()) { + // There is a bug in `CreateForMonitor` that causes a crash if there are no + // active displays. The crash was fixed in Win11, but we are still unable + // to capture screens without an active display. + if (capture_type == CaptureType::kScreen) + return false; + + // There is a bug in the DWM (Desktop Window Manager) that prevents it from + // providing image data if there are no displays attached. This was fixed in + // Windows 11. + if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11) + return false; + } + + // A bug in the WGC API `CreateForMonitor` prevents capturing the entire + // virtual screen (all monitors simultaneously), this was fixed in 20H1. Since + // we can't assert that we won't be asked to capture the entire virtual + // screen, we report unsupported so we can fallback to another capturer. + if (capture_type == CaptureType::kScreen && + rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_20H1) { + return false; + } + + if (!ResolveCoreWinRTDelayload()) + return false; + + // We need to check if the WGC APIs are presesnt on the system. Certain SKUs + // of Windows ship without these APIs. + ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics> + api_info_statics; + HRESULT hr = GetActivationFactory< + ABI::Windows::Foundation::Metadata::IApiInformationStatics, + RuntimeClass_Windows_Foundation_Metadata_ApiInformation>( + &api_info_statics); + if (FAILED(hr)) + return false; + + HSTRING api_contract; + hr = webrtc::CreateHstring(kApiContract, wcslen(kApiContract), &api_contract); + if (FAILED(hr)) + return false; + + boolean is_api_present; + hr = api_info_statics->IsApiContractPresentByMajor( + api_contract, kRequiredApiContractVersion, &is_api_present); + webrtc::DeleteHstring(api_contract); + if (FAILED(hr) || !is_api_present) + return false; + + HSTRING wgc_session_type; + hr = webrtc::CreateHstring(kWgcSessionType, wcslen(kWgcSessionType), + &wgc_session_type); + if (FAILED(hr)) + return false; + + boolean is_type_present; + hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present); + webrtc::DeleteHstring(wgc_session_type); + if (FAILED(hr) || !is_type_present) + return false; + + // If the APIs are present, we need to check that they are supported. + ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics; + hr = GetActivationFactory< + WGC::IGraphicsCaptureSessionStatics, + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>( + &capture_session_statics); + if (FAILED(hr)) + return false; + + boolean is_supported; + hr = capture_session_statics->IsSupported(&is_supported); + if (FAILED(hr) || !is_supported) + return false; + + return true; +} + +WgcCapturerWin::WgcCapturerWin( + std::unique_ptr<WgcCaptureSourceFactory> source_factory, + std::unique_ptr<SourceEnumerator> source_enumerator, + bool allow_delayed_capturable_check) + : source_factory_(std::move(source_factory)), + source_enumerator_(std::move(source_enumerator)), + allow_delayed_capturable_check_(allow_delayed_capturable_check) { + if (!core_messaging_library_) + core_messaging_library_ = LoadLibraryW(kCoreMessagingDll); + + if (core_messaging_library_) { + create_dispatcher_queue_controller_func_ = + reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress( + core_messaging_library_, "CreateDispatcherQueueController")); + } +} + +WgcCapturerWin::~WgcCapturerWin() { + if (core_messaging_library_) + FreeLibrary(core_messaging_library_); +} + +// static +std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer( + const DesktopCaptureOptions& options, + bool allow_delayed_capturable_check) { + return std::make_unique<WgcCapturerWin>( + std::make_unique<WgcWindowSourceFactory>(), + std::make_unique<WindowEnumerator>( + options.enumerate_current_process_windows()), + allow_delayed_capturable_check); +} + +// static +std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + return std::make_unique<WgcCapturerWin>( + std::make_unique<WgcScreenSourceFactory>(), + std::make_unique<ScreenEnumerator>(), false); +} + +bool WgcCapturerWin::GetSourceList(SourceList* sources) { + return source_enumerator_->FindAllSources(sources); +} + +bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) { + capture_source_ = source_factory_->CreateCaptureSource(id); + if (allow_delayed_capturable_check_) + return true; + + return capture_source_->IsCapturable(); +} + +bool WgcCapturerWin::FocusOnSelectedSource() { + if (!capture_source_) + return false; + + return capture_source_->FocusOnSource(); +} + +void WgcCapturerWin::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin); + + callback_ = callback; + + // Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many + // parameters are nullptr as the implemention uses defaults that work well for + // us. + HRESULT hr = D3D11CreateDevice( + /*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE, + /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, + /*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION, + &d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr); + if (hr == DXGI_ERROR_UNSUPPORTED) { + // If a hardware device could not be created, use WARP which is a high speed + // software device. + hr = D3D11CreateDevice( + /*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP, + /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, + /*feature_levels=*/nullptr, /*feature_levels_size=*/0, + D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr, + /*device_context=*/nullptr); + } + + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr; + } +} + +void WgcCapturerWin::CaptureFrame() { + RTC_DCHECK(callback_); + + if (!capture_source_) { + RTC_LOG(LS_ERROR) << "Source hasn't been selected"; + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected); + return; + } + + if (!d3d11_device_) { + RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture."; + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice); + return; + } + + if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) { + RTC_LOG(LS_ERROR) << "Source is not capturable."; + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + return; + } + + HRESULT hr; + if (!dispatcher_queue_created_) { + // Set the apartment type to NONE because this thread should already be COM + // initialized. + DispatcherQueueOptions options{ + sizeof(DispatcherQueueOptions), + DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT, + DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE}; + ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller; + hr = create_dispatcher_queue_controller_func_(options, &queue_controller); + + // If there is already a DispatcherQueue on this thread, that is fine. Its + // lifetime is tied to the thread's, and as long as the thread has one, even + // if we didn't create it, the capture session's events will be delivered on + // this thread. + if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) { + RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure); + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + } else { + dispatcher_queue_created_ = true; + } + } + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + WgcCaptureSession* capture_session = nullptr; + std::map<SourceId, WgcCaptureSession>::iterator session_iter = + ongoing_captures_.find(capture_source_->GetSourceId()); + if (session_iter == ongoing_captures_.end()) { + ComPtr<WGC::IGraphicsCaptureItem> item; + hr = capture_source_->GetCaptureItem(&item); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr; + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure); + return; + } + + std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool> + iter_success_pair = ongoing_captures_.emplace( + std::piecewise_construct, + std::forward_as_tuple(capture_source_->GetSourceId()), + std::forward_as_tuple(d3d11_device_, item, + capture_source_->GetSize())); + RTC_DCHECK(iter_success_pair.second); + capture_session = &iter_success_pair.first->second; + } else { + capture_session = &session_iter->second; + } + + if (!capture_session->IsCaptureStarted()) { + hr = capture_session->StartCapture(); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr; + ongoing_captures_.erase(capture_source_->GetSourceId()); + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure); + return; + } + } + + std::unique_ptr<DesktopFrame> frame; + hr = capture_session->GetFrame(&frame); + if (FAILED(hr)) { + RTC_LOG(LS_ERROR) << "GetFrame failed: " << hr; + ongoing_captures_.erase(capture_source_->GetSourceId()); + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure); + return; + } + + if (!frame) { + callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY, + /*frame=*/nullptr); + RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped); + return; + } + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime", + capture_time_ms); + frame->set_capture_time_ms(capture_time_ms); + frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin); + frame->set_may_contain_cursor(true); + frame->set_top_left(capture_source_->GetTopLeft()); + RecordWgcCapturerResult(WgcCapturerResult::kSuccess); + callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS, + std::move(frame)); +} + +bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) { + std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator + session_iter = ongoing_captures_.find(id); + if (session_iter == ongoing_captures_.end()) + return false; + + return session_iter->second.IsCaptureStarted(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h new file mode 100644 index 0000000000..d9ee9d3fc6 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_ + +#include <DispatcherQueue.h> +#include <d3d11.h> +#include <wrl/client.h> + +#include <map> +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/wgc_capture_session.h" +#include "modules/desktop_capture/win/wgc_capture_source.h" +#include "modules/desktop_capture/win/window_capture_utils.h" + +namespace webrtc { + +// Checks if the WGC API is present and supported on the system. +bool IsWgcSupported(CaptureType capture_type); + +// WgcCapturerWin is initialized with an implementation of this base class, +// which it uses to find capturable sources of a particular type. This way, +// WgcCapturerWin can remain source-agnostic. +class SourceEnumerator { + public: + virtual ~SourceEnumerator() = default; + + virtual bool FindAllSources(DesktopCapturer::SourceList* sources) = 0; +}; + +class WindowEnumerator final : public SourceEnumerator { + public: + explicit WindowEnumerator(bool enumerate_current_process_windows) + : enumerate_current_process_windows_(enumerate_current_process_windows) {} + + WindowEnumerator(const WindowEnumerator&) = delete; + WindowEnumerator& operator=(const WindowEnumerator&) = delete; + + ~WindowEnumerator() override = default; + + bool FindAllSources(DesktopCapturer::SourceList* sources) override { + // WGC fails to capture windows with the WS_EX_TOOLWINDOW style, so we + // provide it as a filter to ensure windows with the style are not returned. + return window_capture_helper_.EnumerateCapturableWindows( + sources, enumerate_current_process_windows_, WS_EX_TOOLWINDOW); + } + + private: + WindowCaptureHelperWin window_capture_helper_; + bool enumerate_current_process_windows_; +}; + +class ScreenEnumerator final : public SourceEnumerator { + public: + ScreenEnumerator() = default; + + ScreenEnumerator(const ScreenEnumerator&) = delete; + ScreenEnumerator& operator=(const ScreenEnumerator&) = delete; + + ~ScreenEnumerator() override = default; + + bool FindAllSources(DesktopCapturer::SourceList* sources) override { + return webrtc::GetScreenList(sources); + } +}; + +// A capturer that uses the Window.Graphics.Capture APIs. It is suitable for +// both window and screen capture (but only one type per instance). Consumers +// should not instantiate this class directly, instead they should use +// `CreateRawWindowCapturer()` or `CreateRawScreenCapturer()` to receive a +// capturer appropriate for the type of source they want to capture. +class WgcCapturerWin : public DesktopCapturer { + public: + WgcCapturerWin(std::unique_ptr<WgcCaptureSourceFactory> source_factory, + std::unique_ptr<SourceEnumerator> source_enumerator, + bool allow_delayed_capturable_check); + + WgcCapturerWin(const WgcCapturerWin&) = delete; + WgcCapturerWin& operator=(const WgcCapturerWin&) = delete; + + ~WgcCapturerWin() override; + + static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer( + const DesktopCaptureOptions& options, + bool allow_delayed_capturable_check = false); + + static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + void Start(Callback* callback) override; + void CaptureFrame() override; + + // Used in WgcCapturerTests. + bool IsSourceBeingCaptured(SourceId id); + + private: + typedef HRESULT(WINAPI* CreateDispatcherQueueControllerFunc)( + DispatcherQueueOptions, + ABI::Windows::System::IDispatcherQueueController**); + + // We need to either create or ensure that someone else created a + // `DispatcherQueue` on the current thread so that events will be delivered + // on the current thread rather than an arbitrary thread. A + // `DispatcherQueue`'s lifetime is tied to the thread's, and we don't post + // any work to it, so we don't need to hold a reference. + bool dispatcher_queue_created_ = false; + + // Statically linking to CoreMessaging.lib is disallowed in Chromium, so we + // load it at runtime. + HMODULE core_messaging_library_ = NULL; + CreateDispatcherQueueControllerFunc create_dispatcher_queue_controller_func_ = + nullptr; + + // Factory to create a WgcCaptureSource for us whenever SelectSource is + // called. Initialized at construction with a source-specific implementation. + std::unique_ptr<WgcCaptureSourceFactory> source_factory_; + + // The source enumerator helps us find capturable sources of the appropriate + // type. Initialized at construction with a source-specific implementation. + std::unique_ptr<SourceEnumerator> source_enumerator_; + + // The WgcCaptureSource represents the source we are capturing. It tells us + // if the source is capturable and it creates the GraphicsCaptureItem for us. + std::unique_ptr<WgcCaptureSource> capture_source_; + + // A map of all the sources we are capturing and the associated + // WgcCaptureSession. Frames for the current source (indicated via + // SelectSource) will be retrieved from the appropriate session when + // requested via CaptureFrame. + // This helps us efficiently capture multiple sources (e.g. when consumers + // are trying to display a list of available capture targets with thumbnails). + std::map<SourceId, WgcCaptureSession> ongoing_captures_; + + // The callback that we deliver frames to, synchronously, before CaptureFrame + // returns. + Callback* callback_ = nullptr; + + // WgcCaptureSource::IsCapturable is expensive to run. So, caller can + // delay capturable check till capture frame is called if the WgcCapturerWin + // is used as a fallback capturer. + bool allow_delayed_capturable_check_ = false; + + // A Direct3D11 device that is shared amongst the WgcCaptureSessions, who + // require one to perform the capture. + Microsoft::WRL::ComPtr<::ID3D11Device> d3d11_device_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc new file mode 100644 index 0000000000..a7b656fcfc --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_capturer_win.h" + +#include <string> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/test_support/test_window.h" +#include "modules/desktop_capture/win/wgc_capture_session.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/thread.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/win/scoped_com_initializer.h" +#include "rtc_base/win/windows_version.h" +#include "system_wrappers/include/metrics.h" +#include "system_wrappers/include/sleep.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr char kWindowThreadName[] = "wgc_capturer_test_window_thread"; +constexpr WCHAR kWindowTitle[] = L"WGC Capturer Test Window"; + +constexpr char kCapturerImplHistogram[] = + "WebRTC.DesktopCapture.Win.DesktopCapturerImpl"; + +constexpr char kCapturerResultHistogram[] = + "WebRTC.DesktopCapture.Win.WgcCapturerResult"; +constexpr int kSuccess = 0; +constexpr int kSessionStartFailure = 4; + +constexpr char kCaptureSessionResultHistogram[] = + "WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult"; +constexpr int kSourceClosed = 1; + +constexpr char kCaptureTimeHistogram[] = + "WebRTC.DesktopCapture.Win.WgcCapturerFrameTime"; + +// The capturer keeps `kNumBuffers` in its frame pool, so we need to request +// that many frames to clear those out. The next frame will have the new size +// (if the size has changed) so we will resize the frame pool at this point. +// Then, we need to clear any frames that may have delivered to the frame pool +// before the resize. Finally, the next frame will be guaranteed to be the new +// size. +constexpr int kNumCapturesToFlushBuffers = + WgcCaptureSession::kNumBuffers * 2 + 1; + +constexpr int kSmallWindowWidth = 200; +constexpr int kSmallWindowHeight = 100; +constexpr int kMediumWindowWidth = 300; +constexpr int kMediumWindowHeight = 200; +constexpr int kLargeWindowWidth = 400; +constexpr int kLargeWindowHeight = 500; + +// The size of the image we capture is slightly smaller than the actual size of +// the window. +constexpr int kWindowWidthSubtrahend = 14; +constexpr int kWindowHeightSubtrahend = 7; + +// Custom message constants so we can direct our thread to close windows and +// quit running. +constexpr UINT kDestroyWindow = WM_APP; +constexpr UINT kQuitRunning = WM_APP + 1; + +// When testing changes to real windows, sometimes the effects (close or resize) +// don't happen immediately, we want to keep trying until we see the effect but +// only for a reasonable amount of time. +constexpr int kMaxTries = 50; + +} // namespace + +class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>, + public DesktopCapturer::Callback { + public: + void SetUp() override { + com_initializer_ = + std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer_->Succeeded()); + + if (!IsWgcSupported(GetParam())) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + } + + void SetUpForWindowCapture(int window_width = kMediumWindowWidth, + int window_height = kMediumWindowHeight) { + capturer_ = WgcCapturerWin::CreateRawWindowCapturer( + DesktopCaptureOptions::CreateDefault()); + CreateWindowOnSeparateThread(window_width, window_height); + StartWindowThreadMessageLoop(); + source_id_ = GetTestWindowIdFromSourceList(); + } + + void SetUpForScreenCapture() { + capturer_ = WgcCapturerWin::CreateRawScreenCapturer( + DesktopCaptureOptions::CreateDefault()); + source_id_ = GetScreenIdFromSourceList(); + } + + void TearDown() override { + if (window_open_) { + CloseTestWindow(); + } + } + + // The window must live on a separate thread so that we can run a message pump + // without blocking the test thread. This is necessary if we are interested in + // having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more + // closely resembles how capture works in the wild. + void CreateWindowOnSeparateThread(int window_width, int window_height) { + window_thread_ = rtc::Thread::Create(); + window_thread_->SetName(kWindowThreadName, nullptr); + window_thread_->Start(); + SendTask(window_thread_.get(), [this, window_width, window_height]() { + window_thread_id_ = GetCurrentThreadId(); + window_info_ = + CreateTestWindow(kWindowTitle, window_height, window_width); + window_open_ = true; + + while (!IsWindowResponding(window_info_.hwnd)) { + RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in " + "WgcWindowCaptureTest."; + } + + while (!IsWindowValidAndVisible(window_info_.hwnd)) { + RTC_LOG(LS_INFO) << "Waiting for test window to be visible in " + "WgcWindowCaptureTest."; + } + }); + + ASSERT_TRUE(window_thread_->RunningForTest()); + ASSERT_FALSE(window_thread_->IsCurrent()); + } + + void StartWindowThreadMessageLoop() { + window_thread_->PostTask([this]() { + MSG msg; + BOOL gm; + while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { + ::DispatchMessage(&msg); + if (msg.message == kDestroyWindow) { + DestroyTestWindow(window_info_); + } + if (msg.message == kQuitRunning) { + PostQuitMessage(0); + } + } + }); + } + + void CloseTestWindow() { + ::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0); + ::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0); + window_thread_->Stop(); + window_open_ = false; + } + + DesktopCapturer::SourceId GetTestWindowIdFromSourceList() { + // Frequently, the test window will not show up in GetSourceList because it + // was created too recently. Since we are confident the window will be found + // eventually we loop here until we find it. + intptr_t src_id = 0; + do { + DesktopCapturer::SourceList sources; + EXPECT_TRUE(capturer_->GetSourceList(&sources)); + auto it = std::find_if( + sources.begin(), sources.end(), + [&](const DesktopCapturer::Source& src) { + return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd); + }); + + if (it != sources.end()) + src_id = it->id; + } while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd)); + + return src_id; + } + + DesktopCapturer::SourceId GetScreenIdFromSourceList() { + DesktopCapturer::SourceList sources; + EXPECT_TRUE(capturer_->GetSourceList(&sources)); + EXPECT_GT(sources.size(), 0ULL); + return sources[0].id; + } + + void DoCapture(int num_captures = 1) { + // Capture the requested number of frames. We expect the first capture to + // always succeed. If we're asked for multiple frames, we do expect to see a + // a couple dropped frames due to resizing the window. + const int max_tries = num_captures == 1 ? 1 : kMaxTries; + int success_count = 0; + for (int i = 0; success_count < num_captures && i < max_tries; i++) { + capturer_->CaptureFrame(); + if (result_ == DesktopCapturer::Result::ERROR_PERMANENT) + break; + if (result_ == DesktopCapturer::Result::SUCCESS) + success_count++; + } + + total_successful_captures_ += success_count; + EXPECT_EQ(success_count, num_captures); + EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS); + EXPECT_TRUE(frame_); + EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSuccess), + total_successful_captures_); + } + + void ValidateFrame(int expected_width, int expected_height) { + EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend); + EXPECT_EQ(frame_->size().height(), + expected_height - kWindowHeightSubtrahend); + + // Verify the buffer contains as much data as it should. + int data_length = frame_->stride() * frame_->size().height(); + + // The first and last pixel should have the same color because they will be + // from the border of the window. + // Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit. + uint32_t first_pixel = static_cast<uint32_t>(*frame_->data()); + uint32_t last_pixel = static_cast<uint32_t>( + *(frame_->data() + data_length - DesktopFrame::kBytesPerPixel)); + EXPECT_EQ(first_pixel, last_pixel); + + // Let's also check a pixel from the middle of the content area, which the + // test window will paint a consistent color for us to verify. + uint8_t* middle_pixel = frame_->data() + (data_length / 2); + + int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4; + EXPECT_EQ(*middle_pixel, kTestWindowBValue); + middle_pixel += sub_pixel_offset; + EXPECT_EQ(*middle_pixel, kTestWindowGValue); + middle_pixel += sub_pixel_offset; + EXPECT_EQ(*middle_pixel, kTestWindowRValue); + middle_pixel += sub_pixel_offset; + + // The window is opaque so we expect 0xFF for the Alpha channel. + EXPECT_EQ(*middle_pixel, 0xFF); + } + + // DesktopCapturer::Callback interface + // The capturer synchronously invokes this method before `CaptureFrame()` + // returns. + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override { + result_ = result; + frame_ = std::move(frame); + } + + protected: + std::unique_ptr<ScopedCOMInitializer> com_initializer_; + DWORD window_thread_id_; + std::unique_ptr<rtc::Thread> window_thread_; + WindowInfo window_info_; + intptr_t source_id_; + bool window_open_ = false; + DesktopCapturer::Result result_; + int total_successful_captures_ = 0; + std::unique_ptr<DesktopFrame> frame_; + std::unique_ptr<DesktopCapturer> capturer_; +}; + +TEST_P(WgcCapturerWinTest, SelectValidSource) { + if (GetParam() == CaptureType::kWindow) { + SetUpForWindowCapture(); + } else { + SetUpForScreenCapture(); + } + + EXPECT_TRUE(capturer_->SelectSource(source_id_)); +} + +TEST_P(WgcCapturerWinTest, SelectInvalidSource) { + if (GetParam() == CaptureType::kWindow) { + capturer_ = WgcCapturerWin::CreateRawWindowCapturer( + DesktopCaptureOptions::CreateDefault()); + source_id_ = kNullWindowId; + } else { + capturer_ = WgcCapturerWin::CreateRawScreenCapturer( + DesktopCaptureOptions::CreateDefault()); + source_id_ = kInvalidScreenId; + } + + EXPECT_FALSE(capturer_->SelectSource(source_id_)); +} + +TEST_P(WgcCapturerWinTest, Capture) { + if (GetParam() == CaptureType::kWindow) { + SetUpForWindowCapture(); + } else { + SetUpForScreenCapture(); + } + + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + capturer_->Start(this); + EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram, + DesktopCapturerId::kWgcCapturerWin), + 1); + + DoCapture(); + EXPECT_GT(frame_->size().width(), 0); + EXPECT_GT(frame_->size().height(), 0); +} + +TEST_P(WgcCapturerWinTest, CaptureTime) { + if (GetParam() == CaptureType::kWindow) { + SetUpForWindowCapture(); + } else { + SetUpForScreenCapture(); + } + + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + capturer_->Start(this); + + int64_t start_time; + start_time = rtc::TimeNanos(); + capturer_->CaptureFrame(); + + int capture_time_ms = + (rtc::TimeNanos() - start_time) / rtc::kNumNanosecsPerMillisec; + EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS); + EXPECT_TRUE(frame_); + + // The test may measure the time slightly differently than the capturer. So we + // just check if it's within 5 ms. + EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5); + EXPECT_GE( + metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1); +} + +INSTANTIATE_TEST_SUITE_P(SourceAgnostic, + WgcCapturerWinTest, + ::testing::Values(CaptureType::kWindow, + CaptureType::kScreen)); + +TEST(WgcCapturerNoMonitorTest, NoMonitors) { + ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer.Succeeded()); + if (HasActiveDisplay()) { + RTC_LOG(LS_INFO) << "Skip WgcCapturerWinTest designed specifically for " + "systems with no monitors"; + GTEST_SKIP(); + } + + // A bug in `CreateForMonitor` prevents screen capture when no displays are + // attached. + EXPECT_FALSE(IsWgcSupported(CaptureType::kScreen)); + + // A bug in the DWM (Desktop Window Manager) prevents it from providing image + // data if there are no displays attached. This was fixed in Windows 11. + if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11) + EXPECT_FALSE(IsWgcSupported(CaptureType::kWindow)); + else + EXPECT_TRUE(IsWgcSupported(CaptureType::kWindow)); +} + +class WgcCapturerMonitorTest : public WgcCapturerWinTest { + public: + void SetUp() { + com_initializer_ = + std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer_->Succeeded()); + + if (!IsWgcSupported(CaptureType::kScreen)) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + } +}; + +TEST_F(WgcCapturerMonitorTest, FocusOnMonitor) { + SetUpForScreenCapture(); + EXPECT_TRUE(capturer_->SelectSource(0)); + + // You can't set focus on a monitor. + EXPECT_FALSE(capturer_->FocusOnSelectedSource()); +} + +TEST_F(WgcCapturerMonitorTest, CaptureAllMonitors) { + SetUpForScreenCapture(); + EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId)); + + capturer_->Start(this); + DoCapture(); + EXPECT_GT(frame_->size().width(), 0); + EXPECT_GT(frame_->size().height(), 0); +} + +class WgcCapturerWindowTest : public WgcCapturerWinTest { + public: + void SetUp() { + com_initializer_ = + std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); + EXPECT_TRUE(com_initializer_->Succeeded()); + + if (!IsWgcSupported(CaptureType::kWindow)) { + RTC_LOG(LS_INFO) + << "Skipping WgcCapturerWinTests on unsupported platforms."; + GTEST_SKIP(); + } + } +}; + +TEST_F(WgcCapturerWindowTest, FocusOnWindow) { + capturer_ = WgcCapturerWin::CreateRawWindowCapturer( + DesktopCaptureOptions::CreateDefault()); + window_info_ = CreateTestWindow(kWindowTitle); + source_id_ = GetScreenIdFromSourceList(); + + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + EXPECT_TRUE(capturer_->FocusOnSelectedSource()); + + HWND hwnd = reinterpret_cast<HWND>(source_id_); + EXPECT_EQ(hwnd, ::GetActiveWindow()); + EXPECT_EQ(hwnd, ::GetForegroundWindow()); + EXPECT_EQ(hwnd, ::GetFocus()); + DestroyTestWindow(window_info_); +} + +TEST_F(WgcCapturerWindowTest, SelectMinimizedWindow) { + SetUpForWindowCapture(); + MinimizeTestWindow(reinterpret_cast<HWND>(source_id_)); + EXPECT_FALSE(capturer_->SelectSource(source_id_)); + + UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_)); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); +} + +TEST_F(WgcCapturerWindowTest, SelectClosedWindow) { + SetUpForWindowCapture(); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + CloseTestWindow(); + EXPECT_FALSE(capturer_->SelectSource(source_id_)); +} + +TEST_F(WgcCapturerWindowTest, UnsupportedWindowStyle) { + // Create a window with the WS_EX_TOOLWINDOW style, which WGC does not + // support. + window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth, + kMediumWindowHeight, WS_EX_TOOLWINDOW); + capturer_ = WgcCapturerWin::CreateRawWindowCapturer( + DesktopCaptureOptions::CreateDefault()); + DesktopCapturer::SourceList sources; + EXPECT_TRUE(capturer_->GetSourceList(&sources)); + auto it = std::find_if( + sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) { + return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd); + }); + + // We should not find the window, since we filter for unsupported styles. + EXPECT_EQ(it, sources.end()); + DestroyTestWindow(window_info_); +} + +TEST_F(WgcCapturerWindowTest, IncreaseWindowSizeMidCapture) { + SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + capturer_->Start(this); + DoCapture(); + ValidateFrame(kSmallWindowWidth, kSmallWindowHeight); + + ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight); + DoCapture(kNumCapturesToFlushBuffers); + ValidateFrame(kSmallWindowWidth, kMediumWindowHeight); + + ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight); + DoCapture(kNumCapturesToFlushBuffers); + ValidateFrame(kLargeWindowWidth, kMediumWindowHeight); +} + +TEST_F(WgcCapturerWindowTest, ReduceWindowSizeMidCapture) { + SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + capturer_->Start(this); + DoCapture(); + ValidateFrame(kLargeWindowWidth, kLargeWindowHeight); + + ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight); + DoCapture(kNumCapturesToFlushBuffers); + ValidateFrame(kLargeWindowWidth, kMediumWindowHeight); + + ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight); + DoCapture(kNumCapturesToFlushBuffers); + ValidateFrame(kSmallWindowWidth, kMediumWindowHeight); +} + +TEST_F(WgcCapturerWindowTest, MinimizeWindowMidCapture) { + SetUpForWindowCapture(); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + capturer_->Start(this); + + // Minmize the window and capture should continue but return temporary errors. + MinimizeTestWindow(window_info_.hwnd); + for (int i = 0; i < 5; ++i) { + capturer_->CaptureFrame(); + EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY); + } + + // Reopen the window and the capture should continue normally. + UnminimizeTestWindow(window_info_.hwnd); + DoCapture(); + // We can't verify the window size here because the test window does not + // repaint itself after it is unminimized, but capturing successfully is still + // a good test. +} + +TEST_F(WgcCapturerWindowTest, CloseWindowMidCapture) { + SetUpForWindowCapture(); + EXPECT_TRUE(capturer_->SelectSource(source_id_)); + + capturer_->Start(this); + DoCapture(); + ValidateFrame(kMediumWindowWidth, kMediumWindowHeight); + + CloseTestWindow(); + + // We need to pump our message queue so the Closed event will be delivered to + // the capturer's event handler. If we are too early and the Closed event + // hasn't arrived yet we should keep trying until the capturer receives it and + // stops. + auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get()); + MSG msg; + for (int i = 0; + wgc_capturer->IsSourceBeingCaptured(source_id_) && i < kMaxTries; ++i) { + // Unlike GetMessage, PeekMessage will not hang if there are no messages in + // the queue. + PeekMessage(&msg, 0, 0, 0, PM_REMOVE); + SleepMs(1); + } + + EXPECT_FALSE(wgc_capturer->IsSourceBeingCaptured(source_id_)); + + // The frame pool can buffer `kNumBuffers` frames. We must consume these + // and then make one more call to CaptureFrame before we expect to see the + // failure. + int num_tries = 0; + do { + capturer_->CaptureFrame(); + } while (result_ == DesktopCapturer::Result::SUCCESS && + ++num_tries <= WgcCaptureSession::kNumBuffers); + + EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure), + 1); + EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed), + 1); + EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc new file mode 100644 index 0000000000..dd9009120b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.cc @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/wgc_desktop_frame.h" + +#include <utility> + +namespace webrtc { + +WgcDesktopFrame::WgcDesktopFrame(DesktopSize size, + int stride, + std::vector<uint8_t>&& image_data) + : DesktopFrame(size, stride, image_data.data(), nullptr), + image_data_(std::move(image_data)) {} + +WgcDesktopFrame::~WgcDesktopFrame() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h new file mode 100644 index 0000000000..0a671cf2f8 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_desktop_frame.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_ + +#include <d3d11.h> +#include <wrl/client.h> + +#include <memory> +#include <vector> + +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// DesktopFrame implementation used by capturers that use the +// Windows.Graphics.Capture API. +class WgcDesktopFrame final : public DesktopFrame { + public: + // WgcDesktopFrame receives an rvalue reference to the `image_data` vector + // so that it can take ownership of it (and avoid a copy). + WgcDesktopFrame(DesktopSize size, + int stride, + std::vector<uint8_t>&& image_data); + + WgcDesktopFrame(const WgcDesktopFrame&) = delete; + WgcDesktopFrame& operator=(const WgcDesktopFrame&) = delete; + + ~WgcDesktopFrame() override; + + private: + std::vector<uint8_t> image_data_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc new file mode 100644 index 0000000000..d58c02e17c --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.cc @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/window_capture_utils.h" + +// Just for the DWMWINDOWATTRIBUTE enums (DWMWA_CLOAKED). +#include <dwmapi.h> + +#include <algorithm> + +#include "modules/desktop_capture/win/scoped_gdi_object.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/win/windows_version.h" + +namespace webrtc { + +namespace { + +struct GetWindowListParams { + GetWindowListParams(int flags, + LONG ex_style_filters, + DesktopCapturer::SourceList* result) + : ignore_untitled(flags & GetWindowListFlags::kIgnoreUntitled), + ignore_unresponsive(flags & GetWindowListFlags::kIgnoreUnresponsive), + ignore_current_process_windows( + flags & GetWindowListFlags::kIgnoreCurrentProcessWindows), + ex_style_filters(ex_style_filters), + result(result) {} + const bool ignore_untitled; + const bool ignore_unresponsive; + const bool ignore_current_process_windows; + const LONG ex_style_filters; + DesktopCapturer::SourceList* const result; +}; + +bool IsWindowOwnedByCurrentProcess(HWND hwnd) { + DWORD process_id; + GetWindowThreadProcessId(hwnd, &process_id); + return process_id == GetCurrentProcessId(); +} + +BOOL CALLBACK GetWindowListHandler(HWND hwnd, LPARAM param) { + GetWindowListParams* params = reinterpret_cast<GetWindowListParams*>(param); + DesktopCapturer::SourceList* list = params->result; + + // Skip invisible and minimized windows + if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) { + return TRUE; + } + + // Skip windows which are not presented in the taskbar, + // namely owned window if they don't have the app window style set + HWND owner = GetWindow(hwnd, GW_OWNER); + LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (owner && !(exstyle & WS_EX_APPWINDOW)) { + return TRUE; + } + + // Filter out windows that match the extended styles the caller has specified, + // e.g. WS_EX_TOOLWINDOW for capturers that don't support overlay windows. + if (exstyle & params->ex_style_filters) { + return TRUE; + } + + if (params->ignore_unresponsive && !IsWindowResponding(hwnd)) { + return TRUE; + } + + DesktopCapturer::Source window; + window.id = reinterpret_cast<WindowId>(hwnd); + + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + window.pid = static_cast<pid_t>(pid); + + // GetWindowText* are potentially blocking operations if `hwnd` is + // owned by the current process. The APIs will send messages to the window's + // message loop, and if the message loop is waiting on this operation we will + // enter a deadlock. + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtexta#remarks + // + // To help consumers avoid this, there is a DesktopCaptureOption to ignore + // windows owned by the current process. Consumers should either ensure that + // the thread running their message loop never waits on this operation, or use + // the option to exclude these windows from the source list. + bool owned_by_current_process = IsWindowOwnedByCurrentProcess(hwnd); + if (owned_by_current_process && params->ignore_current_process_windows) { + return TRUE; + } + + // Even if consumers request to enumerate windows owned by the current + // process, we should not call GetWindowText* on unresponsive windows owned by + // the current process because we will hang. Unfortunately, we could still + // hang if the window becomes unresponsive after this check, hence the option + // to avoid these completely. + if (!owned_by_current_process || IsWindowResponding(hwnd)) { + const size_t kTitleLength = 500; + WCHAR window_title[kTitleLength] = L""; + if (GetWindowTextLength(hwnd) != 0 && + GetWindowTextW(hwnd, window_title, kTitleLength) > 0) { + window.title = rtc::ToUtf8(window_title); + } + } + + // Skip windows when we failed to convert the title or it is empty. + if (params->ignore_untitled && window.title.empty()) + return TRUE; + + // Capture the window class name, to allow specific window classes to be + // skipped. + // + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa + // says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't + // need to have a buffer bigger than that. + const size_t kMaxClassNameLength = 256; + WCHAR class_name[kMaxClassNameLength] = L""; + const int class_name_length = + GetClassNameW(hwnd, class_name, kMaxClassNameLength); + if (class_name_length < 1) + return TRUE; + + // Skip Program Manager window. + if (wcscmp(class_name, L"Progman") == 0) + return TRUE; + + // Skip Start button window on Windows Vista, Windows 7. + // On Windows 8, Windows 8.1, Windows 10 Start button is not a top level + // window, so it will not be examined here. + if (wcscmp(class_name, L"Button") == 0) + return TRUE; + + list->push_back(window); + + return TRUE; +} + +} // namespace + +// Prefix used to match the window class for Chrome windows. +const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_"; + +// The hiddgen taskbar will leave a 2 pixel margin on the screen. +const int kHiddenTaskbarMarginOnScreen = 2; + +bool GetWindowRect(HWND window, DesktopRect* result) { + RECT rect; + if (!::GetWindowRect(window, &rect)) { + return false; + } + *result = DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); + return true; +} + +bool GetCroppedWindowRect(HWND window, + bool avoid_cropping_border, + DesktopRect* cropped_rect, + DesktopRect* original_rect) { + DesktopRect window_rect; + if (!GetWindowRect(window, &window_rect)) { + return false; + } + + if (original_rect) { + *original_rect = window_rect; + } + *cropped_rect = window_rect; + + bool is_maximized = false; + if (!IsWindowMaximized(window, &is_maximized)) { + return false; + } + + // As of Windows8, transparent resize borders are added by the OS at + // left/bottom/right sides of a resizeable window. If the cropped window + // doesn't remove these borders, the background will be exposed a bit. + if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8 || + is_maximized) { + // Only apply this cropping to windows with a resize border (otherwise, + // it'd clip the edges of captured pop-up windows without this border). + LONG style = GetWindowLong(window, GWL_STYLE); + if (style & WS_THICKFRAME || style & DS_MODALFRAME) { + int width = GetSystemMetrics(SM_CXSIZEFRAME); + int bottom_height = GetSystemMetrics(SM_CYSIZEFRAME); + const int visible_border_height = GetSystemMetrics(SM_CYBORDER); + int top_height = visible_border_height; + + // If requested, avoid cropping the visible window border. This is used + // for pop-up windows to include their border, but not for the outermost + // window (where a partially-transparent border may expose the + // background a bit). + if (avoid_cropping_border) { + width = std::max(0, width - GetSystemMetrics(SM_CXBORDER)); + bottom_height = std::max(0, bottom_height - visible_border_height); + top_height = 0; + } + cropped_rect->Extend(-width, -top_height, -width, -bottom_height); + } + } + + return true; +} + +bool GetWindowContentRect(HWND window, DesktopRect* result) { + if (!GetWindowRect(window, result)) { + return false; + } + + RECT rect; + if (!::GetClientRect(window, &rect)) { + return false; + } + + const int width = rect.right - rect.left; + // The GetClientRect() is not expected to return a larger area than + // GetWindowRect(). + if (width > 0 && width < result->width()) { + // - GetClientRect() always set the left / top of RECT to 0. So we need to + // estimate the border width from GetClientRect() and GetWindowRect(). + // - Border width of a window varies according to the window type. + // - GetClientRect() excludes the title bar, which should be considered as + // part of the content and included in the captured frame. So we always + // estimate the border width according to the window width. + // - We assume a window has same border width in each side. + // So we shrink half of the width difference from all four sides. + const int shrink = ((width - result->width()) / 2); + // When `shrink` is negative, DesktopRect::Extend() shrinks itself. + result->Extend(shrink, 0, shrink, 0); + // Usually this should not happen, just in case we have received a strange + // window, which has only left and right borders. + if (result->height() > shrink * 2) { + result->Extend(0, shrink, 0, shrink); + } + RTC_DCHECK(!result->is_empty()); + } + + return true; +} + +int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result) { + win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN>> scoped_hrgn( + CreateRectRgn(0, 0, 0, 0)); + const int region_type = GetWindowRgn(window, scoped_hrgn.Get()); + + if (region_type == SIMPLEREGION) { + RECT rect; + GetRgnBox(scoped_hrgn.Get(), &rect); + *result = + DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); + } + return region_type; +} + +bool GetDcSize(HDC hdc, DesktopSize* size) { + win::ScopedGDIObject<HGDIOBJ, win::DeleteObjectTraits<HGDIOBJ>> scoped_hgdi( + GetCurrentObject(hdc, OBJ_BITMAP)); + BITMAP bitmap; + memset(&bitmap, 0, sizeof(BITMAP)); + if (GetObject(scoped_hgdi.Get(), sizeof(BITMAP), &bitmap) == 0) { + return false; + } + size->set(bitmap.bmWidth, bitmap.bmHeight); + return true; +} + +bool IsWindowMaximized(HWND window, bool* result) { + WINDOWPLACEMENT placement; + memset(&placement, 0, sizeof(WINDOWPLACEMENT)); + placement.length = sizeof(WINDOWPLACEMENT); + if (!::GetWindowPlacement(window, &placement)) { + return false; + } + + *result = (placement.showCmd == SW_SHOWMAXIMIZED); + return true; +} + +bool IsWindowValidAndVisible(HWND window) { + return IsWindow(window) && IsWindowVisible(window) && !IsIconic(window); +} + +bool IsWindowResponding(HWND window) { + // 50ms is chosen in case the system is under heavy load, but it's also not + // too long to delay window enumeration considerably. + const UINT uTimeoutMs = 50; + return SendMessageTimeout(window, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, uTimeoutMs, + nullptr); +} + +bool GetWindowList(int flags, + DesktopCapturer::SourceList* windows, + LONG ex_style_filters) { + GetWindowListParams params(flags, ex_style_filters, windows); + return ::EnumWindows(&GetWindowListHandler, + reinterpret_cast<LPARAM>(¶ms)) != 0; +} + +// WindowCaptureHelperWin implementation. +WindowCaptureHelperWin::WindowCaptureHelperWin() { + // Try to load dwmapi.dll dynamically since it is not available on XP. + dwmapi_library_ = LoadLibraryW(L"dwmapi.dll"); + if (dwmapi_library_) { + func_ = reinterpret_cast<DwmIsCompositionEnabledFunc>( + GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled")); + dwm_get_window_attribute_func_ = + reinterpret_cast<DwmGetWindowAttributeFunc>( + GetProcAddress(dwmapi_library_, "DwmGetWindowAttribute")); + } + + if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN10) { + if (FAILED(::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, + CLSCTX_ALL, + IID_PPV_ARGS(&virtual_desktop_manager_)))) { + RTC_LOG(LS_WARNING) << "Fail to create instance of VirtualDesktopManager"; + } + } +} + +WindowCaptureHelperWin::~WindowCaptureHelperWin() { + if (dwmapi_library_) { + FreeLibrary(dwmapi_library_); + } +} + +bool WindowCaptureHelperWin::IsAeroEnabled() { + BOOL result = FALSE; + if (func_) { + func_(&result); + } + return result != FALSE; +} + +// This is just a best guess of a notification window. Chrome uses the Windows +// native framework for showing notifications. So far what we know about such a +// window includes: no title, class name with prefix "Chrome_WidgetWin_" and +// with certain extended styles. +bool WindowCaptureHelperWin::IsWindowChromeNotification(HWND hwnd) { + const size_t kTitleLength = 32; + WCHAR window_title[kTitleLength]; + GetWindowTextW(hwnd, window_title, kTitleLength); + if (wcsnlen_s(window_title, kTitleLength) != 0) { + return false; + } + + const size_t kClassLength = 256; + WCHAR class_name[kClassLength]; + const int class_name_length = GetClassNameW(hwnd, class_name, kClassLength); + if (class_name_length < 1 || + wcsncmp(class_name, kChromeWindowClassPrefix, + wcsnlen_s(kChromeWindowClassPrefix, kClassLength)) != 0) { + return false; + } + + const LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if ((exstyle & WS_EX_NOACTIVATE) && (exstyle & WS_EX_TOOLWINDOW) && + (exstyle & WS_EX_TOPMOST)) { + return true; + } + + return false; +} + +// `content_rect` is preferred because, +// 1. WindowCapturerWinGdi is using GDI capturer, which cannot capture DX +// output. +// So ScreenCapturer should be used as much as possible to avoid +// uncapturable cases. Note: lots of new applications are using DX output +// (hardware acceleration) to improve the performance which cannot be +// captured by WindowCapturerWinGdi. See bug http://crbug.com/741770. +// 2. WindowCapturerWinGdi is still useful because we do not want to expose the +// content on other windows if the target window is covered by them. +// 3. Shadow and borders should not be considered as "content" on other +// windows because they do not expose any useful information. +// +// So we can bear the false-negative cases (target window is covered by the +// borders or shadow of other windows, but we have not detected it) in favor +// of using ScreenCapturer, rather than let the false-positive cases (target +// windows is only covered by borders or shadow of other windows, but we treat +// it as overlapping) impact the user experience. +bool WindowCaptureHelperWin::AreWindowsOverlapping( + HWND hwnd, + HWND selected_hwnd, + const DesktopRect& selected_window_rect) { + DesktopRect content_rect; + if (!GetWindowContentRect(hwnd, &content_rect)) { + // Bail out if failed to get the window area. + return true; + } + content_rect.IntersectWith(selected_window_rect); + + if (content_rect.is_empty()) { + return false; + } + + // When the taskbar is automatically hidden, it will leave a 2 pixel margin on + // the screen which will overlap the maximized selected window that will use + // up the full screen area. Since there is no solid way to identify a hidden + // taskbar window, we have to make an exemption here if the overlapping is + // 2 x screen_width/height to a maximized window. + bool is_maximized = false; + IsWindowMaximized(selected_hwnd, &is_maximized); + bool overlaps_hidden_horizontal_taskbar = + selected_window_rect.width() == content_rect.width() && + content_rect.height() == kHiddenTaskbarMarginOnScreen; + bool overlaps_hidden_vertical_taskbar = + selected_window_rect.height() == content_rect.height() && + content_rect.width() == kHiddenTaskbarMarginOnScreen; + if (is_maximized && (overlaps_hidden_horizontal_taskbar || + overlaps_hidden_vertical_taskbar)) { + return false; + } + + return true; +} + +bool WindowCaptureHelperWin::IsWindowOnCurrentDesktop(HWND hwnd) { + // Make sure the window is on the current virtual desktop. + if (virtual_desktop_manager_) { + BOOL on_current_desktop; + if (SUCCEEDED(virtual_desktop_manager_->IsWindowOnCurrentVirtualDesktop( + hwnd, &on_current_desktop)) && + !on_current_desktop) { + return false; + } + } + return true; +} + +bool WindowCaptureHelperWin::IsWindowVisibleOnCurrentDesktop(HWND hwnd) { + return IsWindowValidAndVisible(hwnd) && IsWindowOnCurrentDesktop(hwnd) && + !IsWindowCloaked(hwnd); +} + +// A cloaked window is composited but not visible to the user. +// Example: Cortana or the Action Center when collapsed. +bool WindowCaptureHelperWin::IsWindowCloaked(HWND hwnd) { + if (!dwm_get_window_attribute_func_) { + // Does not apply. + return false; + } + + int res = 0; + if (dwm_get_window_attribute_func_(hwnd, DWMWA_CLOAKED, &res, sizeof(res)) != + S_OK) { + // Cannot tell so assume not cloaked for backward compatibility. + return false; + } + + return res != 0; +} + +bool WindowCaptureHelperWin::EnumerateCapturableWindows( + DesktopCapturer::SourceList* results, + bool enumerate_current_process_windows, + LONG ex_style_filters) { + int flags = (GetWindowListFlags::kIgnoreUntitled | + GetWindowListFlags::kIgnoreUnresponsive); + if (!enumerate_current_process_windows) { + flags |= GetWindowListFlags::kIgnoreCurrentProcessWindows; + } + + if (!webrtc::GetWindowList(flags, results, ex_style_filters)) { + return false; + } + + for (auto it = results->begin(); it != results->end();) { + if (!IsWindowVisibleOnCurrentDesktop(reinterpret_cast<HWND>(it->id))) { + it = results->erase(it); + } else { + ++it; + } + } + + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h new file mode 100644 index 0000000000..caea07958d --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_ + +#include <shlobj.h> +#include <windows.h> +#include <wrl/client.h> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Outputs the window rect. The returned DesktopRect is in system coordinates, +// i.e. the primary monitor on the system always starts from (0, 0). This +// function returns false if native APIs fail. +bool GetWindowRect(HWND window, DesktopRect* result); + +// Outputs the window rect, with the left/right/bottom frame border cropped if +// the window is maximized or has a transparent resize border. +// `avoid_cropping_border` may be set to true to avoid cropping the visible +// border when cropping any resize border. +// `cropped_rect` is the cropped rect relative to the +// desktop. `original_rect` is the original rect returned from GetWindowRect. +// Returns true if all API calls succeeded. The returned DesktopRect is in +// system coordinates, i.e. the primary monitor on the system always starts from +// (0, 0). `original_rect` can be nullptr. +// +// TODO(zijiehe): Move this function to CroppingWindowCapturerWin after it has +// been removed from MouseCursorMonitorWin. +// This function should only be used by CroppingWindowCapturerWin. Instead a +// DesktopRect CropWindowRect(const DesktopRect& rect) +// should be added as a utility function to help CroppingWindowCapturerWin and +// WindowCapturerWinGdi to crop out the borders or shadow according to their +// scenarios. But this function is too generic and easy to be misused. +bool GetCroppedWindowRect(HWND window, + bool avoid_cropping_border, + DesktopRect* cropped_rect, + DesktopRect* original_rect); + +// Retrieves the rectangle of the content area of `window`. Usually it contains +// title bar and window client area, but borders or shadow are excluded. The +// returned DesktopRect is in system coordinates, i.e. the primary monitor on +// the system always starts from (0, 0). This function returns false if native +// APIs fail. +bool GetWindowContentRect(HWND window, DesktopRect* result); + +// Returns the region type of the `window` and fill `rect` with the region of +// `window` if region type is SIMPLEREGION. +int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result); + +// Retrieves the size of the `hdc`. This function returns false if native APIs +// fail. +bool GetDcSize(HDC hdc, DesktopSize* size); + +// Retrieves whether the `window` is maximized and stores in `result`. This +// function returns false if native APIs fail. +bool IsWindowMaximized(HWND window, bool* result); + +// Checks that the HWND is for a valid window, that window's visibility state is +// visible, and that it is not minimized. +bool IsWindowValidAndVisible(HWND window); + +// Checks if a window responds to a message within 50ms. +bool IsWindowResponding(HWND window); + +enum GetWindowListFlags { + kNone = 0x00, + kIgnoreUntitled = 1 << 0, + kIgnoreUnresponsive = 1 << 1, + kIgnoreCurrentProcessWindows = 1 << 2, +}; + +// Retrieves the list of top-level windows on the screen. +// Some windows will be ignored: +// - Those that are invisible or minimized. +// - Program Manager & Start menu. +// - [with kIgnoreUntitled] windows with no title. +// - [with kIgnoreUnresponsive] windows that are unresponsive. +// - [with kIgnoreCurrentProcessWindows] windows owned by the current process. +// - Any windows with extended styles that match `ex_style_filters`. +// Returns false if native APIs failed. +bool GetWindowList(int flags, + DesktopCapturer::SourceList* windows, + LONG ex_style_filters = 0); + +typedef HRESULT(WINAPI* DwmIsCompositionEnabledFunc)(BOOL* enabled); +typedef HRESULT(WINAPI* DwmGetWindowAttributeFunc)(HWND hwnd, + DWORD flag, + PVOID result_ptr, + DWORD result_size); +class WindowCaptureHelperWin { + public: + WindowCaptureHelperWin(); + ~WindowCaptureHelperWin(); + + WindowCaptureHelperWin(const WindowCaptureHelperWin&) = delete; + WindowCaptureHelperWin& operator=(const WindowCaptureHelperWin&) = delete; + + bool IsAeroEnabled(); + bool IsWindowChromeNotification(HWND hwnd); + bool AreWindowsOverlapping(HWND hwnd, + HWND selected_hwnd, + const DesktopRect& selected_window_rect); + bool IsWindowOnCurrentDesktop(HWND hwnd); + bool IsWindowVisibleOnCurrentDesktop(HWND hwnd); + bool IsWindowCloaked(HWND hwnd); + + // The optional `ex_style_filters` parameter allows callers to provide + // extended window styles (e.g. WS_EX_TOOLWINDOW) and prevent windows that + // match from being included in `results`. + bool EnumerateCapturableWindows(DesktopCapturer::SourceList* results, + bool enumerate_current_process_windows, + LONG ex_style_filters = 0); + + private: + HMODULE dwmapi_library_ = nullptr; + DwmIsCompositionEnabledFunc func_ = nullptr; + DwmGetWindowAttributeFunc dwm_get_window_attribute_func_ = nullptr; + + // Only used on Win10+. + Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURE_UTILS_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc new file mode 100644 index 0000000000..137440b09e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capture_utils_unittest.cc @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/window_capture_utils.h" + +#include <winuser.h> + +#include <algorithm> +#include <memory> +#include <mutex> + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/test_support/test_window.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/thread.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +const char kWindowThreadName[] = "window_capture_utils_test_thread"; +const WCHAR kWindowTitle[] = L"Window Capture Utils Test"; + +std::unique_ptr<rtc::Thread> SetUpUnresponsiveWindow(std::mutex& mtx, + WindowInfo& info) { + std::unique_ptr<rtc::Thread> window_thread; + window_thread = rtc::Thread::Create(); + window_thread->SetName(kWindowThreadName, nullptr); + window_thread->Start(); + + SendTask(window_thread.get(), [&] { info = CreateTestWindow(kWindowTitle); }); + + // Intentionally create a deadlock to cause the window to become unresponsive. + mtx.lock(); + window_thread->PostTask([&mtx]() { + mtx.lock(); + mtx.unlock(); + }); + + return window_thread; +} + +} // namespace + +TEST(WindowCaptureUtilsTest, GetWindowList) { + WindowInfo info = CreateTestWindow(kWindowTitle); + DesktopCapturer::SourceList window_list; + ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list)); + EXPECT_GT(window_list.size(), 0ULL); + EXPECT_NE(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + DestroyTestWindow(info); +} + +TEST(WindowCaptureUtilsTest, IncludeUnresponsiveWindows) { + std::mutex mtx; + WindowInfo info; + std::unique_ptr<rtc::Thread> window_thread = + SetUpUnresponsiveWindow(mtx, info); + + EXPECT_FALSE(IsWindowResponding(info.hwnd)); + + DesktopCapturer::SourceList window_list; + ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list)); + EXPECT_GT(window_list.size(), 0ULL); + EXPECT_NE(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + + mtx.unlock(); + SendTask(window_thread.get(), [&info]() { DestroyTestWindow(info); }); + window_thread->Stop(); +} + +TEST(WindowCaptureUtilsTest, IgnoreUnresponsiveWindows) { + std::mutex mtx; + WindowInfo info; + std::unique_ptr<rtc::Thread> window_thread = + SetUpUnresponsiveWindow(mtx, info); + + EXPECT_FALSE(IsWindowResponding(info.hwnd)); + + DesktopCapturer::SourceList window_list; + ASSERT_TRUE( + GetWindowList(GetWindowListFlags::kIgnoreUnresponsive, &window_list)); + EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + + mtx.unlock(); + SendTask(window_thread.get(), [&info]() { DestroyTestWindow(info); }); + window_thread->Stop(); +} + +TEST(WindowCaptureUtilsTest, IncludeUntitledWindows) { + WindowInfo info = CreateTestWindow(L""); + DesktopCapturer::SourceList window_list; + ASSERT_TRUE(GetWindowList(GetWindowListFlags::kNone, &window_list)); + EXPECT_GT(window_list.size(), 0ULL); + EXPECT_NE(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + DestroyTestWindow(info); +} + +TEST(WindowCaptureUtilsTest, IgnoreUntitledWindows) { + WindowInfo info = CreateTestWindow(L""); + DesktopCapturer::SourceList window_list; + ASSERT_TRUE(GetWindowList(GetWindowListFlags::kIgnoreUntitled, &window_list)); + EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + DestroyTestWindow(info); +} + +TEST(WindowCaptureUtilsTest, IgnoreCurrentProcessWindows) { + WindowInfo info = CreateTestWindow(kWindowTitle); + DesktopCapturer::SourceList window_list; + ASSERT_TRUE(GetWindowList(GetWindowListFlags::kIgnoreCurrentProcessWindows, + &window_list)); + EXPECT_EQ(std::find_if(window_list.begin(), window_list.end(), + [&info](DesktopCapturer::Source window) { + return reinterpret_cast<HWND>(window.id) == + info.hwnd; + }), + window_list.end()); + DestroyTestWindow(info); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc new file mode 100644 index 0000000000..6fd3a4db6e --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.cc @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/window_capturer_win_gdi.h" + +#include <cmath> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "modules/desktop_capture/cropped_desktop_frame.h" +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame_win.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/selected_window_context.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "rtc_base/win/windows_version.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +// Used to pass input/output data during the EnumWindows call to collect +// owned/pop-up windows that should be captured. +struct OwnedWindowCollectorContext : public SelectedWindowContext { + OwnedWindowCollectorContext(HWND selected_window, + DesktopRect selected_window_rect, + WindowCaptureHelperWin* window_capture_helper, + std::vector<HWND>* owned_windows) + : SelectedWindowContext(selected_window, + selected_window_rect, + window_capture_helper), + owned_windows(owned_windows) {} + + std::vector<HWND>* owned_windows; +}; + +// Called via EnumWindows for each root window; adds owned/pop-up windows that +// should be captured to a vector it's passed. +BOOL CALLBACK OwnedWindowCollector(HWND hwnd, LPARAM param) { + OwnedWindowCollectorContext* context = + reinterpret_cast<OwnedWindowCollectorContext*>(param); + if (hwnd == context->selected_window()) { + // Windows are enumerated in top-down z-order, so we can stop enumerating + // upon reaching the selected window. + return FALSE; + } + + // Skip windows that aren't visible pop-up windows. + if (!(GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) || + !context->window_capture_helper()->IsWindowVisibleOnCurrentDesktop( + hwnd)) { + return TRUE; + } + + // Owned windows that intersect the selected window should be captured. + if (context->IsWindowOwnedBySelectedWindow(hwnd) && + context->IsWindowOverlappingSelectedWindow(hwnd)) { + // Skip windows that draw shadows around menus. These "SysShadow" windows + // would otherwise be captured as solid black bars with no transparency + // gradient (since this capturer doesn't detect / respect variations in the + // window alpha channel). Any other semi-transparent owned windows will be + // captured fully-opaque. This seems preferable to excluding them (at least + // when they have content aside from a solid fill color / visual adornment; + // e.g. some tooltips have the transparent style set). + if (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) { + const WCHAR kSysShadow[] = L"SysShadow"; + const size_t kClassLength = arraysize(kSysShadow); + WCHAR class_name[kClassLength]; + const int class_name_length = + GetClassNameW(hwnd, class_name, kClassLength); + if (class_name_length == kClassLength - 1 && + wcscmp(class_name, kSysShadow) == 0) { + return TRUE; + } + } + + context->owned_windows->push_back(hwnd); + } + + return TRUE; +} + +WindowCapturerWinGdi::WindowCapturerWinGdi( + bool enumerate_current_process_windows) + : enumerate_current_process_windows_(enumerate_current_process_windows) {} +WindowCapturerWinGdi::~WindowCapturerWinGdi() {} + +bool WindowCapturerWinGdi::GetSourceList(SourceList* sources) { + if (!window_capture_helper_.EnumerateCapturableWindows( + sources, enumerate_current_process_windows_)) + return false; + + std::map<HWND, DesktopSize> new_map; + for (const auto& item : *sources) { + HWND hwnd = reinterpret_cast<HWND>(item.id); + new_map[hwnd] = window_size_map_[hwnd]; + } + window_size_map_.swap(new_map); + + return true; +} + +bool WindowCapturerWinGdi::SelectSource(SourceId id) { + HWND window = reinterpret_cast<HWND>(id); + if (!IsWindowValidAndVisible(window)) + return false; + + window_ = window; + // When a window is not in the map, window_size_map_[window] will create an + // item with DesktopSize (0, 0). + previous_size_ = window_size_map_[window]; + return true; +} + +bool WindowCapturerWinGdi::FocusOnSelectedSource() { + if (!window_) + return false; + + if (!IsWindowValidAndVisible(window_)) + return false; + + return BringWindowToTop(window_) && SetForegroundWindow(window_); +} + +bool WindowCapturerWinGdi::IsOccluded(const DesktopVector& pos) { + DesktopVector sys_pos = pos.add(GetFullscreenRect().top_left()); + HWND hwnd = + reinterpret_cast<HWND>(window_finder_.GetWindowUnderPoint(sys_pos)); + + return hwnd != window_ && + std::find(owned_windows_.begin(), owned_windows_.end(), hwnd) == + owned_windows_.end(); +} + +void WindowCapturerWinGdi::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kWindowCapturerWinGdi); + + callback_ = callback; +} + +void WindowCapturerWinGdi::CaptureFrame() { + RTC_DCHECK(callback_); + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + CaptureResults results = CaptureFrame(/*capture_owned_windows*/ true); + if (!results.frame) { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000( + "WebRTC.DesktopCapture.Win.WindowGdiCapturerFrameTime", capture_time_ms); + results.frame->set_capture_time_ms(capture_time_ms); + results.frame->set_capturer_id(DesktopCapturerId::kWindowCapturerWinGdi); + callback_->OnCaptureResult(results.result, std::move(results.frame)); +} + +WindowCapturerWinGdi::CaptureResults WindowCapturerWinGdi::CaptureFrame( + bool capture_owned_windows) { + TRACE_EVENT0("webrtc", "WindowCapturerWinGdi::CaptureFrame"); + + if (!window_) { + RTC_LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); + return {Result::ERROR_PERMANENT, nullptr}; + } + + // Stop capturing if the window has been closed. + if (!IsWindow(window_)) { + RTC_LOG(LS_ERROR) << "Target window has been closed."; + return {Result::ERROR_PERMANENT, nullptr}; + } + + // Determine the window region excluding any resize border, and including + // any visible border if capturing an owned window / dialog. (Don't include + // any visible border for the selected window for consistency with + // CroppingWindowCapturerWin, which would expose a bit of the background + // through the partially-transparent border.) + const bool avoid_cropping_border = !capture_owned_windows; + DesktopRect cropped_rect; + DesktopRect original_rect; + + if (!GetCroppedWindowRect(window_, avoid_cropping_border, &cropped_rect, + &original_rect)) { + RTC_LOG(LS_WARNING) << "Failed to get drawable window area: " + << GetLastError(); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + // Return a 1x1 black frame if the window is minimized or invisible on current + // desktop, to match behavior on mace. Window can be temporarily invisible + // during the transition of full screen mode on/off. + if (original_rect.is_empty() || + !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) { + std::unique_ptr<DesktopFrame> frame( + new BasicDesktopFrame(DesktopSize(1, 1))); + + previous_size_ = frame->size(); + window_size_map_[window_] = previous_size_; + return {Result::SUCCESS, std::move(frame)}; + } + + HDC window_dc = GetWindowDC(window_); + if (!window_dc) { + RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError(); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + DesktopRect unscaled_cropped_rect = cropped_rect; + double horizontal_scale = 1.0; + double vertical_scale = 1.0; + + DesktopSize window_dc_size; + if (GetDcSize(window_dc, &window_dc_size)) { + // The `window_dc_size` is used to detect the scaling of the original + // window. If the application does not support high-DPI settings, it will + // be scaled by Windows according to the scaling setting. + // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8 + // So the size of the `window_dc`, i.e. the bitmap we can retrieve from + // PrintWindow() or BitBlt() function, will be smaller than + // `original_rect` and `cropped_rect`. Part of the captured desktop frame + // will be black. See + // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for + // details. + + // If `window_dc_size` is smaller than `window_rect`, let's resize both + // `original_rect` and `cropped_rect` according to the scaling factor. + // This will adjust the width and height of the two rects. + horizontal_scale = + static_cast<double>(window_dc_size.width()) / original_rect.width(); + vertical_scale = + static_cast<double>(window_dc_size.height()) / original_rect.height(); + original_rect.Scale(horizontal_scale, vertical_scale); + cropped_rect.Scale(horizontal_scale, vertical_scale); + + // Translate `cropped_rect` to the left so that its position within + // `original_rect` remains accurate after scaling. + // See crbug.com/1083527 for more info. + int translate_left = static_cast<int>(std::round( + (cropped_rect.left() - original_rect.left()) * (horizontal_scale - 1))); + int translate_top = static_cast<int>(std::round( + (cropped_rect.top() - original_rect.top()) * (vertical_scale - 1))); + cropped_rect.Translate(translate_left, translate_top); + } + + std::unique_ptr<DesktopFrameWin> frame( + DesktopFrameWin::Create(original_rect.size(), nullptr, window_dc)); + if (!frame.get()) { + RTC_LOG(LS_WARNING) << "Failed to create frame."; + ReleaseDC(window_, window_dc); + return {Result::ERROR_TEMPORARY, nullptr}; + } + + HDC mem_dc = CreateCompatibleDC(window_dc); + HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap()); + BOOL result = FALSE; + + // When desktop composition (Aero) is enabled each window is rendered to a + // private buffer allowing BitBlt() to get the window content even if the + // window is occluded. PrintWindow() is slower but lets rendering the window + // contents to an off-screen device context when Aero is not available. + // PrintWindow() is not supported by some applications. + // + // If Aero is enabled, we prefer BitBlt() because it's faster and avoids + // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may + // render occluding windows on top of the desired window. + // + // When composition is enabled the DC returned by GetWindowDC() doesn't always + // have window frame rendered correctly. Windows renders it only once and then + // caches the result between captures. We hack it around by calling + // PrintWindow() whenever window size changes, including the first time of + // capturing - it somehow affects what we get from BitBlt() on the subsequent + // captures. + // + // For Windows 8.1 and later, we want to always use PrintWindow when the + // cropping screen capturer falls back to the window capturer. I.e. + // on Windows 8.1 and later, PrintWindow is only used when the window is + // occluded. When the window is not occluded, it is much faster to capture + // the screen and to crop it to the window position and size. + if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) { + // Special flag that makes PrintWindow to work on Windows 8.1 and later. + // Indeed certain apps (e.g. those using DirectComposition rendering) can't + // be captured using BitBlt or PrintWindow without this flag. Note that on + // Windows 8.0 this flag is not supported so the block below will fallback + // to the other call to PrintWindow. It seems to be very tricky to detect + // Windows 8.0 vs 8.1 so a try/fallback is more approriate here. + const UINT flags = PW_RENDERFULLCONTENT; + result = PrintWindow(window_, mem_dc, flags); + } + + if (!result && (!window_capture_helper_.IsAeroEnabled() || + !previous_size_.equals(frame->size()))) { + result = PrintWindow(window_, mem_dc, 0); + } + + // Aero is enabled or PrintWindow() failed, use BitBlt. + if (!result) { + result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(), + window_dc, 0, 0, SRCCOPY); + } + + SelectObject(mem_dc, previous_object); + DeleteDC(mem_dc); + ReleaseDC(window_, window_dc); + + previous_size_ = frame->size(); + window_size_map_[window_] = previous_size_; + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + frame->set_top_left( + original_rect.top_left().subtract(GetFullscreenRect().top_left())); + + if (!result) { + RTC_LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed."; + return {Result::ERROR_TEMPORARY, nullptr}; + } + + // Rect for the data is relative to the first pixel of the frame. + cropped_rect.Translate(-original_rect.left(), -original_rect.top()); + std::unique_ptr<DesktopFrame> cropped_frame = + CreateCroppedDesktopFrame(std::move(frame), cropped_rect); + RTC_DCHECK(cropped_frame); + + if (capture_owned_windows) { + // If any owned/pop-up windows overlap the selected window, capture them + // and copy/composite their contents into the frame. + owned_windows_.clear(); + OwnedWindowCollectorContext context(window_, unscaled_cropped_rect, + &window_capture_helper_, + &owned_windows_); + + if (context.IsSelectedWindowValid()) { + EnumWindows(OwnedWindowCollector, reinterpret_cast<LPARAM>(&context)); + + if (!owned_windows_.empty()) { + if (!owned_window_capturer_) { + owned_window_capturer_ = std::make_unique<WindowCapturerWinGdi>( + enumerate_current_process_windows_); + } + + // Owned windows are stored in top-down z-order, so this iterates in + // reverse to capture / draw them in bottom-up z-order + for (auto it = owned_windows_.rbegin(); it != owned_windows_.rend(); + it++) { + HWND hwnd = *it; + if (owned_window_capturer_->SelectSource( + reinterpret_cast<SourceId>(hwnd))) { + CaptureResults results = owned_window_capturer_->CaptureFrame( + /*capture_owned_windows*/ false); + + if (results.result != DesktopCapturer::Result::SUCCESS) { + // Simply log any error capturing an owned/pop-up window without + // bubbling it up to the caller (an expected error here is that + // the owned/pop-up window was closed; any unexpected errors won't + // fail the outer capture). + RTC_LOG(LS_INFO) << "Capturing owned window failed (previous " + "error/warning pertained to that)"; + } else { + // Copy / composite the captured frame into the outer frame. This + // may no-op if they no longer intersect (if the owned window was + // moved outside the owner bounds since scheduled for capture.) + cropped_frame->CopyIntersectingPixelsFrom( + *results.frame, horizontal_scale, vertical_scale); + } + } + } + } + } + } + + return {Result::SUCCESS, std::move(cropped_frame)}; +} + +// static +std::unique_ptr<DesktopCapturer> WindowCapturerWinGdi::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + return std::unique_ptr<DesktopCapturer>( + new WindowCapturerWinGdi(options.enumerate_current_process_windows())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h new file mode 100644 index 0000000000..bf94dfe192 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/window_capturer_win_gdi.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ +#define MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ + +#include <map> +#include <memory> +#include <vector> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "modules/desktop_capture/window_finder_win.h" + +namespace webrtc { + +class WindowCapturerWinGdi : public DesktopCapturer { + public: + explicit WindowCapturerWinGdi(bool enumerate_current_process_windows); + + // Disallow copy and assign + WindowCapturerWinGdi(const WindowCapturerWinGdi&) = delete; + WindowCapturerWinGdi& operator=(const WindowCapturerWinGdi&) = delete; + + ~WindowCapturerWinGdi() override; + + static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + private: + struct CaptureResults { + Result result; + std::unique_ptr<DesktopFrame> frame; + }; + + CaptureResults CaptureFrame(bool capture_owned_windows); + + Callback* callback_ = nullptr; + + // HWND and HDC for the currently selected window or nullptr if window is not + // selected. + HWND window_ = nullptr; + + DesktopSize previous_size_; + + WindowCaptureHelperWin window_capture_helper_; + + bool enumerate_current_process_windows_; + + // This map is used to avoid flickering for the case when SelectWindow() calls + // are interleaved with Capture() calls. + std::map<HWND, DesktopSize> window_size_map_; + + WindowFinderWin window_finder_; + + std::vector<HWND> owned_windows_; + std::unique_ptr<WindowCapturerWinGdi> owned_window_capturer_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WIN_WINDOW_CAPTURER_WIN_GDI_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/window_capturer_linux.cc b/third_party/libwebrtc/modules/desktop_capture/window_capturer_linux.cc new file mode 100644 index 0000000000..b2b1e376ad --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_capturer_linux.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" + +#if defined(WEBRTC_USE_PIPEWIRE) +#if defined(WEBRTC_MOZILLA_BUILD) +#include "modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.h" +#else +#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h" +#endif +#endif // defined(WEBRTC_USE_PIPEWIRE) + +#if defined(WEBRTC_USE_X11) +#include "modules/desktop_capture/linux/x11/window_capturer_x11.h" +#endif // defined(WEBRTC_USE_X11) + +namespace webrtc { + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { +#if defined(WEBRTC_USE_PIPEWIRE) + if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) { +#if defined(WEBRTC_MOZILLA_BUILD) + return BaseCapturerPipeWire::CreateRawCapturer(options); +#else + return std::make_unique<BaseCapturerPipeWire>(options); +#endif + } +#endif // defined(WEBRTC_USE_PIPEWIRE) + +#if defined(WEBRTC_USE_X11) + return WindowCapturerX11::CreateRawWindowCapturer(options); +#else + return nullptr; +#endif // defined(WEBRTC_USE_X11) +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_capturer_mac.mm b/third_party/libwebrtc/modules/desktop_capture/window_capturer_mac.mm new file mode 100644 index 0000000000..882498bc34 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_capturer_mac.mm @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> + +#include <utility> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" +#include "modules/desktop_capture/mac/window_list_utils.h" +#include "modules/desktop_capture/window_finder_mac.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +namespace { + +// Returns true if the window exists. +bool IsWindowValid(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(nullptr, reinterpret_cast<const void**>(&id), 1, nullptr); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + bool valid = window_array && CFArrayGetCount(window_array); + CFRelease(window_id_array); + CFRelease(window_array); + + return valid; +} + +class WindowCapturerMac : public DesktopCapturer { + public: + explicit WindowCapturerMac( + rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector, + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor); + ~WindowCapturerMac() override; + + WindowCapturerMac(const WindowCapturerMac&) = delete; + WindowCapturerMac& operator=(const WindowCapturerMac&) = delete; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + bool IsOccluded(const DesktopVector& pos) override; + + private: + Callback* callback_ = nullptr; + + // The window being captured. + CGWindowID window_id_ = 0; + + rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_; + + const rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; + + WindowFinderMac window_finder_; +}; + +WindowCapturerMac::WindowCapturerMac( + rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector, + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor) + : full_screen_window_detector_(std::move(full_screen_window_detector)), + configuration_monitor_(std::move(configuration_monitor)), + window_finder_(configuration_monitor_) {} + +WindowCapturerMac::~WindowCapturerMac() {} + +bool WindowCapturerMac::GetSourceList(SourceList* sources) { + return webrtc::GetWindowList(sources, true, true); +} + +bool WindowCapturerMac::SelectSource(SourceId id) { + if (!IsWindowValid(id)) + return false; + window_id_ = id; + return true; +} + +bool WindowCapturerMac::FocusOnSelectedSource() { + if (!window_id_) + return false; + + CGWindowID ids[1]; + ids[0] = window_id_; + CFArrayRef window_id_array = + CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); + + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + if (!window_array || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + RTC_LOG(LS_INFO) << "Window not found"; + CFRelease(window_id_array); + return false; + } + + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0)); + CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + + int pid; + CFNumberGetValue(pid_ref, kCFNumberIntType, &pid); + + // TODO(jiayl): this will bring the process main window to the front. We + // should find a way to bring only the window to the front. + bool result = + [[NSRunningApplication runningApplicationWithProcessIdentifier: pid] + activateWithOptions: NSApplicationActivateIgnoringOtherApps]; + + CFRelease(window_id_array); + CFRelease(window_array); + return result; +} + +bool WindowCapturerMac::IsOccluded(const DesktopVector& pos) { + DesktopVector sys_pos = pos; + if (configuration_monitor_) { + auto configuration = configuration_monitor_->desktop_configuration(); + sys_pos = pos.add(configuration.bounds.top_left()); + } + return window_finder_.GetWindowUnderPoint(sys_pos) != window_id_; +} + +void WindowCapturerMac::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void WindowCapturerMac::CaptureFrame() { + TRACE_EVENT0("webrtc", "WindowCapturerMac::CaptureFrame"); + + if (!IsWindowValid(window_id_)) { + RTC_LOG(LS_ERROR) << "The window is not valid any longer."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + CGWindowID on_screen_window = window_id_; + if (full_screen_window_detector_) { + full_screen_window_detector_->UpdateWindowListIfNeeded( + window_id_, [](DesktopCapturer::SourceList* sources) { + // Not using webrtc::GetWindowList(sources, true, false) + // as it doesn't allow to have in the result window with + // empty title along with titled window owned by the same pid. + return webrtc::GetWindowList( + [sources](CFDictionaryRef window) { + WindowId window_id = GetWindowId(window); + int pid = GetWindowOwnerPid(window); + if (window_id != kNullWindowId) { + sources->push_back(DesktopCapturer::Source{window_id, pid, GetWindowTitle(window)}); + } + return true; + }, + true, + false); + }); + + CGWindowID full_screen_window = full_screen_window_detector_->FindFullScreenWindow(window_id_); + + if (full_screen_window != kCGNullWindowID) on_screen_window = full_screen_window; + } + + std::unique_ptr<DesktopFrame> frame = DesktopFrameCGImage::CreateForWindow(on_screen_window); + if (!frame) { + RTC_LOG(LS_WARNING) << "Temporarily failed to capture window."; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + frame->set_top_left(GetWindowBounds(on_screen_window).top_left()); + + float scale_factor = GetWindowScaleFactor(window_id_, frame->size()); + frame->set_dpi(DesktopVector(kStandardDPI * scale_factor, kStandardDPI * scale_factor)); + + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +} // namespace + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + return std::unique_ptr<DesktopCapturer>(new WindowCapturerMac( + options.full_screen_window_detector(), options.configuration_monitor())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_capturer_null.cc b/third_party/libwebrtc/modules/desktop_capture/window_capturer_null.cc new file mode 100644 index 0000000000..6da2a76691 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_capturer_null.cc @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +class WindowCapturerNull : public DesktopCapturer { + public: + WindowCapturerNull(); + ~WindowCapturerNull() override; + + WindowCapturerNull(const WindowCapturerNull&) = delete; + WindowCapturerNull& operator=(const WindowCapturerNull&) = delete; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* sources) override; + bool SelectSource(SourceId id) override; + + private: + Callback* callback_ = nullptr; +}; + +WindowCapturerNull::WindowCapturerNull() {} +WindowCapturerNull::~WindowCapturerNull() {} + +bool WindowCapturerNull::GetSourceList(SourceList* sources) { + // Not implemented yet. + return false; +} + +bool WindowCapturerNull::SelectSource(SourceId id) { + // Not implemented yet. + return false; +} + +void WindowCapturerNull::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void WindowCapturerNull::CaptureFrame() { + // Not implemented yet. + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); +} + +} // namespace + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + return std::unique_ptr<DesktopCapturer>(new WindowCapturerNull()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_capturer_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/window_capturer_unittest.cc new file mode 100644 index 0000000000..519c04601b --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_capturer_unittest.cc @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> +#include <string> +#include <utility> + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "rtc_base/checks.h" +#include "test/gtest.h" + +namespace webrtc { + +class WindowCapturerTest : public ::testing::Test, + public DesktopCapturer::Callback { + public: + void SetUp() override { + capturer_ = DesktopCapturer::CreateWindowCapturer( + DesktopCaptureOptions::CreateDefault()); + ASSERT_TRUE(capturer_); + } + + void TearDown() override {} + + // DesktopCapturer::Callback interface + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) override { + frame_ = std::move(frame); + } + + protected: + std::unique_ptr<DesktopCapturer> capturer_; + std::unique_ptr<DesktopFrame> frame_; +}; + +// Verify that we can enumerate windows. +// TODO(bugs.webrtc.org/12950): Re-enable when libc++ issue is fixed +#if defined(WEBRTC_LINUX) && defined(MEMORY_SANITIZER) +#define MAYBE_Enumerate DISABLED_Enumerate +#else +#define MAYBE_Enumerate Enumerate +#endif +TEST_F(WindowCapturerTest, MAYBE_Enumerate) { + DesktopCapturer::SourceList sources; + EXPECT_TRUE(capturer_->GetSourceList(&sources)); + + // Verify that window titles are set. + for (auto it = sources.begin(); it != sources.end(); ++it) { + EXPECT_FALSE(it->title.empty()); + } +} + +// Flaky on Linux. See: crbug.com/webrtc/7830. +// Failing on macOS 11: See bugs.webrtc.org/12801 +#if defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) +#define MAYBE_Capture DISABLED_Capture +#else +#define MAYBE_Capture Capture +#endif +// Verify we can capture a window. +// +// TODO(sergeyu): Currently this test just looks at the windows that already +// exist. Ideally it should create a test window and capture from it, but there +// is no easy cross-platform way to create new windows (potentially we could +// have a python script showing Tk dialog, but launching code will differ +// between platforms). +TEST_F(WindowCapturerTest, MAYBE_Capture) { + DesktopCapturer::SourceList sources; + capturer_->Start(this); + EXPECT_TRUE(capturer_->GetSourceList(&sources)); + + // Verify that we can select and capture each window. + for (auto it = sources.begin(); it != sources.end(); ++it) { + frame_.reset(); + if (capturer_->SelectSource(it->id)) { + capturer_->CaptureFrame(); + } + + // If we failed to capture a window make sure it no longer exists. + if (!frame_.get()) { + DesktopCapturer::SourceList new_list; + EXPECT_TRUE(capturer_->GetSourceList(&new_list)); + for (auto new_list_it = new_list.begin(); new_list_it != new_list.end(); + ++new_list_it) { + EXPECT_FALSE(it->id == new_list_it->id); + } + continue; + } + + EXPECT_GT(frame_->size().width(), 0); + EXPECT_GT(frame_->size().height(), 0); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_capturer_win.cc b/third_party/libwebrtc/modules/desktop_capture/window_capturer_win.cc new file mode 100644 index 0000000000..f289746e30 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_capturer_win.cc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/win/window_capturer_win_gdi.h" + +#if defined(RTC_ENABLE_WIN_WGC) +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/win/wgc_capturer_win.h" +#include "rtc_base/win/windows_version.h" +#endif // defined(RTC_ENABLE_WIN_WGC) + +namespace webrtc { + +// static +std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr<DesktopCapturer> capturer( + WindowCapturerWinGdi::CreateRawWindowCapturer(options)); +#if defined(RTC_ENABLE_WIN_WGC) + if (options.allow_wgc_capturer_fallback() && + rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN11) { + // BlankDectector capturer will send an error when it detects a failed + // GDI rendering, then Fallback capturer will try to capture it again with + // WGC. + capturer = std::make_unique<BlankDetectorDesktopCapturerWrapper>( + std::move(capturer), RgbaColor(0, 0, 0, 0), + /*check_per_capture*/ true); + + capturer = std::make_unique<FallbackDesktopCapturerWrapper>( + std::move(capturer), + WgcCapturerWin::CreateRawWindowCapturer( + options, /*allow_delayed_capturable_check*/ true)); + } +#endif // defined(RTC_ENABLE_WIN_WGC) + return capturer; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder.cc b/third_party/libwebrtc/modules/desktop_capture/window_finder.cc new file mode 100644 index 0000000000..86127d4c05 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder.cc @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/window_finder.h" + +namespace webrtc { + +WindowFinder::Options::Options() = default; +WindowFinder::Options::~Options() = default; +WindowFinder::Options::Options(const WindowFinder::Options& other) = default; +WindowFinder::Options::Options(WindowFinder::Options&& other) = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder.h b/third_party/libwebrtc/modules/desktop_capture/window_finder.h new file mode 100644 index 0000000000..99e3cce559 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_H_ +#define MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_H_ + +#include <memory> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#endif + +namespace webrtc { + +#if defined(WEBRTC_USE_X11) +class XAtomCache; +#endif + +// An interface to return the id of the visible window under a certain point. +class WindowFinder { + public: + WindowFinder() = default; + virtual ~WindowFinder() = default; + + // Returns the id of the visible window under `point`. This function returns + // kNullWindowId if no window is under `point` and the platform does not have + // "root window" concept, i.e. the visible area under `point` is the desktop. + // `point` is always in system coordinate, i.e. the primary monitor always + // starts from (0, 0). + virtual WindowId GetWindowUnderPoint(DesktopVector point) = 0; + + struct Options final { + Options(); + ~Options(); + Options(const Options& other); + Options(Options&& other); + +#if defined(WEBRTC_USE_X11) + XAtomCache* cache = nullptr; +#endif +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor; +#endif + }; + + // Creates a platform-independent WindowFinder implementation. This function + // returns nullptr if `options` does not contain enough information or + // WindowFinder does not support current platform. + static std::unique_ptr<WindowFinder> Create(const Options& options); +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.h b/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.h new file mode 100644 index 0000000000..988dd497dd --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_MAC_H_ +#define MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_MAC_H_ + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/window_finder.h" + +namespace webrtc { + +class DesktopConfigurationMonitor; + +// The implementation of WindowFinder for Mac OSX. +class WindowFinderMac final : public WindowFinder { + public: + explicit WindowFinderMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor); + ~WindowFinderMac() override; + + // WindowFinder implementation. + WindowId GetWindowUnderPoint(DesktopVector point) override; + + private: + const rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_MAC_H_ diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.mm b/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.mm new file mode 100644 index 0000000000..e1d0316c79 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder_mac.mm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/window_finder_mac.h" + +#include <CoreFoundation/CoreFoundation.h> + +#include <memory> +#include <utility> + +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "modules/desktop_capture/mac/window_list_utils.h" + +namespace webrtc { + +WindowFinderMac::WindowFinderMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor) + : configuration_monitor_(std::move(configuration_monitor)) {} +WindowFinderMac::~WindowFinderMac() = default; + +WindowId WindowFinderMac::GetWindowUnderPoint(DesktopVector point) { + WindowId id = kNullWindowId; + GetWindowList( + [&id, point](CFDictionaryRef window) { + DesktopRect bounds; + bounds = GetWindowBounds(window); + if (bounds.Contains(point)) { + id = GetWindowId(window); + return false; + } + return true; + }, + true, + true); + return id; +} + +// static +std::unique_ptr<WindowFinder> WindowFinder::Create( + const WindowFinder::Options& options) { + return std::make_unique<WindowFinderMac>(options.configuration_monitor); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/window_finder_unittest.cc new file mode 100644 index 0000000000..ac13f124d3 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder_unittest.cc @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/window_finder.h" + +#include <stdint.h> + +#include <memory> + +#include "api/scoped_refptr.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/screen_drawer.h" +#include "rtc_base/logging.h" +#include "test/gtest.h" + +#if defined(WEBRTC_USE_X11) +#include "modules/desktop_capture/linux/x11/shared_x_display.h" +#include "modules/desktop_capture/linux/x11/x_atom_cache.h" +#endif + +#if defined(WEBRTC_WIN) +#include <windows.h> + +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "modules/desktop_capture/window_finder_win.h" +#endif + +namespace webrtc { + +namespace { + +#if defined(WEBRTC_WIN) +// ScreenDrawerWin does not have a message loop, so it's unresponsive to user +// inputs. WindowFinderWin cannot detect this kind of unresponsive windows. +// Instead, console window is used to test WindowFinderWin. +TEST(WindowFinderTest, FindConsoleWindow) { + // Creates a ScreenDrawer to avoid this test from conflicting with + // ScreenCapturerIntegrationTest: both tests require its window to be in + // foreground. + // + // In ScreenCapturer related tests, this is controlled by + // ScreenDrawer, which has a global lock to ensure only one ScreenDrawer + // window is active. So even we do not use ScreenDrawer for Windows test, + // creating an instance can block ScreenCapturer related tests until this test + // finishes. + // + // Usually the test framework should take care of this "isolated test" + // requirement, but unfortunately WebRTC trybots do not support this. + std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create(); + const int kMaxSize = 10000; + // Enlarges current console window. + system("mode 1000,1000"); + const HWND console_window = GetConsoleWindow(); + // Ensures that current console window is visible. + ShowWindow(console_window, SW_MAXIMIZE); + // Moves the window to the top-left of the display. + MoveWindow(console_window, 0, 0, kMaxSize, kMaxSize, true); + + bool should_restore_notopmost = + (GetWindowLong(console_window, GWL_EXSTYLE) & WS_EX_TOPMOST) == 0; + + // Brings console window to top. + SetWindowPos(console_window, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + BringWindowToTop(console_window); + + bool success = false; + WindowFinderWin finder; + for (int i = 0; i < kMaxSize; i++) { + const DesktopVector spot(i, i); + const HWND id = reinterpret_cast<HWND>(finder.GetWindowUnderPoint(spot)); + if (id == console_window) { + success = true; + break; + } + } + if (should_restore_notopmost) + SetWindowPos(console_window, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + if (!success) + FAIL(); +} + +#else +TEST(WindowFinderTest, FindDrawerWindow) { + WindowFinder::Options options; +#if defined(WEBRTC_USE_X11) + std::unique_ptr<XAtomCache> cache; + const auto shared_x_display = SharedXDisplay::CreateDefault(); + if (shared_x_display) { + cache = std::make_unique<XAtomCache>(shared_x_display->display()); + options.cache = cache.get(); + } +#endif + std::unique_ptr<WindowFinder> finder = WindowFinder::Create(options); + if (!finder) { + RTC_LOG(LS_WARNING) + << "No WindowFinder implementation for current platform."; + return; + } + + std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create(); + if (!drawer) { + RTC_LOG(LS_WARNING) + << "No ScreenDrawer implementation for current platform."; + return; + } + + if (drawer->window_id() == kNullWindowId) { + // TODO(zijiehe): WindowFinderTest can use a dedicated window without + // relying on ScreenDrawer. + RTC_LOG(LS_WARNING) + << "ScreenDrawer implementation for current platform does " + "create a window."; + return; + } + + // ScreenDrawer may not be able to bring the window to the top. So we test + // several spots, at least one of them should succeed. + const DesktopRect region = drawer->DrawableRegion(); + if (region.is_empty()) { + RTC_LOG(LS_WARNING) + << "ScreenDrawer::DrawableRegion() is too small for the " + "WindowFinderTest."; + return; + } + + for (int i = 0; i < region.width(); i++) { + const DesktopVector spot( + region.left() + i, region.top() + i * region.height() / region.width()); + const WindowId id = finder->GetWindowUnderPoint(spot); + if (id == drawer->window_id()) { + return; + } + } + + FAIL(); +} +#endif + +TEST(WindowFinderTest, ShouldReturnNullWindowIfSpotIsOutOfScreen) { + WindowFinder::Options options; +#if defined(WEBRTC_USE_X11) + std::unique_ptr<XAtomCache> cache; + const auto shared_x_display = SharedXDisplay::CreateDefault(); + if (shared_x_display) { + cache = std::make_unique<XAtomCache>(shared_x_display->display()); + options.cache = cache.get(); + } +#endif + std::unique_ptr<WindowFinder> finder = WindowFinder::Create(options); + if (!finder) { + RTC_LOG(LS_WARNING) + << "No WindowFinder implementation for current platform."; + return; + } + + ASSERT_EQ(kNullWindowId, + finder->GetWindowUnderPoint(DesktopVector(INT16_MAX, INT16_MAX))); + ASSERT_EQ(kNullWindowId, + finder->GetWindowUnderPoint(DesktopVector(INT16_MAX, INT16_MIN))); + ASSERT_EQ(kNullWindowId, + finder->GetWindowUnderPoint(DesktopVector(INT16_MIN, INT16_MAX))); + ASSERT_EQ(kNullWindowId, + finder->GetWindowUnderPoint(DesktopVector(INT16_MIN, INT16_MIN))); +} + +} // namespace + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder_win.cc b/third_party/libwebrtc/modules/desktop_capture/window_finder_win.cc new file mode 100644 index 0000000000..a8c3d39e19 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder_win.cc @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/window_finder_win.h" + +#include <windows.h> + +#include <memory> + +namespace webrtc { + +WindowFinderWin::WindowFinderWin() = default; +WindowFinderWin::~WindowFinderWin() = default; + +WindowId WindowFinderWin::GetWindowUnderPoint(DesktopVector point) { + HWND window = WindowFromPoint(POINT{point.x(), point.y()}); + if (!window) { + return kNullWindowId; + } + + // The difference between GA_ROOTOWNER and GA_ROOT can be found at + // https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/Hirr_DkuZdw. + // In short, we should use GA_ROOT, since we only care about the root window + // but not the owner. + window = GetAncestor(window, GA_ROOT); + if (!window) { + return kNullWindowId; + } + + return reinterpret_cast<WindowId>(window); +} + +// static +std::unique_ptr<WindowFinder> WindowFinder::Create( + const WindowFinder::Options& options) { + return std::make_unique<WindowFinderWin>(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/desktop_capture/window_finder_win.h b/third_party/libwebrtc/modules/desktop_capture/window_finder_win.h new file mode 100644 index 0000000000..a04e7e1aae --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/window_finder_win.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_WIN_H_ +#define MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_WIN_H_ + +#include "modules/desktop_capture/window_finder.h" + +namespace webrtc { + +// The implementation of WindowFinder for Windows. +class WindowFinderWin final : public WindowFinder { + public: + WindowFinderWin(); + ~WindowFinderWin() override; + + // WindowFinder implementation. + WindowId GetWindowUnderPoint(DesktopVector point) override; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_WIN_H_ |