diff options
Diffstat (limited to 'webrtc/modules/desktop_capture')
107 files changed, 13272 insertions, 0 deletions
diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn new file mode 100644 index 0000000000..aa33993192 --- /dev/null +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -0,0 +1,164 @@ +# 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/ui.gni") +import("../../build/webrtc.gni") + +use_desktop_capture_differ_sse2 = + !is_ios && (current_cpu == "x86" || current_cpu == "x64") + +source_set("primitives") { + sources = [ + "desktop_capture_types.h", + "desktop_frame.cc", + "desktop_frame.h", + "desktop_geometry.cc", + "desktop_geometry.h", + "desktop_region.cc", + "desktop_region.h", + ] + + public_configs = [ "../..:common_inherited_config" ] +} + +source_set("desktop_capture") { + sources = [ + "cropped_desktop_frame.cc", + "cropped_desktop_frame.h", + "cropping_window_capturer.cc", + "cropping_window_capturer.h", + "cropping_window_capturer_win.cc", + "desktop_and_cursor_composer.cc", + "desktop_and_cursor_composer.h", + "desktop_capture_options.cc", + "desktop_capture_options.h", + "desktop_capturer.h", + "desktop_capturer.h", + "desktop_frame_win.cc", + "desktop_frame_win.h", + "differ.cc", + "differ.h", + "differ_block.cc", + "differ_block.h", + "mac/desktop_configuration.h", + "mac/desktop_configuration.mm", + "mac/desktop_configuration_monitor.cc", + "mac/desktop_configuration_monitor.h", + "mac/full_screen_chrome_window_detector.cc", + "mac/full_screen_chrome_window_detector.h", + "mac/scoped_pixel_buffer_object.cc", + "mac/scoped_pixel_buffer_object.h", + "mac/window_list_utils.cc", + "mac/window_list_utils.h", + "mouse_cursor.cc", + "mouse_cursor.h", + "mouse_cursor_monitor.h", + "mouse_cursor_monitor_mac.mm", + "mouse_cursor_monitor_win.cc", + "screen_capture_frame_queue.cc", + "screen_capture_frame_queue.h", + "screen_capturer.cc", + "screen_capturer.h", + "screen_capturer_helper.cc", + "screen_capturer_helper.h", + "screen_capturer_mac.mm", + "screen_capturer_win.cc", + "shared_desktop_frame.cc", + "shared_desktop_frame.h", + "shared_memory.cc", + "shared_memory.h", + "win/cursor.cc", + "win/cursor.h", + "win/desktop.cc", + "win/desktop.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_gdi.cc", + "win/screen_capturer_win_gdi.h", + "win/screen_capturer_win_magnifier.cc", + "win/screen_capturer_win_magnifier.h", + "win/window_capture_utils.cc", + "win/window_capture_utils.h", + "window_capturer.cc", + "window_capturer.h", + "window_capturer_mac.mm", + "window_capturer_win.cc", + ] + + if (use_x11) { + sources += [ + "mouse_cursor_monitor_x11.cc", + "screen_capturer_x11.cc", + "window_capturer_x11.cc", + "x11/shared_x_display.cc", + "x11/shared_x_display.h", + "x11/x_error_trap.cc", + "x11/x_error_trap.h", + "x11/x_server_pixel_buffer.cc", + "x11/x_server_pixel_buffer.h", + ] + configs += [ "//build/config/linux:x11" ] + } + + if (!is_win && !is_mac && !use_x11) { + sources += [ + "mouse_cursor_monitor_null.cc", + "screen_capturer_null.cc", + "window_capturer_null.cc", + ] + } + + if (is_mac) { + libs = [ + "AppKit.framework", + "IOKit.framework", + "OpenGL.framework", + ] + } + + configs += [ "../..:common_config" ] + public_configs = [ "../..:common_inherited_config" ] + + if (is_clang && !is_nacl) { + # Suppress warnings from Chrome's Clang plugins. + # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. + configs -= [ "//build/config/clang:find_bad_constructs" ] + } + + deps = [ + ":primitives", + "../../base:rtc_base_approved", + "../../system_wrappers", + ] + + if (use_desktop_capture_differ_sse2) { + deps += [ ":desktop_capture_differ_sse2" ] + } +} + +if (use_desktop_capture_differ_sse2) { + # Have to be compiled as a separate target because it needs to be compiled + # with SSE2 enabled. + source_set("desktop_capture_differ_sse2") { + visibility = [ ":*" ] + sources = [ + "differ_block_sse2.cc", + "differ_block_sse2.h", + ] + + configs += [ "../..:common_config" ] + public_configs = [ "../..:common_inherited_config" ] + + if (is_posix && !is_mac) { + cflags = [ "-msse2" ] + } + } +} diff --git a/webrtc/modules/desktop_capture/OWNERS b/webrtc/modules/desktop_capture/OWNERS new file mode 100644 index 0000000000..67d2fa1959 --- /dev/null +++ b/webrtc/modules/desktop_capture/OWNERS @@ -0,0 +1,11 @@ +alexeypa@chromium.org +jiayl@webrtc.org +sergeyu@chromium.org +wez@chromium.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/webrtc/modules/desktop_capture/cropped_desktop_frame.cc b/webrtc/modules/desktop_capture/cropped_desktop_frame.cc new file mode 100644 index 0000000000..2c709733e1 --- /dev/null +++ b/webrtc/modules/desktop_capture/cropped_desktop_frame.cc @@ -0,0 +1,45 @@ +/* + * 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 "webrtc/modules/desktop_capture/cropped_desktop_frame.h" + +namespace webrtc { + +// A DesktopFrame that is a sub-rect of another DesktopFrame. +class CroppedDesktopFrame : public DesktopFrame { + public: + CroppedDesktopFrame(DesktopFrame* frame, const DesktopRect& rect); + + private: + rtc::scoped_ptr<DesktopFrame> frame_; + + RTC_DISALLOW_COPY_AND_ASSIGN(CroppedDesktopFrame); +}; + +DesktopFrame* +CreateCroppedDesktopFrame(DesktopFrame* frame, const DesktopRect& rect) { + if (!DesktopRect::MakeSize(frame->size()).ContainsRect(rect)) { + delete frame; + return NULL; + } + + return new CroppedDesktopFrame(frame, rect); +} + +CroppedDesktopFrame::CroppedDesktopFrame(DesktopFrame* frame, + const DesktopRect& rect) + : DesktopFrame(rect.size(), + frame->stride(), + frame->GetFrameDataAtPos(rect.top_left()), + frame->shared_memory()), + frame_(frame) { +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/cropped_desktop_frame.h b/webrtc/modules/desktop_capture/cropped_desktop_frame.h new file mode 100644 index 0000000000..29449e27f3 --- /dev/null +++ b/webrtc/modules/desktop_capture/cropped_desktop_frame.h @@ -0,0 +1,25 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ + +#include "webrtc/modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +// Always takes ownership of |frame|. Returns NULL if |rect| is not contained +// by the bounds of |frame|. +DesktopFrame* CreateCroppedDesktopFrame(DesktopFrame* frame, + const DesktopRect& rect); +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_ + diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer.cc b/webrtc/modules/desktop_capture/cropping_window_capturer.cc new file mode 100644 index 0000000000..1e37e782ff --- /dev/null +++ b/webrtc/modules/desktop_capture/cropping_window_capturer.cc @@ -0,0 +1,111 @@ +/* + * 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 "webrtc/modules/desktop_capture/cropping_window_capturer.h" + +#include "webrtc/modules/desktop_capture/cropped_desktop_frame.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +CroppingWindowCapturer::CroppingWindowCapturer( + const DesktopCaptureOptions& options) + : options_(options), + callback_(NULL), + window_capturer_(WindowCapturer::Create(options)), + selected_window_(kNullWindowId), + excluded_window_(kNullWindowId) { +} + +CroppingWindowCapturer::~CroppingWindowCapturer() {} + +void CroppingWindowCapturer::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + window_capturer_->Start(callback); +} + +void CroppingWindowCapturer::Capture(const DesktopRegion& region) { + if (ShouldUseScreenCapturer()) { + if (!screen_capturer_.get()) { + screen_capturer_.reset(ScreenCapturer::Create(options_)); + if (excluded_window_) { + screen_capturer_->SetExcludedWindow(excluded_window_); + } + screen_capturer_->Start(this); + } + screen_capturer_->Capture(region); + } else { + window_capturer_->Capture(region); + } +} + +void CroppingWindowCapturer::SetExcludedWindow(WindowId window) { + excluded_window_ = window; + if (screen_capturer_.get()) { + screen_capturer_->SetExcludedWindow(window); + } +} + +bool CroppingWindowCapturer::GetWindowList(WindowList* windows) { + return window_capturer_->GetWindowList(windows); +} + +bool CroppingWindowCapturer::SelectWindow(WindowId id) { + if (window_capturer_->SelectWindow(id)) { + selected_window_ = id; + return true; + } + return false; +} + +bool CroppingWindowCapturer::BringSelectedWindowToFront() { + return window_capturer_->BringSelectedWindowToFront(); +} + +SharedMemory* CroppingWindowCapturer::CreateSharedMemory(size_t size) { + return callback_->CreateSharedMemory(size); +} + +void CroppingWindowCapturer::OnCaptureCompleted(DesktopFrame* frame) { + rtc::scoped_ptr<DesktopFrame> screen_frame(frame); + + if (!ShouldUseScreenCapturer()) { + LOG(LS_INFO) << "Window no longer on top when ScreenCapturer finishes"; + window_capturer_->Capture(DesktopRegion()); + return; + } + + if (!frame) { + LOG(LS_WARNING) << "ScreenCapturer failed to capture a frame"; + callback_->OnCaptureCompleted(NULL); + return; + } + + DesktopRect window_rect = GetWindowRectInVirtualScreen(); + if (window_rect.is_empty()) { + LOG(LS_WARNING) << "Window rect is empty"; + callback_->OnCaptureCompleted(NULL); + return; + } + + rtc::scoped_ptr<DesktopFrame> window_frame( + CreateCroppedDesktopFrame(screen_frame.release(), window_rect)); + callback_->OnCaptureCompleted(window_frame.release()); +} + +#if !defined(WEBRTC_WIN) +// static +WindowCapturer* +CroppingWindowCapturer::Create(const DesktopCaptureOptions& options) { + return WindowCapturer::Create(options); +} +#endif + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer.h b/webrtc/modules/desktop_capture/cropping_window_capturer.h new file mode 100644 index 0000000000..e0f598c7ea --- /dev/null +++ b/webrtc/modules/desktop_capture/cropping_window_capturer.h @@ -0,0 +1,74 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/modules/desktop_capture/window_capturer.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 CroppingWindowCapturer : public WindowCapturer, + public DesktopCapturer::Callback { + public: + static WindowCapturer* Create(const DesktopCaptureOptions& options); + virtual ~CroppingWindowCapturer(); + + // DesktopCapturer implementation. + void Start(DesktopCapturer::Callback* callback) override; + void Capture(const DesktopRegion& region) override; + void SetExcludedWindow(WindowId window) override; + + // WindowCapturer implementation. + bool GetWindowList(WindowList* windows) override; + bool SelectWindow(WindowId id) override; + bool BringSelectedWindowToFront() override; + + // DesktopCapturer::Callback implementation, passed to |screen_capturer_| to + // intercept the capture result. + SharedMemory* CreateSharedMemory(size_t size) override; + void OnCaptureCompleted(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. + virtual DesktopRect GetWindowRectInVirtualScreen() = 0; + + WindowId selected_window() const { return selected_window_; } + WindowId excluded_window() const { return excluded_window_; } + + private: + DesktopCaptureOptions options_; + DesktopCapturer::Callback* callback_; + rtc::scoped_ptr<WindowCapturer> window_capturer_; + rtc::scoped_ptr<ScreenCapturer> screen_capturer_; + WindowId selected_window_; + WindowId excluded_window_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_ + diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc new file mode 100644 index 0000000000..fe696eba67 --- /dev/null +++ b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc @@ -0,0 +1,218 @@ +/* + * 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 "webrtc/modules/desktop_capture/cropping_window_capturer.h" + +#include "webrtc/base/win32.h" +#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/modules/desktop_capture/win/window_capture_utils.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +namespace { + +// Used to pass input/output data during the EnumWindow call for verifying if +// the selected window is on top. +struct TopWindowVerifierContext { + TopWindowVerifierContext(HWND selected_window, HWND excluded_window) + : selected_window(selected_window), + excluded_window(excluded_window), + is_top_window(false), + selected_window_process_id(0) {} + + HWND selected_window; + HWND excluded_window; + bool is_top_window; + DWORD selected_window_process_id; + DesktopRect selected_window_rect; +}; + +// The function is called during EnumWindow for every window enumerated and is +// responsible for verifying if the selected window is on top. +BOOL CALLBACK TopWindowVerifier(HWND hwnd, LPARAM param) { + TopWindowVerifierContext* context = + reinterpret_cast<TopWindowVerifierContext*>(param); + + if (hwnd == context->selected_window) { + context->is_top_window = true; + return FALSE; + } + + // Ignore the excluded window. + if (hwnd == context->excluded_window) { + return TRUE; + } + + // Ignore hidden or minimized window. + if (IsIconic(hwnd) || !IsWindowVisible(hwnd)) { + return TRUE; + } + + // Ignore descendant/owned windows since we want to capture them. + // This check does not work for tooltips and context menus. Drop down menus + // and popup windows are fine. + if (GetAncestor(hwnd, GA_ROOTOWNER) == context->selected_window) { + return TRUE; + } + + // If |hwnd| has no title and belongs to the same process, assume it's a + // tooltip or context menu from the selected window and ignore it. + const size_t kTitleLength = 32; + WCHAR window_title[kTitleLength]; + GetWindowText(hwnd, window_title, kTitleLength); + if (wcsnlen_s(window_title, kTitleLength) == 0) { + DWORD enumerated_process; + GetWindowThreadProcessId(hwnd, &enumerated_process); + if (!context->selected_window_process_id) { + GetWindowThreadProcessId(context->selected_window, + &context->selected_window_process_id); + } + if (context->selected_window_process_id == enumerated_process) { + return TRUE; + } + } + + // Check if the enumerated window intersects with the selected window. + RECT enumerated_rect; + if (!GetWindowRect(hwnd, &enumerated_rect)) { + // Bail out if failed to get the window area. + context->is_top_window = false; + return FALSE; + } + + DesktopRect intersect_rect = context->selected_window_rect; + DesktopRect enumerated_desktop_rect = + DesktopRect::MakeLTRB(enumerated_rect.left, + enumerated_rect.top, + enumerated_rect.right, + enumerated_rect.bottom); + intersect_rect.IntersectWith(enumerated_desktop_rect); + + // If intersection is not empty, the selected window is not on top. + if (!intersect_rect.is_empty()) { + context->is_top_window = false; + return FALSE; + } + // Otherwise, keep enumerating. + return TRUE; +} + +class CroppingWindowCapturerWin : public CroppingWindowCapturer { + public: + CroppingWindowCapturerWin( + const DesktopCaptureOptions& options) + : CroppingWindowCapturer(options) {} + + private: + bool ShouldUseScreenCapturer() override; + DesktopRect GetWindowRectInVirtualScreen() override; + + // 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_; + + AeroChecker aero_checker_; +}; + +bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() { + if (!rtc::IsWindows8OrLater() && aero_checker_.IsAeroEnabled()) + return false; + + // Check if the window is a translucent layered window. + HWND selected = reinterpret_cast<HWND>(selected_window()); + 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; + } + + TopWindowVerifierContext context( + selected, reinterpret_cast<HWND>(excluded_window())); + + RECT selected_window_rect; + if (!GetWindowRect(selected, &selected_window_rect)) { + return false; + } + context.selected_window_rect = DesktopRect::MakeLTRB( + selected_window_rect.left, + selected_window_rect.top, + selected_window_rect.right, + selected_window_rect.bottom); + + // Get the window region and check if it is rectangular. + win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN> > + scoped_hrgn(CreateRectRgn(0, 0, 0, 0)); + int region_type = GetWindowRgn(selected, scoped_hrgn.Get()); + + // 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) { + RECT region_rect; + GetRgnBox(scoped_hrgn.Get(), ®ion_rect); + DesktopRect rgn_rect = + DesktopRect::MakeLTRB(region_rect.left, + region_rect.top, + region_rect.right, + region_rect.bottom); + rgn_rect.Translate(context.selected_window_rect.left(), + context.selected_window_rect.top()); + context.selected_window_rect.IntersectWith(rgn_rect); + } + window_region_rect_ = context.selected_window_rect; + + // Check if the window is occluded by any other window, excluding the child + // windows, context menus, and |excluded_window_|. + EnumWindows(&TopWindowVerifier, reinterpret_cast<LPARAM>(&context)); + return context.is_top_window; +} + +DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() { + DesktopRect original_rect; + DesktopRect window_rect; + HWND hwnd = reinterpret_cast<HWND>(selected_window()); + if (!GetCroppedWindowRect(hwnd, &window_rect, &original_rect)) { + 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(GetScreenRect(kFullDesktopScreenId, L"")); + window_rect.IntersectWith(screen_rect); + window_rect.Translate(-screen_rect.left(), -screen_rect.top()); + return window_rect; +} + +} // namespace + +// static +WindowCapturer* +CroppingWindowCapturer::Create(const DesktopCaptureOptions& options) { + return new CroppingWindowCapturerWin(options); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc new file mode 100644 index 0000000000..74d25d4e1f --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc @@ -0,0 +1,174 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h" + +#include <string.h> + +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.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(DesktopFrame* frame, + const MouseCursor& cursor, + const DesktopVector& position); + virtual ~DesktopFrameWithCursor(); + + private: + rtc::scoped_ptr<DesktopFrame> original_frame_; + + DesktopVector restore_position_; + rtc::scoped_ptr<DesktopFrame> restore_frame_; + + RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrameWithCursor); +}; + +DesktopFrameWithCursor::DesktopFrameWithCursor(DesktopFrame* frame, + const MouseCursor& cursor, + const DesktopVector& position) + : DesktopFrame(frame->size(), frame->stride(), + frame->data(), frame->shared_memory()), + original_frame_(frame) { + set_dpi(frame->dpi()); + set_capture_time_ms(frame->capture_time_ms()); + mutable_updated_region()->Swap(frame->mutable_updated_region()); + + DesktopVector image_pos = position.subtract(cursor.hotspot()); + DesktopRect target_rect = DesktopRect::MakeSize(cursor.image()->size()); + target_rect.Translate(image_pos); + DesktopVector target_origin = target_rect.top_left(); + target_rect.IntersectWith(DesktopRect::MakeSize(size())); + + if (target_rect.is_empty()) + return; + + // Copy original screen content under cursor to |restore_frame_|. + restore_position_ = target_rect.top_left(); + restore_frame_.reset(new BasicDesktopFrame(target_rect.size())); + restore_frame_->CopyPixelsFrom(*this, target_rect.top_left(), + DesktopRect::MakeSize(restore_frame_->size())); + + // Blit the cursor. + uint8_t* target_rect_data = reinterpret_cast<uint8_t*>(data()) + + target_rect.top() * stride() + + target_rect.left() * DesktopFrame::kBytesPerPixel; + DesktopVector origin_shift = target_rect.top_left().subtract(target_origin); + AlphaBlend(target_rect_data, stride(), + cursor.image()->data() + + origin_shift.y() * cursor.image()->stride() + + origin_shift.x() * DesktopFrame::kBytesPerPixel, + cursor.image()->stride(), + target_rect.size()); +} + +DesktopFrameWithCursor::~DesktopFrameWithCursor() { + // Restore original content of the frame. + if (restore_frame_.get()) { + 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( + DesktopCapturer* desktop_capturer, + MouseCursorMonitor* mouse_monitor) + : desktop_capturer_(desktop_capturer), + mouse_monitor_(mouse_monitor) { +} + +DesktopAndCursorComposer::~DesktopAndCursorComposer() {} + +void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + if (mouse_monitor_.get()) + mouse_monitor_->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION); + desktop_capturer_->Start(this); +} + +void DesktopAndCursorComposer::Capture(const DesktopRegion& region) { + if (mouse_monitor_.get()) + mouse_monitor_->Capture(); + desktop_capturer_->Capture(region); +} + +void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) { + desktop_capturer_->SetExcludedWindow(window); +} + +SharedMemory* DesktopAndCursorComposer::CreateSharedMemory(size_t size) { + return callback_->CreateSharedMemory(size); +} + +void DesktopAndCursorComposer::OnCaptureCompleted(DesktopFrame* frame) { + if (frame && cursor_.get() && cursor_state_ == MouseCursorMonitor::INSIDE) { + DesktopFrameWithCursor* frame_with_cursor = + new DesktopFrameWithCursor(frame, *cursor_, cursor_position_); + frame = frame_with_cursor; + } + + callback_->OnCaptureCompleted(frame); +} + +void DesktopAndCursorComposer::OnMouseCursor(MouseCursor* cursor) { + cursor_.reset(cursor); +} + +void DesktopAndCursorComposer::OnMouseCursorPosition( + MouseCursorMonitor::CursorState state, + const DesktopVector& position) { + cursor_state_ = state; + cursor_position_ = position; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h new file mode 100644 index 0000000000..7a72031c79 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h @@ -0,0 +1,63 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +namespace webrtc { + +// A wrapper for DesktopCapturer that also captures mouse using specified +// MouseCursorMonitor and renders it on the generated streams. +class DesktopAndCursorComposer : public DesktopCapturer, + public DesktopCapturer::Callback, + public MouseCursorMonitor::Callback { + public: + // Creates a new blender that captures mouse cursor using |mouse_monitor| and + // renders it into the frames generated by |desktop_capturer|. If + // |mouse_monitor| is NULL the frames are passed unmodified. Takes ownership + // of both arguments. + DesktopAndCursorComposer(DesktopCapturer* desktop_capturer, + MouseCursorMonitor* mouse_monitor); + virtual ~DesktopAndCursorComposer(); + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void Capture(const DesktopRegion& region) override; + void SetExcludedWindow(WindowId window) override; + + private: + // DesktopCapturer::Callback interface. + SharedMemory* CreateSharedMemory(size_t size) override; + void OnCaptureCompleted(DesktopFrame* frame) override; + + // MouseCursorMonitor::Callback interface. + void OnMouseCursor(MouseCursor* cursor) override; + void OnMouseCursorPosition(MouseCursorMonitor::CursorState state, + const DesktopVector& position) override; + + rtc::scoped_ptr<DesktopCapturer> desktop_capturer_; + rtc::scoped_ptr<MouseCursorMonitor> mouse_monitor_; + + DesktopCapturer::Callback* callback_; + + rtc::scoped_ptr<MouseCursor> cursor_; + MouseCursorMonitor::CursorState cursor_state_; + DesktopVector cursor_position_; + + RTC_DISALLOW_COPY_AND_ASSIGN(DesktopAndCursorComposer); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_ diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc new file mode 100644 index 0000000000..bee9e087f9 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc @@ -0,0 +1,250 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/modules/desktop_capture/window_capturer.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +namespace { + +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.data() + pos.y() * frame.stride() + + pos.x() * DesktopFrame::kBytesPerPixel); +} + +// 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() { + DesktopFrame* frame = + new BasicDesktopFrame(DesktopSize(kScreenWidth, kScreenHeight)); + uint32_t* data = reinterpret_cast<uint32_t*>(frame->data()); + for (int y = 0; y < kScreenHeight; ++y) { + for (int x = 0; x < kScreenWidth; ++x) { + *(data++) = GetFakeFramePixelValue(DesktopVector(x, y)); + } + } + return frame; +} + +class FakeScreenCapturer : public DesktopCapturer { + public: + FakeScreenCapturer() {} + + void Start(Callback* callback) override { callback_ = callback; } + + void Capture(const DesktopRegion& region) override { + callback_->OnCaptureCompleted(next_frame_.release()); + } + + void SetNextFrame(DesktopFrame* next_frame) { + next_frame_.reset(next_frame); + } + + private: + Callback* callback_; + + rtc::scoped_ptr<DesktopFrame> next_frame_; +}; + +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) override { callback_ = callback; } + + void Capture() override { + if (changed_) { + rtc::scoped_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight))); + uint32_t* data = reinterpret_cast<uint32_t*>(image->data()); + memset(data, 0, image->stride() * kCursorHeight); + + // 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]; + } + } + + callback_->OnMouseCursor(new MouseCursor(image.release(), hotspot_)); + } + + callback_->OnMouseCursorPosition(state_, 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)); + } + } + } +} + +class DesktopAndCursorComposerTest : public testing::Test, + public DesktopCapturer::Callback { + public: + DesktopAndCursorComposerTest() + : fake_screen_(new FakeScreenCapturer()), + fake_cursor_(new FakeMouseMonitor()), + blender_(fake_screen_, fake_cursor_) { + } + + // DesktopCapturer::Callback interface + SharedMemory* CreateSharedMemory(size_t size) override { return NULL; } + + void OnCaptureCompleted(DesktopFrame* frame) override { frame_.reset(frame); } + + protected: + // Owned by |blender_|. + FakeScreenCapturer* fake_screen_; + FakeMouseMonitor* fake_cursor_; + + DesktopAndCursorComposer blender_; + rtc::scoped_ptr<DesktopFrame> frame_; +}; + +// Verify DesktopAndCursorComposer can handle the case when the screen capturer +// fails. +TEST_F(DesktopAndCursorComposerTest, Error) { + blender_.Start(this); + + fake_cursor_->SetHotspot(DesktopVector()); + fake_cursor_->SetState(MouseCursorMonitor::INSIDE, DesktopVector()); + fake_screen_->SetNextFrame(NULL); + + blender_.Capture(DesktopRegion()); + + EXPECT_EQ(frame_, static_cast<DesktopFrame*>(NULL)); +} + +TEST_F(DesktopAndCursorComposerTest, Blend) { + 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}, + }; + + blender_.Start(this); + + for (size_t i = 0; i < (sizeof(tests) / sizeof(tests[0])); ++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); + + rtc::scoped_ptr<SharedDesktopFrame> frame( + SharedDesktopFrame::Wrap(CreateTestFrame())); + fake_screen_->SetNextFrame(frame->Share()); + + blender_.Capture(DesktopRegion()); + + VerifyFrame(*frame_, state, pos); + + // Verify that the cursor is erased before the frame buffer is returned to + // the screen capturer. + frame_.reset(); + VerifyFrame(*frame, MouseCursorMonitor::OUTSIDE, DesktopVector()); + } +} + +} // namespace + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi new file mode 100644 index 0000000000..b92447c349 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -0,0 +1,160 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'desktop_capture', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + '<(webrtc_root)/base/base.gyp:rtc_base', + ], + 'sources': [ + 'cropped_desktop_frame.cc', + 'cropped_desktop_frame.h', + 'cropping_window_capturer.cc', + 'cropping_window_capturer.h', + 'cropping_window_capturer_win.cc', + "desktop_and_cursor_composer.cc", + "desktop_and_cursor_composer.h", + "desktop_capture_types.h", + "desktop_capturer.h", + "desktop_frame.cc", + "desktop_frame.h", + "desktop_frame_win.cc", + "desktop_frame_win.h", + "desktop_geometry.cc", + "desktop_geometry.h", + "desktop_capture_options.h", + "desktop_capture_options.cc", + "desktop_capturer.h", + "desktop_region.cc", + "desktop_region.h", + "differ.cc", + "differ.h", + "differ_block.cc", + "differ_block.h", + "mac/desktop_configuration.h", + "mac/desktop_configuration.mm", + "mac/desktop_configuration_monitor.h", + "mac/desktop_configuration_monitor.cc", + "mac/full_screen_chrome_window_detector.cc", + "mac/full_screen_chrome_window_detector.h", + "mac/scoped_pixel_buffer_object.cc", + "mac/scoped_pixel_buffer_object.h", + "mac/window_list_utils.cc", + "mac/window_list_utils.h", + "mouse_cursor.cc", + "mouse_cursor.h", + "mouse_cursor_monitor.h", + "mouse_cursor_monitor_mac.mm", + "mouse_cursor_monitor_win.cc", + "mouse_cursor_monitor_x11.cc", + "screen_capture_frame_queue.cc", + "screen_capture_frame_queue.h", + "screen_capturer.cc", + "screen_capturer.h", + "screen_capturer_helper.cc", + "screen_capturer_helper.h", + "screen_capturer_mac.mm", + "screen_capturer_win.cc", + "screen_capturer_x11.cc", + "shared_desktop_frame.cc", + "shared_desktop_frame.h", + "shared_memory.cc", + "shared_memory.h", + "win/cursor.cc", + "win/cursor.h", + "win/desktop.cc", + "win/desktop.h", + "win/scoped_gdi_object.h", + "win/scoped_thread_desktop.cc", + "win/scoped_thread_desktop.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/screen_capture_utils.cc", + "win/screen_capture_utils.h", + "win/window_capture_utils.cc", + "win/window_capture_utils.h", + "window_capturer.cc", + "window_capturer.h", + "window_capturer_mac.mm", + "window_capturer_win.cc", + "window_capturer_x11.cc", + "x11/shared_x_display.h", + "x11/shared_x_display.cc", + "x11/x_error_trap.cc", + "x11/x_error_trap.h", + "x11/x_server_pixel_buffer.cc", + "x11/x_server_pixel_buffer.h", + ], + 'conditions': [ + ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', { + 'dependencies': [ + 'desktop_capture_differ_sse2', + ], + }], + ['use_x11 == 1', { + 'link_settings': { + 'libraries': [ + '-lX11', + '-lXcomposite', + '-lXdamage', + '-lXext', + '-lXfixes', + '-lXrender', + ], + }, + }], + ['OS!="win" and OS!="mac" and use_x11==0', { + 'sources': [ + "mouse_cursor_monitor_null.cc", + "screen_capturer_null.cc", + "window_capturer_null.cc", + ], + }], + ['OS=="mac"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + '$(SDKROOT)/System/Library/Frameworks/IOKit.framework', + '$(SDKROOT)/System/Library/Frameworks/OpenGL.framework', + ], + }, + }], + ], + }, + ], # targets + 'conditions': [ + ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', { + 'targets': [ + { + # Have to be compiled as a separate target because it needs to be + # compiled with SSE2 enabled. + 'target_name': 'desktop_capture_differ_sse2', + 'type': 'static_library', + 'sources': [ + "differ_block_sse2.cc", + "differ_block_sse2.h", + ], + 'conditions': [ + ['os_posix==1', { + 'cflags': [ '-msse2', ], + 'xcode_settings': { + 'OTHER_CFLAGS': [ '-msse2', ], + }, + }], + ], + }, + ], # targets + }], + ], +} diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.cc b/webrtc/modules/desktop_capture/desktop_capture_options.cc new file mode 100644 index 0000000000..b7f123b9d1 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capture_options.cc @@ -0,0 +1,44 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_capture_options.h" + +namespace webrtc { + +DesktopCaptureOptions::DesktopCaptureOptions() + : use_update_notifications_(true), + disable_effects_(true) { +#if defined(USE_X11) + // XDamage is often broken, so don't use it by default. + use_update_notifications_ = false; +#endif + +#if defined(WEBRTC_WIN) + allow_use_magnification_api_ = false; +#endif +} + +DesktopCaptureOptions::~DesktopCaptureOptions() {} + +// static +DesktopCaptureOptions DesktopCaptureOptions::CreateDefault() { + DesktopCaptureOptions result; +#if defined(USE_X11) + result.set_x_display(SharedXDisplay::CreateDefault()); +#endif +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + result.set_configuration_monitor(new DesktopConfigurationMonitor()); + result.set_full_screen_chrome_window_detector( + new FullScreenChromeWindowDetector()); +#endif + return result; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.h b/webrtc/modules/desktop_capture/desktop_capture_options.h new file mode 100644 index 0000000000..68bb588445 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capture_options.h @@ -0,0 +1,108 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ref_ptr.h" + +#if defined(USE_X11) +#include "webrtc/modules/desktop_capture/x11/shared_x_display.h" +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" +#endif + +namespace webrtc { + +// An object that stores initialization parameters for screen and window +// capturers. +class DesktopCaptureOptions { + public: + // Creates an empty Options instance (e.g. without X display). + DesktopCaptureOptions(); + ~DesktopCaptureOptions(); + + // 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(); + +#if defined(USE_X11) + 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) + DesktopConfigurationMonitor* configuration_monitor() const { + return configuration_monitor_; + } + void set_configuration_monitor( + rtc::scoped_refptr<DesktopConfigurationMonitor> m) { + configuration_monitor_ = m; + } + + FullScreenChromeWindowDetector* full_screen_chrome_window_detector() const { + return full_screen_window_detector_; + } + void set_full_screen_chrome_window_detector( + rtc::scoped_refptr<FullScreenChromeWindowDetector> detector) { + full_screen_window_detector_ = detector; + } +#endif + + // 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; + } + +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api() const { + return allow_use_magnification_api_; + } + void set_allow_use_magnification_api(bool allow) { + allow_use_magnification_api_ = allow; + } +#endif + + private: +#if defined(USE_X11) + rtc::scoped_refptr<SharedXDisplay> x_display_; +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; + rtc::scoped_refptr<FullScreenChromeWindowDetector> + full_screen_window_detector_; +#endif + +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api_; +#endif + bool use_update_notifications_; + bool disable_effects_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_ diff --git a/webrtc/modules/desktop_capture/desktop_capture_types.h b/webrtc/modules/desktop_capture/desktop_capture_types.h new file mode 100644 index 0000000000..3e41796553 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capture_types.h @@ -0,0 +1,43 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ + +#include <stdint.h> + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +// 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. +typedef intptr_t ScreenId; + +// The screen id corresponds to all screen combined together. +const ScreenId kFullDesktopScreenId = -1; + +const ScreenId kInvalidScreenId = -2; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h new file mode 100644 index 0000000000..7ad1636497 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capturer.h @@ -0,0 +1,67 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ + +#include <stddef.h> + +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" + +namespace webrtc { + +class DesktopFrame; +class DesktopRegion; +class SharedMemory; + +// Abstract interface for screen and window capturers. +class DesktopCapturer { + public: + // Interface that must be implemented by the DesktopCapturer consumers. + class Callback { + public: + // Creates a new shared memory buffer for a frame create by the capturer. + // Should return null shared memory is not used for captured frames (in that + // case the capturer will allocate memory on the heap). + virtual SharedMemory* CreateSharedMemory(size_t size) = 0; + + // Called after a frame has been captured. Handler must take ownership of + // |frame|. If capture has failed for any reason |frame| is set to NULL + // (e.g. the window has been closed). + virtual void OnCaptureCompleted(DesktopFrame* frame) = 0; + + protected: + virtual ~Callback() {} + }; + + virtual ~DesktopCapturer() {} + + // Called at the beginning of a capturing session. |callback| must remain + // valid until capturer is destroyed. + virtual void Start(Callback* callback) = 0; + + // Captures next frame. |region| specifies region of the capture target that + // should be fresh in the resulting frame. The frame may also include fresh + // data for areas outside |region|. In that case capturer will include these + // areas in updated_region() of the frame. |region| is specified relative to + // the top left corner of the capture target. Pending capture operations are + // canceled when DesktopCapturer is deleted. + virtual void Capture(const DesktopRegion& region) = 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) {} +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_frame.cc b/webrtc/modules/desktop_capture/desktop_frame.cc new file mode 100644 index 0000000000..cb32ef7826 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_frame.cc @@ -0,0 +1,94 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_frame.h" + +#include <assert.h> +#include <string.h> + +namespace webrtc { + +DesktopFrame::DesktopFrame(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory) + : size_(size), + stride_(stride), + data_(data), + shared_memory_(shared_memory), + capture_time_ms_(0) { +} + +DesktopFrame::~DesktopFrame() {} + +void DesktopFrame::CopyPixelsFrom(uint8_t* src_buffer, int src_stride, + const DesktopRect& dest_rect) { + assert(DesktopRect::MakeSize(size()).ContainsRect(dest_rect)); + + uint8_t* dest = GetFrameDataAtPos(dest_rect.top_left()); + for (int y = 0; y < dest_rect.height(); ++y) { + memcpy(dest, src_buffer, DesktopFrame::kBytesPerPixel * dest_rect.width()); + src_buffer += src_stride; + dest += stride(); + } +} + +void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame, + const DesktopVector& src_pos, + const DesktopRect& dest_rect) { + assert(DesktopRect::MakeSize(src_frame.size()).ContainsRect( + DesktopRect::MakeOriginSize(src_pos, dest_rect.size()))); + + CopyPixelsFrom(src_frame.GetFrameDataAtPos(src_pos), + src_frame.stride(), dest_rect); +} + +uint8_t* DesktopFrame::GetFrameDataAtPos(const DesktopVector& pos) const { + return data() + stride() * pos.y() + DesktopFrame::kBytesPerPixel * pos.x(); +} + +BasicDesktopFrame::BasicDesktopFrame(DesktopSize size) + : DesktopFrame(size, kBytesPerPixel * size.width(), + new uint8_t[kBytesPerPixel * size.width() * size.height()], + NULL) { +} + +BasicDesktopFrame::~BasicDesktopFrame() { + delete[] data_; +} + +DesktopFrame* BasicDesktopFrame::CopyOf(const DesktopFrame& frame) { + DesktopFrame* result = new BasicDesktopFrame(frame.size()); + for (int y = 0; y < frame.size().height(); ++y) { + memcpy(result->data() + y * result->stride(), + frame.data() + y * frame.stride(), + frame.size().width() * kBytesPerPixel); + } + result->set_dpi(frame.dpi()); + result->set_capture_time_ms(frame.capture_time_ms()); + *result->mutable_updated_region() = frame.updated_region(); + return result; +} + + +SharedMemoryDesktopFrame::SharedMemoryDesktopFrame( + DesktopSize size, + int stride, + SharedMemory* shared_memory) + : DesktopFrame(size, stride, + reinterpret_cast<uint8_t*>(shared_memory->data()), + shared_memory) { +} + +SharedMemoryDesktopFrame::~SharedMemoryDesktopFrame() { + delete shared_memory_; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_frame.h b/webrtc/modules/desktop_capture/desktop_frame.h new file mode 100644 index 0000000000..49b964630c --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_frame.h @@ -0,0 +1,126 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/shared_memory.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +// DesktopFrame represents a video frame captured from the screen. +class DesktopFrame { + public: + // DesktopFrame objects always hold RGBA data. + static const int kBytesPerPixel = 4; + + virtual ~DesktopFrame(); + + // Size of the frame. + const DesktopSize& size() const { return size_; } + + // 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; } + + // 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; } + + // Optional shape for the frame. Frames may be shaped e.g. if + // capturing the contents of a shaped window. + const DesktopRegion* shape() const { return shape_.get(); } + void set_shape(DesktopRegion* shape) { shape_.reset(shape); } + + // Copies pixels from a buffer or another frame. |dest_rect| rect must lay + // within bounds of this frame. + void CopyPixelsFrom(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); + + // A helper to return the data pointer of a frame at the specified position. + uint8_t* GetFrameDataAtPos(const DesktopVector& pos) const; + + protected: + DesktopFrame(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory); + + const DesktopSize size_; + const int stride_; + + // 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_; + + DesktopRegion updated_region_; + DesktopVector dpi_; + int64_t capture_time_ms_; + rtc::scoped_ptr<DesktopRegion> shape_; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrame); +}; + +// A DesktopFrame that stores data in the heap. +class BasicDesktopFrame : public DesktopFrame { + public: + explicit BasicDesktopFrame(DesktopSize size); + ~BasicDesktopFrame() override; + + // Creates a BasicDesktopFrame that contains copy of |frame|. + static DesktopFrame* CopyOf(const DesktopFrame& frame); + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(BasicDesktopFrame); +}; + +// A DesktopFrame that stores data in shared memory. +class SharedMemoryDesktopFrame : public DesktopFrame { + public: + // Takes ownership of |shared_memory|. + SharedMemoryDesktopFrame(DesktopSize size, + int stride, + SharedMemory* shared_memory); + ~SharedMemoryDesktopFrame() override; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(SharedMemoryDesktopFrame); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_frame_win.cc b/webrtc/modules/desktop_capture/desktop_frame_win.cc new file mode 100644 index 0000000000..6b97b132d8 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_frame_win.cc @@ -0,0 +1,63 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_frame_win.h" + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +DesktopFrameWin::DesktopFrameWin(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory, + HBITMAP bitmap) + : DesktopFrame(size, stride, data, shared_memory), + bitmap_(bitmap), + owned_shared_memory_(shared_memory_) { +} + +DesktopFrameWin::~DesktopFrameWin() { + DeleteObject(bitmap_); +} + +// static +DesktopFrameWin* DesktopFrameWin::Create(DesktopSize size, + SharedMemory* shared_memory, + HDC hdc) { + int bytes_per_row = size.width() * kBytesPerPixel; + + // 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(); + + HANDLE section_handle = NULL; + if (shared_memory) + section_handle = shared_memory->handle(); + void* data = NULL; + HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &data, + section_handle, 0); + if (!bitmap) { + LOG(LS_WARNING) << "Failed to allocate new window frame " << GetLastError(); + delete shared_memory; + return NULL; + } + + return new DesktopFrameWin(size, bytes_per_row, + reinterpret_cast<uint8_t*>(data), + shared_memory, bitmap); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_frame_win.h b/webrtc/modules/desktop_capture/desktop_frame_win.h new file mode 100644 index 0000000000..15b5883c36 --- /dev/null +++ b/webrtc/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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ + +#include <windows.h> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/typedefs.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: + virtual ~DesktopFrameWin(); + static DesktopFrameWin* Create(DesktopSize size, + SharedMemory* shared_memory, + HDC hdc); + + HBITMAP bitmap() { return bitmap_; } + + private: + DesktopFrameWin(DesktopSize size, + int stride, + uint8_t* data, + SharedMemory* shared_memory, + HBITMAP bitmap); + + HBITMAP bitmap_; + rtc::scoped_ptr<SharedMemory> owned_shared_memory_; + + RTC_DISALLOW_COPY_AND_ASSIGN(DesktopFrameWin); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_geometry.cc b/webrtc/modules/desktop_capture/desktop_geometry.cc new file mode 100644 index 0000000000..1ff7c683c7 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_geometry.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 "webrtc/modules/desktop_capture/desktop_geometry.h" + +#include <algorithm> + +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::Translate(int32_t dx, int32_t dy) { + left_ += dx; + top_ += dy; + right_ += dx; + bottom_ += dy; +} + +} // namespace webrtc + diff --git a/webrtc/modules/desktop_capture/desktop_geometry.h b/webrtc/modules/desktop_capture/desktop_geometry.h new file mode 100644 index 0000000000..047eeec3d9 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_geometry.h @@ -0,0 +1,145 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.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()); + } + + 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 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_; } + + 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); + + // 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()); }; + + 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 // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_region.cc b/webrtc/modules/desktop_capture/desktop_region.cc new file mode 100644 index 0000000000..bc9972660a --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_region.cc @@ -0,0 +1,569 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_region.h" + +#include <assert.h> + +#include <algorithm> + +namespace webrtc { + +DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right) + : left(left), right(right) { +} + +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. + assert(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) { + assert(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(); + assert(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); + assert(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. + assert(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); + assert(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) { + assert(!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()) { + assert(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() { + assert(!IsAtEnd()); + + while (true) { + ++row_span_; + if (row_span_ == row_->second->spans.end()) { + previous_row_ = row_; + ++row_; + if (row_ != region_.rows_.end()) { + assert(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; + } + + assert(!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/webrtc/modules/desktop_capture/desktop_region.h b/webrtc/modules/desktop_capture/desktop_region.h new file mode 100644 index 0000000000..c86da56e17 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_region.h @@ -0,0 +1,167 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ + +#include <map> +#include <vector> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/typedefs.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 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(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 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 // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ + diff --git a/webrtc/modules/desktop_capture/desktop_region_unittest.cc b/webrtc/modules/desktop_capture/desktop_region_unittest.cc new file mode 100644 index 0000000000..3062f0b93c --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_region_unittest.cc @@ -0,0 +1,710 @@ +/* + * 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 "webrtc/modules/desktop_capture/desktop_region.h" + +#include <algorithm> + +#include "testing/gtest/include/gtest/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/webrtc/modules/desktop_capture/differ.cc b/webrtc/modules/desktop_capture/differ.cc new file mode 100644 index 0000000000..8140e612a1 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ.cc @@ -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 "webrtc/modules/desktop_capture/differ.h" + +#include "string.h" + +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +Differ::Differ(int width, int height, int bpp, int stride) { + // Dimensions of screen. + width_ = width; + height_ = height; + bytes_per_pixel_ = bpp; + bytes_per_row_ = stride; + + // Calc number of blocks (full and partial) required to cover entire image. + // One additional row/column is added as a boundary on the right & bottom. + diff_info_width_ = ((width_ + kBlockSize - 1) / kBlockSize) + 1; + diff_info_height_ = ((height_ + kBlockSize - 1) / kBlockSize) + 1; + diff_info_size_ = diff_info_width_ * diff_info_height_ * sizeof(bool); + diff_info_.reset(new bool[diff_info_size_]); +} + +Differ::~Differ() {} + +void Differ::CalcDirtyRegion(const uint8_t* prev_buffer, + const uint8_t* curr_buffer, + DesktopRegion* region) { + // Identify all the blocks that contain changed pixels. + MarkDirtyBlocks(prev_buffer, curr_buffer); + + // Now that we've identified the blocks that have changed, merge adjacent + // blocks to minimize the number of rects that we return. + MergeBlocks(region); +} + +void Differ::MarkDirtyBlocks(const uint8_t* prev_buffer, + const uint8_t* curr_buffer) { + memset(diff_info_.get(), 0, diff_info_size_); + + // Calc number of full blocks. + int x_full_blocks = width_ / kBlockSize; + int y_full_blocks = height_ / kBlockSize; + + // Calc size of partial blocks which may be present on right and bottom edge. + int partial_column_width = width_ - (x_full_blocks * kBlockSize); + int partial_row_height = height_ - (y_full_blocks * kBlockSize); + + // Offset from the start of one block-column to the next. + int block_x_offset = bytes_per_pixel_ * kBlockSize; + // Offset from the start of one block-row to the next. + int block_y_stride = (width_ * bytes_per_pixel_) * kBlockSize; + // Offset from the start of one diff_info row to the next. + int diff_info_stride = diff_info_width_ * sizeof(bool); + + const uint8_t* prev_block_row_start = prev_buffer; + const uint8_t* curr_block_row_start = curr_buffer; + bool* diff_info_row_start = diff_info_.get(); + + for (int y = 0; y < y_full_blocks; y++) { + const uint8_t* prev_block = prev_block_row_start; + const uint8_t* curr_block = curr_block_row_start; + bool* diff_info = diff_info_row_start; + + for (int x = 0; x < x_full_blocks; x++) { + // Mark this block as being modified so that it gets incorporated into + // a dirty rect. + *diff_info = BlockDifference(prev_block, curr_block, bytes_per_row_); + prev_block += block_x_offset; + curr_block += block_x_offset; + diff_info += sizeof(bool); + } + + // If there is a partial column at the end, handle it. + // This condition should rarely, if ever, occur. + if (partial_column_width != 0) { + *diff_info = !PartialBlocksEqual(prev_block, curr_block, bytes_per_row_, + partial_column_width, kBlockSize); + diff_info += sizeof(bool); + } + + // Update pointers for next row. + prev_block_row_start += block_y_stride; + curr_block_row_start += block_y_stride; + diff_info_row_start += diff_info_stride; + } + + // If the screen height is not a multiple of the block size, then this + // handles the last partial row. This situation is far more common than the + // 'partial column' case. + if (partial_row_height != 0) { + const uint8_t* prev_block = prev_block_row_start; + const uint8_t* curr_block = curr_block_row_start; + bool* diff_info = diff_info_row_start; + for (int x = 0; x < x_full_blocks; x++) { + *diff_info = !PartialBlocksEqual(prev_block, curr_block, + bytes_per_row_, + kBlockSize, partial_row_height); + prev_block += block_x_offset; + curr_block += block_x_offset; + diff_info += sizeof(bool); + } + if (partial_column_width != 0) { + *diff_info = !PartialBlocksEqual(prev_block, curr_block, bytes_per_row_, + partial_column_width, + partial_row_height); + diff_info += sizeof(bool); + } + } +} + +bool Differ::PartialBlocksEqual(const uint8_t* prev_buffer, + const uint8_t* curr_buffer, + int stride, int width, int height) { + int width_bytes = width * bytes_per_pixel_; + for (int y = 0; y < height; y++) { + if (memcmp(prev_buffer, curr_buffer, width_bytes) != 0) + return false; + prev_buffer += bytes_per_row_; + curr_buffer += bytes_per_row_; + } + return true; +} + +void Differ::MergeBlocks(DesktopRegion* region) { + region->Clear(); + + bool* diff_info_row_start = diff_info_.get(); + int diff_info_stride = diff_info_width_ * sizeof(bool); + + for (int y = 0; y < diff_info_height_; y++) { + bool* diff_info = diff_info_row_start; + for (int x = 0; x < diff_info_width_; x++) { + if (*diff_info) { + // We've found a modified block. Look at blocks to the right and below + // to group this block with as many others as we can. + int left = x * kBlockSize; + int top = y * kBlockSize; + int width = 1; + int height = 1; + *diff_info = false; + + // Group with blocks to the right. + // We can keep looking until we find an unchanged block because we + // have a boundary block which is never marked as having diffs. + bool* right = diff_info + 1; + while (*right) { + *right++ = false; + width++; + } + + // Group with blocks below. + // The entire width of blocks that we matched above much match for + // each row that we add. + bool* bottom = diff_info; + bool found_new_row; + do { + found_new_row = true; + bottom += diff_info_stride; + right = bottom; + for (int x2 = 0; x2 < width; x2++) { + if (!*right++) { + found_new_row = false; + } + } + + if (found_new_row) { + height++; + + // We need to go back and erase the diff markers so that we don't + // try to add these blocks a second time. + right = bottom; + for (int x2 = 0; x2 < width; x2++) { + *right++ = false; + } + } + } while (found_new_row); + + // Add rect to list of dirty rects. + width *= kBlockSize; + if (left + width > width_) { + width = width_ - left; + } + height *= kBlockSize; + if (top + height > height_) { + height = height_ - top; + } + region->AddRect(DesktopRect::MakeXYWH(left, top, width, height)); + } + + // Increment to next block in this row. + diff_info++; + } + + // Go to start of next row. + diff_info_row_start += diff_info_stride; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ.h b/webrtc/modules/desktop_capture/differ.h new file mode 100644 index 0000000000..b3b0e7c244 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ.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. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ + +#include <vector> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" + +namespace webrtc { + +// TODO(sergeyu): Simplify differ now that we are working with DesktopRegion. +// diff_info_ should no longer be needed, as we can put our data directly into +// the region that we are calculating. +// http://crbug.com/92379 +// TODO(sergeyu): Rename this class to something more sensible, e.g. +// ScreenCaptureFrameDifferencer. +class Differ { + public: + // Create a differ that operates on bitmaps with the specified width, height + // and bytes_per_pixel. + Differ(int width, int height, int bytes_per_pixel, int stride); + ~Differ(); + + int width() { return width_; } + int height() { return height_; } + int bytes_per_pixel() { return bytes_per_pixel_; } + int bytes_per_row() { return bytes_per_row_; } + + // Given the previous and current screen buffer, calculate the dirty region + // that encloses all of the changed pixels in the new screen. + void CalcDirtyRegion(const uint8_t* prev_buffer, const uint8_t* curr_buffer, + DesktopRegion* region); + + private: + // Allow tests to access our private parts. + friend class DifferTest; + + // Identify all of the blocks that contain changed pixels. + void MarkDirtyBlocks(const uint8_t* prev_buffer, const uint8_t* curr_buffer); + + // After the dirty blocks have been identified, this routine merges adjacent + // blocks into a region. + // The goal is to minimize the region that covers the dirty blocks. + void MergeBlocks(DesktopRegion* region); + + // Checks whether the upper-left portions of the buffers are equal. The size + // of the portion to check is specified by the |width| and |height| values. + // Note that if we force the capturer to always return images whose width and + // height are multiples of kBlockSize, then this will never be called. + bool PartialBlocksEqual(const uint8_t* prev_buffer, + const uint8_t* curr_buffer, + int stride, + int width, int height); + + // Dimensions of screen. + int width_; + int height_; + + // Number of bytes for each pixel in source and dest bitmap. + // (Yes, they must match.) + int bytes_per_pixel_; + + // Number of bytes in each row of the image (AKA: stride). + int bytes_per_row_; + + // Diff information for each block in the image. + rtc::scoped_ptr<bool[]> diff_info_; + + // Dimensions and total size of diff info array. + int diff_info_width_; + int diff_info_height_; + int diff_info_size_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Differ); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ diff --git a/webrtc/modules/desktop_capture/differ_block.cc b/webrtc/modules/desktop_capture/differ_block.cc new file mode 100644 index 0000000000..0dcb377411 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block.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 "webrtc/modules/desktop_capture/differ_block.h" + +#include <string.h> + +#include "build/build_config.h" +#include "webrtc/modules/desktop_capture/differ_block_sse2.h" +#include "webrtc/system_wrappers/include/cpu_features_wrapper.h" + +namespace webrtc { + +bool BlockDifference_C(const uint8_t* image1, + const uint8_t* image2, + int stride) { + int width_bytes = kBlockSize * kBytesPerPixel; + + for (int y = 0; y < kBlockSize; y++) { + if (memcmp(image1, image2, width_bytes) != 0) + return true; + image1 += stride; + image2 += stride; + } + return false; +} + +bool BlockDifference(const uint8_t* image1, + const uint8_t* image2, + int stride) { + static bool (*diff_proc)(const uint8_t*, const uint8_t*, int) = NULL; + + if (!diff_proc) { +#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // For ARM and MIPS processors, always use C version. + // TODO(hclam): Implement a NEON version. + diff_proc = &BlockDifference_C; +#else + bool have_sse2 = WebRtc_GetCPUInfo(kSSE2) != 0; + // For x86 processors, check if SSE2 is supported. + if (have_sse2 && kBlockSize == 32) { + diff_proc = &BlockDifference_SSE2_W32; + } else if (have_sse2 && kBlockSize == 16) { + diff_proc = &BlockDifference_SSE2_W16; + } else { + diff_proc = &BlockDifference_C; + } +#endif + } + + return diff_proc(image1, image2, stride); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ_block.h b/webrtc/modules/desktop_capture/differ_block.h new file mode 100644 index 0000000000..e1d487d68b --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block.h @@ -0,0 +1,33 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ +#define WEBRTC_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 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 // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.cc b/webrtc/modules/desktop_capture/differ_block_sse2.cc new file mode 100644 index 0000000000..8d35df2226 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_sse2.cc @@ -0,0 +1,120 @@ +/* + * 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 "webrtc/modules/desktop_capture/differ_block_sse2.h" + +#if defined(_MSC_VER) +#include <intrin.h> +#else +#include <mmintrin.h> +#include <emmintrin.h> +#endif + +#include "webrtc/modules/desktop_capture/differ_block.h" + +namespace webrtc { + +extern bool BlockDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2, + int stride) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + for (int y = 0; y < kBlockSize; ++y) { + 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); + int diff = _mm_cvtsi128_si32(sad); + if (diff) + return true; + image1 += stride; + image2 += stride; + } + return false; +} + +extern bool BlockDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2, + int stride) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + for (int y = 0; y < kBlockSize; ++y) { + 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); + int diff = _mm_cvtsi128_si32(sad); + if (diff) + return true; + image1 += stride; + image2 += stride; + } + return false; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.h b/webrtc/modules/desktop_capture/differ_block_sse2.h new file mode 100644 index 0000000000..90426dafab --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_sse2.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +// This header file is used only differ_block.h. It defines the SSE2 rountines +// for finding block difference. + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ + +#include <stdint.h> + +namespace webrtc { + +// Find block difference of dimension 16x16. +extern bool BlockDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2, + int stride); + +// Find block difference of dimension 32x32. +extern bool BlockDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2, + int stride); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ diff --git a/webrtc/modules/desktop_capture/differ_block_unittest.cc b/webrtc/modules/desktop_capture/differ_block_unittest.cc new file mode 100644 index 0000000000..df9f4d517a --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_unittest.cc @@ -0,0 +1,87 @@ +/* + * 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 "testing/gmock/include/gmock/gmock.h" +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/system_wrappers/include/ref_count.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/webrtc/modules/desktop_capture/differ_unittest.cc b/webrtc/modules/desktop_capture/differ_unittest.cc new file mode 100644 index 0000000000..642cb37448 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_unittest.cc @@ -0,0 +1,652 @@ +/* + * 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 "testing/gmock/include/gmock/gmock.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/differ_block.h" + +namespace webrtc { + +// 96x96 screen gives a 4x4 grid of blocks. +const int kScreenWidth= 96; +const int kScreenHeight = 96; + +// To test partial blocks, we need a width and height that are not multiples +// of 16 (or 32, depending on current block size). +const int kPartialScreenWidth = 70; +const int kPartialScreenHeight = 70; + +class DifferTest : public testing::Test { + public: + DifferTest() { + } + + protected: + void InitDiffer(int width, int height) { + width_ = width; + height_ = height; + bytes_per_pixel_ = kBytesPerPixel; + stride_ = (kBytesPerPixel * width); + buffer_size_ = width_ * height_ * bytes_per_pixel_; + + differ_.reset(new Differ(width_, height_, bytes_per_pixel_, stride_)); + + prev_.reset(new uint8_t[buffer_size_]); + memset(prev_.get(), 0, buffer_size_); + + curr_.reset(new uint8_t[buffer_size_]); + memset(curr_.get(), 0, buffer_size_); + } + + void ClearBuffer(uint8_t* buffer) { + memset(buffer, 0, buffer_size_); + } + + // Here in DifferTest so that tests can access private methods of Differ. + void MarkDirtyBlocks(const uint8_t* prev_buffer, const uint8_t* curr_buffer) { + differ_->MarkDirtyBlocks(prev_buffer, curr_buffer); + } + + void MergeBlocks(DesktopRegion* dirty) { + differ_->MergeBlocks(dirty); + } + + // Convenience method to count rectangles in a region. + int RegionRectCount(const DesktopRegion& region) { + int count = 0; + for (DesktopRegion::Iterator iter(region); + !iter.IsAtEnd(); iter.Advance()) { + ++count; + } + return count; + } + + // Convenience wrapper for Differ's DiffBlock that calculates the appropriate + // offset to the start of the desired block. + bool DiffBlock(int block_x, int block_y) { + // Offset from upper-left of buffer to upper-left of requested block. + int block_offset = ((block_y * stride_) + (block_x * bytes_per_pixel_)) + * kBlockSize; + return BlockDifference(prev_.get() + block_offset, + curr_.get() + block_offset, + stride_); + } + + // Write the pixel |value| into the specified block in the |buffer|. + // This is a convenience wrapper around WritePixel(). + void WriteBlockPixel(uint8_t* buffer, int block_x, int block_y, + int pixel_x, int pixel_y, uint32_t value) { + WritePixel(buffer, (block_x * kBlockSize) + pixel_x, + (block_y * kBlockSize) + pixel_y, value); + } + + // Write the test pixel |value| into the |buffer| at the specified |x|,|y| + // location. + // Only the low-order bytes from |value| are written (assuming little-endian). + // So, for |value| = 0xaabbccdd: + // If bytes_per_pixel = 4, then ddccbbaa will be written as the pixel value. + // If = 3, ddccbb + // If = 2, ddcc + // If = 1, dd + void WritePixel(uint8_t* buffer, int x, int y, uint32_t value) { + uint8_t* pixel = reinterpret_cast<uint8_t*>(&value); + buffer += (y * stride_) + (x * bytes_per_pixel_); + for (int b = bytes_per_pixel_ - 1; b >= 0; b--) { + *buffer++ = pixel[b]; + } + } + + // DiffInfo utility routines. + // These are here so that we don't have to make each DifferText_Xxx_Test + // class a friend class to Differ. + + // Clear out the entire |diff_info_| buffer. + void ClearDiffInfo() { + memset(differ_->diff_info_.get(), 0, differ_->diff_info_size_); + } + + // Get the value in the |diff_info_| array at (x,y). + bool GetDiffInfo(int x, int y) { + bool* diff_info = differ_->diff_info_.get(); + return diff_info[(y * GetDiffInfoWidth()) + x]; + } + + // Width of |diff_info_| array. + int GetDiffInfoWidth() { + return differ_->diff_info_width_; + } + + // Height of |diff_info_| array. + int GetDiffInfoHeight() { + return differ_->diff_info_height_; + } + + // Size of |diff_info_| array. + int GetDiffInfoSize() { + return differ_->diff_info_size_; + } + + void SetDiffInfo(int x, int y, bool value) { + bool* diff_info = differ_->diff_info_.get(); + diff_info[(y * GetDiffInfoWidth()) + x] = value; + } + + // Mark the range of blocks specified. + void MarkBlocks(int x_origin, int y_origin, int width, int height) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + SetDiffInfo(x_origin + x, y_origin + y, true); + } + } + } + + // Verify that |region| contains a rectangle defined by |x|, |y|, |width| and + // |height|. + // |x|, |y|, |width| and |height| are specified in block (not pixel) units. + bool CheckDirtyRegionContainsRect(const DesktopRegion& region, + int x, int y, + int width, int height) { + DesktopRect r = + DesktopRect::MakeXYWH(x * kBlockSize, y * kBlockSize, + width * kBlockSize, height * kBlockSize); + for (DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) { + if (i.rect().equals(r)) + return true; + } + return false; + } + + // Mark the range of blocks specified and then verify that they are + // merged correctly. + // Only one rectangular region of blocks can be checked with this routine. + bool MarkBlocksAndCheckMerge(int x_origin, int y_origin, + int width, int height) { + ClearDiffInfo(); + MarkBlocks(x_origin, y_origin, width, height); + + DesktopRegion dirty; + MergeBlocks(&dirty); + + + DesktopRect expected_rect = DesktopRect::MakeXYWH( + x_origin * kBlockSize, y_origin * kBlockSize, + width * kBlockSize, height * kBlockSize); + + // Verify that the region contains expected_rect and it's the only + // rectangle. + DesktopRegion::Iterator it(dirty); + return !it.IsAtEnd() && expected_rect.equals(it.rect()) && + (it.Advance(), it.IsAtEnd()); + } + + // The differ class we're testing. + rtc::scoped_ptr<Differ> differ_; + + // Screen/buffer info. + int width_; + int height_; + int bytes_per_pixel_; + int stride_; + + // Size of each screen buffer. + int buffer_size_; + + // Previous and current screen buffers. + rtc::scoped_ptr<uint8_t[]> prev_; + rtc::scoped_ptr<uint8_t[]> curr_; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(DifferTest); +}; + +TEST_F(DifferTest, Setup) { + InitDiffer(kScreenWidth, kScreenHeight); + // 96x96 pixels results in 3x3 array. Add 1 to each dimension as boundary. + // +---+---+---+---+ + // | o | o | o | _ | + // +---+---+---+---+ o = blocks mapped to screen pixels + // | o | o | o | _ | + // +---+---+---+---+ _ = boundary blocks + // | o | o | o | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + EXPECT_EQ(4, GetDiffInfoWidth()); + EXPECT_EQ(4, GetDiffInfoHeight()); + EXPECT_EQ(16, GetDiffInfoSize()); +} + +TEST_F(DifferTest, MarkDirtyBlocks_All) { + InitDiffer(kScreenWidth, kScreenHeight); + ClearDiffInfo(); + + // Update a pixel in each block. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + WriteBlockPixel(curr_.get(), x, y, 10, 10, 0xff00ff); + } + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure each block is marked as dirty. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_TRUE(GetDiffInfo(x, y)) + << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, MarkDirtyBlocks_Sampling) { + InitDiffer(kScreenWidth, kScreenHeight); + ClearDiffInfo(); + + // Update some pixels in image. + WriteBlockPixel(curr_.get(), 1, 0, 10, 10, 0xff00ff); + WriteBlockPixel(curr_.get(), 2, 1, 10, 10, 0xff00ff); + WriteBlockPixel(curr_.get(), 0, 2, 10, 10, 0xff00ff); + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure corresponding blocks are updated. + EXPECT_FALSE(GetDiffInfo(0, 0)); + EXPECT_FALSE(GetDiffInfo(0, 1)); + EXPECT_TRUE(GetDiffInfo(0, 2)); + EXPECT_TRUE(GetDiffInfo(1, 0)); + EXPECT_FALSE(GetDiffInfo(1, 1)); + EXPECT_FALSE(GetDiffInfo(1, 2)); + EXPECT_FALSE(GetDiffInfo(2, 0)); + EXPECT_TRUE(GetDiffInfo(2, 1)); + EXPECT_FALSE(GetDiffInfo(2, 2)); +} + +TEST_F(DifferTest, DiffBlock) { + InitDiffer(kScreenWidth, kScreenHeight); + + // Verify no differences at start. + EXPECT_FALSE(DiffBlock(0, 0)); + EXPECT_FALSE(DiffBlock(1, 1)); + + // Write new data into the 4 corners of the middle block and verify that + // neighboring blocks are not affected. + int max = kBlockSize - 1; + WriteBlockPixel(curr_.get(), 1, 1, 0, 0, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, 0, max, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, max, 0, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, max, max, 0xffffff); + EXPECT_FALSE(DiffBlock(0, 0)); + EXPECT_FALSE(DiffBlock(0, 1)); + EXPECT_FALSE(DiffBlock(0, 2)); + EXPECT_FALSE(DiffBlock(1, 0)); + EXPECT_TRUE(DiffBlock(1, 1)); // Only this block should change. + EXPECT_FALSE(DiffBlock(1, 2)); + EXPECT_FALSE(DiffBlock(2, 0)); + EXPECT_FALSE(DiffBlock(2, 1)); + EXPECT_FALSE(DiffBlock(2, 2)); +} + +TEST_F(DifferTest, Partial_Setup) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + // 70x70 pixels results in 3x3 array: 2x2 full blocks + partials around + // the edge. One more is added to each dimension as a boundary. + // +---+---+---+---+ + // | o | o | + | _ | + // +---+---+---+---+ o = blocks mapped to screen pixels + // | o | o | + | _ | + // +---+---+---+---+ + = partial blocks (top/left mapped to screen pixels) + // | + | + | + | _ | + // +---+---+---+---+ _ = boundary blocks + // | _ | _ | _ | _ | + // +---+---+---+---+ + EXPECT_EQ(4, GetDiffInfoWidth()); + EXPECT_EQ(4, GetDiffInfoHeight()); + EXPECT_EQ(16, GetDiffInfoSize()); +} + +TEST_F(DifferTest, Partial_FirstPixel) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + ClearDiffInfo(); + + // Update the first pixel in each block. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + WriteBlockPixel(curr_.get(), x, y, 0, 0, 0xff00ff); + } + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure each block is marked as dirty. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_TRUE(GetDiffInfo(x, y)) + << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, Partial_BorderPixel) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + ClearDiffInfo(); + + // Update the right/bottom border pixels. + for (int y = 0; y < height_; y++) { + WritePixel(curr_.get(), width_ - 1, y, 0xff00ff); + } + for (int x = 0; x < width_; x++) { + WritePixel(curr_.get(), x, height_ - 1, 0xff00ff); + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure last (partial) block in each row/column is marked as dirty. + int x_last = GetDiffInfoWidth() - 2; + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + EXPECT_TRUE(GetDiffInfo(x_last, y)) + << "when x = " << x_last << ", and y = " << y; + } + int y_last = GetDiffInfoHeight() - 2; + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_TRUE(GetDiffInfo(x, y_last)) + << "when x = " << x << ", and y = " << y_last; + } + // All other blocks are clean. + for (int y = 0; y < GetDiffInfoHeight() - 2; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 2; x++) { + EXPECT_FALSE(GetDiffInfo(x, y)) << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, MergeBlocks_Empty) { + InitDiffer(kScreenWidth, kScreenHeight); + + // No blocks marked: + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + + DesktopRegion dirty; + MergeBlocks(&dirty); + + EXPECT_TRUE(dirty.is_empty()); +} + +TEST_F(DifferTest, MergeBlocks_SingleBlock) { + InitDiffer(kScreenWidth, kScreenHeight); + // Mark a single block and make sure that there is a single merged + // rect with the correct bounds. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + ASSERT_TRUE(MarkBlocksAndCheckMerge(x, y, 1, 1)) << "x: " << x + << "y: " << y; + } + } +} + +TEST_F(DifferTest, MergeBlocks_BlockRow) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 1)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 1)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 2, 2, 1)); +} + +TEST_F(DifferTest, MergeBlocks_BlockColumn) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | | | _ | + // +---+---+---+---+ + // | X | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 1, 2)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | | _ | + // +---+---+---+---+ + // | | X | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 1, 2)); + + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(2, 0, 1, 3)); +} + +TEST_F(DifferTest, MergeBlocks_BlockRect) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 2)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 2, 2)); + + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 0, 2, 3)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 2)); + + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 3, 3)); +} + +// This tests marked regions that require more than 1 single dirty rect. +// The exact rects returned depend on the current implementation, so these +// may need to be updated if we modify how we merge blocks. +TEST_F(DifferTest, MergeBlocks_MultiRect) { + InitDiffer(kScreenWidth, kScreenHeight); + DesktopRegion dirty; + + // +---+---+---+---+ +---+---+---+ + // | | X | | _ | | | 0 | | + // +---+---+---+---+ +---+---+---+ + // | X | | | _ | | 1 | | | + // +---+---+---+---+ => +---+---+---+ + // | | | X | _ | | | | 2 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(1, 0, 1, 1); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 2, 1, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(3, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 0, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 2, 1, 1)); + + // +---+---+---+---+ +---+---+---+ + // | | | X | _ | | | | 0 | + // +---+---+---+---+ +---+---+---+ + // | X | X | X | _ | | 1 1 1 | + // +---+---+---+---+ => + + + // | X | X | X | _ | | 1 1 1 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(2, 0, 1, 1); + MarkBlocks(0, 1, 3, 2); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(2, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 0, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 3, 2)); + + // +---+---+---+---+ +---+---+---+ + // | | | | _ | | | | | + // +---+---+---+---+ +---+---+---+ + // | X | | X | _ | | 0 | | 1 | + // +---+---+---+---+ => +---+---+---+ + // | X | X | X | _ | | 2 2 2 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 1, 1, 1); + MarkBlocks(0, 2, 3, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(3, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1)); + + // +---+---+---+---+ +---+---+---+ + // | X | X | X | _ | | 0 0 0 | + // +---+---+---+---+ +---+---+---+ + // | X | | X | _ | | 1 | | 2 | + // +---+---+---+---+ => +---+---+---+ + // | X | X | X | _ | | 3 3 3 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 0, 3, 1); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 1, 1, 1); + MarkBlocks(0, 2, 3, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(4, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 3, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1)); + + // +---+---+---+---+ +---+---+---+ + // | X | X | | _ | | 0 0 | | + // +---+---+---+---+ + +---+ + // | X | X | | _ | | 0 0 | | + // +---+---+---+---+ => +---+---+---+ + // | | X | | _ | | | 1 | | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 0, 2, 2); + MarkBlocks(1, 2, 1, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(2, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1)); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.h b/webrtc/modules/desktop_capture/mac/desktop_configuration.h new file mode 100644 index 0000000000..bb2339bb0f --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.h @@ -0,0 +1,78 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ + +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> +#include <vector> + +#include "webrtc/typedefs.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Describes the configuration of a specific display. +struct MacDisplayConfiguration { + MacDisplayConfiguration(); + + // Cocoa identifier for this display. + CGDirectDisplayID id; + + // 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; +}; + +typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations; + +// Describes the configuration of the whole desktop. +struct MacDesktopConfiguration { + // Used to request bottom-up or top-down coordinates. + enum Origin { BottomLeftOrigin, TopLeftOrigin }; + + MacDesktopConfiguration(); + ~MacDesktopConfiguration(); + + // Returns the desktop & display configurations in Cocoa-style "bottom-up" + // (the origin is the bottom-left of the primary monitor, and coordinates + // increase as you move up the screen) or Carbon-style "top-down" coordinates. + static MacDesktopConfiguration GetCurrent(Origin origin); + + // Returns true if the given desktop configuration equals this one. + bool Equals(const MacDesktopConfiguration& other); + + // Returns the pointer to the display configuration with the specified id. + 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; + + // Configurations of the displays making up the desktop area. + MacDisplayConfigurations displays; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.mm b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm new file mode 100644 index 0000000000..9e483e5b81 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm @@ -0,0 +1,180 @@ +/* + * 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 "webrtc/modules/desktop_capture/mac/desktop_configuration.h" + +#include <math.h> +#include <algorithm> +#include <Cocoa/Cocoa.h> + +#include "webrtc/system_wrappers/include/logging.h" + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 + +@interface NSScreen (LionAPI) +- (CGFloat)backingScaleFactor; +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +#endif // MAC_OS_X_VERSION_10_7 + +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))); +} + +DesktopRect JoinRects(const DesktopRect& a, + const DesktopRect& b) { + return DesktopRect::MakeLTRB( + std::min(a.left(), b.left()), + std::min(a.top(), b.top()), + std::max(a.right(), b.right()), + std::max(a.bottom(), b.bottom())); +} + +// Inverts the position of |rect| from bottom-up coordinates to top-down, +// relative to |bounds|. +void InvertRectYOrigin(const DesktopRect& bounds, + DesktopRect* rect) { + assert(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; + } + + return display_config; +} + +} // namespace + +MacDisplayConfiguration::MacDisplayConfiguration() + : id(0), + dip_to_pixel_scale(1.0f) { +} + +MacDesktopConfiguration::MacDesktopConfiguration() + : dip_to_pixel_scale(1.0f) { +} + +MacDesktopConfiguration::~MacDesktopConfiguration() { +} + +// static +MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) { + MacDesktopConfiguration desktop_config; + + NSArray* screens = [NSScreen screens]; + assert(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 = + JoinRects(desktop_config.bounds, display_config.bounds); + desktop_config.pixel_bounds = + JoinRects(desktop_config.pixel_bounds, 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; +} + +// Finds the display configuration with the specified id. +const MacDisplayConfiguration* +MacDesktopConfiguration::FindDisplayConfigurationById( + CGDirectDisplayID id) { + for (MacDisplayConfigurations::const_iterator it = displays.begin(); + it != displays.end(); ++it) { + if (it->id == id) + return &(*it); + } + return NULL; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc new file mode 100644 index 0000000000..eeccecb6cc --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.cc @@ -0,0 +1,91 @@ +/* + * 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 "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" + +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +// The amount of time allowed for displays to reconfigure. +static const int64_t kDisplayConfigurationEventTimeoutMs = 10 * 1000; + +DesktopConfigurationMonitor::DesktopConfigurationMonitor() + : ref_count_(0), + display_configuration_capture_event_(EventWrapper::Create()) { + CGError err = CGDisplayRegisterReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; + abort(); + } + display_configuration_capture_event_->Set(); + + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); +} + +DesktopConfigurationMonitor::~DesktopConfigurationMonitor() { + CGError err = CGDisplayRemoveReconfigurationCallback( + DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; +} + +void DesktopConfigurationMonitor::Lock() { + if (!display_configuration_capture_event_->Wait( + kDisplayConfigurationEventTimeoutMs)) { + LOG_F(LS_ERROR) << "Event wait timed out."; + abort(); + } +} + +void DesktopConfigurationMonitor::Unlock() { + display_configuration_capture_event_->Set(); +} + +// static +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) { + if (flags & kCGDisplayBeginConfigurationFlag) { + if (reconfiguring_displays_.empty()) { + // If this is the first display to start reconfiguring then wait on + // |display_configuration_capture_event_| to block the capture thread + // from accessing display memory until the reconfiguration completes. + if (!display_configuration_capture_event_->Wait( + kDisplayConfigurationEventTimeoutMs)) { + LOG_F(LS_ERROR) << "Event wait timed out."; + abort(); + } + } + reconfiguring_displays_.insert(display); + } else { + reconfiguring_displays_.erase(display); + if (reconfiguring_displays_.empty()) { + desktop_configuration_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + display_configuration_capture_event_->Set(); + } + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h new file mode 100644 index 0000000000..b2fa81a416 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h @@ -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. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include <set> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/system_wrappers/include/atomic32.h" + +namespace webrtc { + +class EventWrapper; + +// The class provides functions to synchronize capturing and display +// reconfiguring across threads, and the up-to-date MacDesktopConfiguration. +class DesktopConfigurationMonitor { + public: + DesktopConfigurationMonitor(); + // Acquires a lock on the current configuration. + void Lock(); + // Releases the lock previously acquired. + void Unlock(); + // Returns the current desktop configuration. Should only be called when the + // lock has been acquired. + const MacDesktopConfiguration& desktop_configuration() { + return desktop_configuration_; + } + + void AddRef() { ++ref_count_; } + void Release() { + if (--ref_count_ == 0) + delete this; + } + + private: + static void DisplaysReconfiguredCallback(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void *user_parameter); + ~DesktopConfigurationMonitor(); + + void DisplaysReconfigured(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags); + + Atomic32 ref_count_; + std::set<CGDirectDisplayID> reconfiguring_displays_; + MacDesktopConfiguration desktop_configuration_; + rtc::scoped_ptr<EventWrapper> display_configuration_capture_event_; + + RTC_DISALLOW_COPY_AND_ASSIGN(DesktopConfigurationMonitor); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_ diff --git a/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc new file mode 100644 index 0000000000..84579c4149 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc @@ -0,0 +1,244 @@ +/* + * 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 "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" + +#include <assert.h> +#include <libproc.h> +#include <string> + +#include "webrtc/base/macutils.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/mac/window_list_utils.h" +#include "webrtc/system_wrappers/include/logging.h" + + +namespace webrtc { + +namespace { + +const int64_t kUpdateIntervalMs = 500; + +// Returns true if the window is minimized. +bool IsWindowMinimized(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + bool minimized = false; + + if (window_array && CFArrayGetCount(window_array)) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0)); + CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + + minimized = !on_screen; + } + + CFRelease(window_id_array); + CFRelease(window_array); + + return minimized; +} + +// 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; +} + +std::string GetWindowTitle(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + std::string title; + + if (window_array && CFArrayGetCount(window_array)) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0)); + CFStringRef title_ref = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + + if (title_ref) + rtc::ToUtf8(title_ref, &title); + } + CFRelease(window_id_array); + CFRelease(window_array); + + return title; +} + +int GetWindowOwnerPid(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + int pid = 0; + + if (window_array && CFArrayGetCount(window_array)) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, 0)); + CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + + if (pid_ref) + CFNumberGetValue(pid_ref, kCFNumberIntType, &pid); + } + CFRelease(window_id_array); + CFRelease(window_array); + + return pid; +} + +// Returns the window that is full-screen and has the same title and owner pid +// as the input window. +CGWindowID FindFullScreenWindowWithSamePidAndTitle(CGWindowID id) { + int pid = GetWindowOwnerPid(id); + std::string title = GetWindowTitle(id); + + // Only get on screen, non-desktop windows. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return kCGNullWindowID; + + CGWindowID full_screen_window = kCGNullWindowID; + + 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)); + CFStringRef window_title_ref = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id_ref = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_pid_ref = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowOwnerPID)); + + if (!window_title_ref || !window_id_ref || !window_pid_ref) + continue; + + int window_pid = 0; + CFNumberGetValue(window_pid_ref, kCFNumberIntType, &window_pid); + if (window_pid != pid) + continue; + + std::string window_title; + if (!rtc::ToUtf8(window_title_ref, &window_title) || + window_title != title) { + continue; + } + + CGWindowID window_id; + CFNumberGetValue(window_id_ref, kCFNumberIntType, &window_id); + if (IsWindowFullScreen(desktop_config, window)) { + full_screen_window = window_id; + break; + } + } + + CFRelease(window_array); + return full_screen_window; +} + +bool IsChromeWindow(CGWindowID id) { + int pid = GetWindowOwnerPid(id); + char buffer[PROC_PIDPATHINFO_MAXSIZE]; + int path_length = proc_pidpath(pid, buffer, sizeof(buffer)); + if (path_length <= 0) + return false; + + const char* last_slash = strrchr(buffer, '/'); + std::string name(last_slash ? last_slash + 1 : buffer); + return name.find("Google Chrome") == 0 || name == "Chromium"; +} + +} // namespace + +FullScreenChromeWindowDetector::FullScreenChromeWindowDetector() + : ref_count_(0) {} + +FullScreenChromeWindowDetector::~FullScreenChromeWindowDetector() {} + +CGWindowID FullScreenChromeWindowDetector::FindFullScreenWindow( + CGWindowID original_window) { + if (!IsChromeWindow(original_window) || !IsWindowMinimized(original_window)) + return kCGNullWindowID; + + CGWindowID full_screen_window_id = + FindFullScreenWindowWithSamePidAndTitle(original_window); + + if (full_screen_window_id == kCGNullWindowID) + return kCGNullWindowID; + + for (WindowCapturer::WindowList::iterator it = previous_window_list_.begin(); + it != previous_window_list_.end(); ++it) { + if (static_cast<CGWindowID>(it->id) != full_screen_window_id) + continue; + + int64_t time_interval = + (TickTime::Now() - last_udpate_time_).Milliseconds(); + LOG(LS_WARNING) << "The full-screen window exists in the list, " + << "which was updated " << time_interval << "ms ago."; + return kCGNullWindowID; + } + + return full_screen_window_id; +} + +void FullScreenChromeWindowDetector::UpdateWindowListIfNeeded( + CGWindowID original_window) { + if (IsChromeWindow(original_window) && + (TickTime::Now() - last_udpate_time_).Milliseconds() + > kUpdateIntervalMs) { + previous_window_list_.clear(); + previous_window_list_.swap(current_window_list_); + + // No need to update the window list when the window is minimized. + if (IsWindowMinimized(original_window)) { + previous_window_list_.clear(); + return; + } + + GetWindowList(¤t_window_list_); + last_udpate_time_ = TickTime::Now(); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h new file mode 100644 index 0000000000..4e6008966e --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h @@ -0,0 +1,69 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_ + +#include <ApplicationServices/ApplicationServices.h> + +#include "webrtc/modules/desktop_capture/window_capturer.h" +#include "webrtc/system_wrappers/include/atomic32.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { + +// This is a work around for the Chrome tab full-screen behavior: Chrome +// creates a new window in full-screen mode to show a tab full-screen and +// minimizes the old window. To continue capturing in this case, we try to +// find the new full-screen window using these criteria: +// 0. The original shared window is minimized. +// 1. The original shared window's owner application name is "Google Chrome". +// 2. The original window and the new window have the same title and owner +// pid. +// 3. The new window is full-screen. +// 4. The new window didn't exist at least 500 millisecond ago. + +class FullScreenChromeWindowDetector { + public: + FullScreenChromeWindowDetector(); + + void AddRef() { ++ref_count_; } + void Release() { + if (--ref_count_ == 0) + delete this; + } + + // Returns the full-screen window in place of the original window if all the + // criteria are met, or kCGNullWindowID if no such window found. + CGWindowID FindFullScreenWindow(CGWindowID original_window); + + // The caller should call this function periodically, no less than twice per + // second. + void UpdateWindowListIfNeeded(CGWindowID original_window); + + private: + ~FullScreenChromeWindowDetector(); + + Atomic32 ref_count_; + + // We cache the last two results of the window list, so + // |previous_window_list_| is taken at least 500ms before the next Capture() + // call. If we only save the last result, we may get false positive (i.e. + // full-screen window exists in the list) if Capture() is called too soon. + WindowCapturer::WindowList current_window_list_; + WindowCapturer::WindowList previous_window_list_; + TickTime last_udpate_time_; + + RTC_DISALLOW_COPY_AND_ASSIGN(FullScreenChromeWindowDetector); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_FULL_SCREEN_CHROME_WINDOW_DETECTOR_H_ diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc new file mode 100644 index 0000000000..5dcbc52a9e --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc @@ -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. + */ + +#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h" + +#include <stddef.h> + +namespace webrtc { + +ScopedPixelBufferObject::ScopedPixelBufferObject() + : cgl_context_(NULL), + pixel_buffer_object_(0) { +} + +ScopedPixelBufferObject::~ScopedPixelBufferObject() { + Release(); +} + +bool ScopedPixelBufferObject::Init(CGLContextObj cgl_context, + int size_in_bytes) { + cgl_context_ = cgl_context; + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glGenBuffersARB(1, &pixel_buffer_object_); + if (glGetError() == GL_NO_ERROR) { + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_); + glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size_in_bytes, NULL, + GL_STREAM_READ_ARB); + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + if (glGetError() != GL_NO_ERROR) { + Release(); + } + } else { + cgl_context_ = NULL; + pixel_buffer_object_ = 0; + } + return pixel_buffer_object_ != 0; +} + +void ScopedPixelBufferObject::Release() { + if (pixel_buffer_object_) { + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glDeleteBuffersARB(1, &pixel_buffer_object_); + cgl_context_ = NULL; + pixel_buffer_object_ = 0; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h new file mode 100644 index 0000000000..a32d470954 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h @@ -0,0 +1,41 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ + +#include <OpenGL/CGLMacro.h> +#include <OpenGL/OpenGL.h> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class ScopedPixelBufferObject { + public: + ScopedPixelBufferObject(); + ~ScopedPixelBufferObject(); + + bool Init(CGLContextObj cgl_context, int size_in_bytes); + void Release(); + + GLuint get() const { return pixel_buffer_object_; } + + private: + CGLContextObj cgl_context_; + GLuint pixel_buffer_object_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScopedPixelBufferObject); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.cc b/webrtc/modules/desktop_capture/mac/window_list_utils.cc new file mode 100644 index 0000000000..0c3eaa3abd --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/window_list_utils.cc @@ -0,0 +1,62 @@ +/* + * 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 "webrtc/modules/desktop_capture/mac/window_list_utils.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include "webrtc/base/macutils.h" + +namespace webrtc { + +bool GetWindowList(WindowCapturer::WindowList* windows) { + // Only get on screen, non-desktop windows. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return false; + + // 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)); + CFStringRef window_title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_layer = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (window_title && window_id && window_layer) { + // Skip windows with layer=0 (menu, dock). + int layer; + CFNumberGetValue(window_layer, kCFNumberIntType, &layer); + if (layer != 0) + continue; + + int id; + CFNumberGetValue(window_id, kCFNumberIntType, &id); + WindowCapturer::Window window; + window.id = id; + if (!rtc::ToUtf8(window_title, &(window.title)) || + window.title.empty()) { + continue; + } + windows->push_back(window); + } + } + + CFRelease(window_array); + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.h b/webrtc/modules/desktop_capture/mac/window_list_utils.h new file mode 100644 index 0000000000..7be38506bb --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/window_list_utils.h @@ -0,0 +1,24 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_ + +#include "webrtc/modules/desktop_capture/window_capturer.h" + +namespace webrtc { + +// A helper function to get the on-screen windows. +bool GetWindowList(WindowCapturer::WindowList* windows); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_ + diff --git a/webrtc/modules/desktop_capture/mouse_cursor.cc b/webrtc/modules/desktop_capture/mouse_cursor.cc new file mode 100644 index 0000000000..22a9c0ee8c --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor.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 "webrtc/modules/desktop_capture/mouse_cursor.h" + +#include <assert.h> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +MouseCursor::MouseCursor() {} + +MouseCursor::MouseCursor(DesktopFrame* image, const DesktopVector& hotspot) + : image_(image), + hotspot_(hotspot) { + assert(0 <= hotspot_.x() && hotspot_.x() <= image_->size().width()); + assert(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/webrtc/modules/desktop_capture/mouse_cursor.h b/webrtc/modules/desktop_capture/mouse_cursor.h new file mode 100644 index 0000000000..dd5dc0eb44 --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor.h @@ -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. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class DesktopFrame; + +class MouseCursor { + public: + MouseCursor(); + + // Takes ownership of |image|. |hotspot| must be within |image| boundaries. + MouseCursor(DesktopFrame* image, const DesktopVector& hotspot); + + ~MouseCursor(); + + 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: + rtc::scoped_ptr<DesktopFrame> image_; + DesktopVector hotspot_; + + RTC_DISALLOW_COPY_AND_ASSIGN(MouseCursor); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_H_ diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor.h b/webrtc/modules/desktop_capture/mouse_cursor_monitor.h new file mode 100644 index 0000000000..24dfe72dfa --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor.h @@ -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. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ + +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class DesktopCaptureOptions; +class DesktopFrame; +class MouseCursor; + +// Captures mouse shape and position. +class MouseCursorMonitor { + public: + 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. + virtual void OnMouseCursorPosition(CursorState state, + const DesktopVector& position) = 0; + + protected: + virtual ~Callback() {} + }; + + virtual ~MouseCursorMonitor() {} + + // Creates a capturer that notifies of mouse cursor events while the cursor is + // over the specified window. + static MouseCursorMonitor* CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window); + + // Creates a capturer that monitors the mouse cursor shape and position across + // the entire desktop. + // + // TODO(sergeyu): Provide a way to select a specific screen. + static MouseCursorMonitor* CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen); + + // 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 // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_MONITOR_H_ + diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm b/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm new file mode 100644 index 0000000000..6033127ae1 --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm @@ -0,0 +1,296 @@ +/* + * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +#include <assert.h> +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> + +#include "webrtc/base/macutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +class MouseCursorMonitorMac : public MouseCursorMonitor { + public: + MouseCursorMonitorMac(const DesktopCaptureOptions& options, + CGWindowID window_id, + ScreenId screen_id); + virtual ~MouseCursorMonitorMac(); + + 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(); + + rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; + CGWindowID window_id_; + ScreenId screen_id_; + Callback* callback_; + Mode mode_; + rtc::scoped_ptr<MouseCursor> last_cursor_; + rtc::scoped_refptr<FullScreenChromeWindowDetector> + full_screen_chrome_window_detector_; +}; + +MouseCursorMonitorMac::MouseCursorMonitorMac( + const DesktopCaptureOptions& options, + CGWindowID window_id, + ScreenId screen_id) + : configuration_monitor_(options.configuration_monitor()), + window_id_(window_id), + screen_id_(screen_id), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + full_screen_chrome_window_detector_( + options.full_screen_chrome_window_detector()) { + assert(window_id == kCGNullWindowID || screen_id == kInvalidScreenId); + if (screen_id != kInvalidScreenId && + rtc::GetOSVersionName() < rtc::kMacOSLion) { + // Single screen capture is not supported on pre OS X 10.7. + screen_id_ = kFullDesktopScreenId; + } +} + +MouseCursorMonitorMac::~MouseCursorMonitorMac() {} + +void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) { + assert(!callback_); + assert(callback); + + callback_ = callback; + mode_ = mode; +} + +void MouseCursorMonitorMac::Capture() { + assert(callback_); + + CaptureImage(); + + if (mode_ != SHAPE_AND_POSITION) + return; + + CursorState state = INSIDE; + + CGEventRef event = CGEventCreate(NULL); + CGPoint gc_position = CGEventGetLocation(event); + CFRelease(event); + + DesktopVector position(gc_position.x, gc_position.y); + + configuration_monitor_->Lock(); + MacDesktopConfiguration configuration = + configuration_monitor_->desktop_configuration(); + configuration_monitor_->Unlock(); + float scale = 1.0f; + + // Find the dpi to physical pixel scale for the screen where the mouse cursor + // is. + for (MacDisplayConfigurations::iterator it = configuration.displays.begin(); + it != configuration.displays.end(); ++it) { + if (it->bounds.Contains(position)) { + scale = it->dip_to_pixel_scale; + break; + } + } + // If we are capturing cursor for a specific window then we need to figure out + // if the current mouse position is covered by another window and also adjust + // |position| to make it relative to the window origin. + if (window_id_ != kCGNullWindowID) { + CGWindowID on_screen_window = window_id_; + if (full_screen_chrome_window_detector_) { + CGWindowID full_screen_window = + full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_); + + if (full_screen_window != kCGNullWindowID) + on_screen_window = full_screen_window; + } + + // Get list of windows that may be covering parts of |on_screen_window|. + // CGWindowListCopyWindowInfo() returns windows in order from front to back, + // so |on_screen_window| is expected to be the last in the list. + CFArrayRef window_array = + CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | + kCGWindowListOptionOnScreenAboveWindow | + kCGWindowListOptionIncludingWindow, + on_screen_window); + bool found_window = false; + if (window_array) { + CFIndex count = CFArrayGetCount(window_array); + for (CFIndex i = 0; i < count; ++i) { + CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( + CFArrayGetValueAtIndex(window_array, i)); + + // Skip the Dock window. Dock window covers the whole screen, but it is + // transparent. + CFStringRef window_name = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + if (window_name && CFStringCompare(window_name, CFSTR("Dock"), 0) == 0) + continue; + + CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>( + CFDictionaryGetValue(window, kCGWindowBounds)); + CFNumberRef window_number = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + + if (window_bounds && window_number) { + CGRect gc_window_rect; + if (!CGRectMakeWithDictionaryRepresentation(window_bounds, + &gc_window_rect)) { + continue; + } + DesktopRect window_rect = + DesktopRect::MakeXYWH(gc_window_rect.origin.x, + gc_window_rect.origin.y, + gc_window_rect.size.width, + gc_window_rect.size.height); + + CGWindowID window_id; + if (!CFNumberGetValue(window_number, kCFNumberIntType, &window_id)) + continue; + + if (window_id == on_screen_window) { + found_window = true; + if (!window_rect.Contains(position)) + state = OUTSIDE; + position = position.subtract(window_rect.top_left()); + + assert(i == count - 1); + break; + } else if (window_rect.Contains(position)) { + state = OUTSIDE; + position.set(-1, -1); + break; + } + } + } + CFRelease(window_array); + } + if (!found_window) { + // If we failed to get list of windows or the window wasn't in the list + // pretend that the cursor is outside the window. This can happen, e.g. if + // the window was closed. + state = OUTSIDE; + position.set(-1, -1); + } + } else { + assert(screen_id_ >= kFullDesktopScreenId); + if (screen_id_ != kFullDesktopScreenId) { + // For single screen capturing, convert the position to relative to the + // target screen. + const MacDisplayConfiguration* config = + configuration.FindDisplayConfigurationById( + static_cast<CGDirectDisplayID>(screen_id_)); + if (config) { + if (!config->pixel_bounds.Contains(position)) + state = OUTSIDE; + position = position.subtract(config->bounds.top_left()); + } else { + // The target screen is no longer valid. + state = OUTSIDE; + position.set(-1, -1); + } + } else { + position.subtract(configuration.bounds.top_left()); + } + } + if (state == INSIDE) { + // Convert Density Independent Pixel to physical pixel. + position = DesktopVector(round(position.x() * scale), + round(position.y() * scale)); + } + callback_->OnMouseCursorPosition(state, position); +} + +void MouseCursorMonitorMac::CaptureImage() { + NSCursor* nscursor = [NSCursor currentSystemCursor]; + + NSImage* nsimage = [nscursor image]; + NSSize nssize = [nsimage size]; + DesktopSize size(nssize.width, nssize.height); + NSPoint nshotspot = [nscursor hotSpot]; + DesktopVector hotspot( + std::max(0, std::min(size.width(), static_cast<int>(nshotspot.x))), + std::max(0, std::min(size.height(), static_cast<int>(nshotspot.y)))); + CGImageRef cg_image = + [nsimage CGImageForProposedRect:NULL context:nil hints:nil]; + if (!cg_image) + return; + + if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 || + CGImageGetBytesPerRow(cg_image) != + static_cast<size_t>(DesktopFrame::kBytesPerPixel * size.width()) || + CGImageGetBitsPerComponent(cg_image) != 8) { + return; + } + + CGDataProviderRef provider = CGImageGetDataProvider(cg_image); + CFDataRef image_data_ref = CGDataProviderCopyData(provider); + if (image_data_ref == NULL) + return; + + const uint8_t* src_data = + reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref)); + + // Compare the cursor with the previous one. + if (last_cursor_.get() && + last_cursor_->image()->size().equals(size) && + last_cursor_->hotspot().equals(hotspot) && + memcmp(last_cursor_->image()->data(), src_data, + last_cursor_->image()->stride() * size.height()) == 0) { + CFRelease(image_data_ref); + return; + } + + // Create a MouseCursor that describes the cursor and pass it to + // the client. + rtc::scoped_ptr<DesktopFrame> image( + new BasicDesktopFrame(DesktopSize(size.width(), size.height()))); + memcpy(image->data(), src_data, + size.width() * size.height() * DesktopFrame::kBytesPerPixel); + + CFRelease(image_data_ref); + + rtc::scoped_ptr<MouseCursor> cursor( + new MouseCursor(image.release(), hotspot)); + last_cursor_.reset(MouseCursor::CopyOf(*cursor)); + + 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); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc new file mode 100644 index 0000000000..3a632cc0d9 --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_null.cc @@ -0,0 +1,29 @@ +/* + * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +#include <stddef.h> + +namespace webrtc { + +MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( + const DesktopCaptureOptions& options, + WindowId window) { + return NULL; +} + +MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + return NULL; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc new file mode 100644 index 0000000000..5d5a81caba --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_unittest.cc @@ -0,0 +1,131 @@ +/* + * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/window_capturer.h" +#include "webrtc/system_wrappers/include/logging.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(MouseCursorMonitor::CursorState state, + const DesktopVector& position) override { + state_ = state; + position_ = position; + position_received_ = true; + } + + protected: + rtc::scoped_ptr<MouseCursor> cursor_image_; + MouseCursorMonitor::CursorState state_; + 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)) { + rtc::scoped_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForScreen( + DesktopCaptureOptions::CreateDefault(), + webrtc::kFullDesktopScreenId)); + assert(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_); + EXPECT_EQ(MouseCursorMonitor::INSIDE, state_); +} + +TEST_F(MouseCursorMonitorTest, MAYBE(FromWindow)) { + DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault(); + + // First get list of windows. + rtc::scoped_ptr<WindowCapturer> window_capturer( + WindowCapturer::Create(options)); + + // If window capturing is not supported then skip this test. + if (!window_capturer.get()) + return; + + WindowCapturer::WindowList windows; + EXPECT_TRUE(window_capturer->GetWindowList(&windows)); + + // Iterate over all windows and try capturing mouse cursor for each of them. + for (size_t i = 0; i < windows.size(); ++i) { + cursor_image_.reset(); + position_received_ = false; + + rtc::scoped_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForWindow( + DesktopCaptureOptions::CreateDefault(), windows[i].id)); + assert(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)) { + rtc::scoped_ptr<MouseCursorMonitor> capturer( + MouseCursorMonitor::CreateForScreen( + DesktopCaptureOptions::CreateDefault(), + webrtc::kFullDesktopScreenId)); + assert(capturer.get()); + capturer->Init(this, MouseCursorMonitor::SHAPE_ONLY); + capturer->Capture(); + + EXPECT_TRUE(cursor_image_.get()); + EXPECT_FALSE(position_received_); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc new file mode 100644 index 0000000000..54dfba2032 --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_win.cc @@ -0,0 +1,173 @@ +/* + * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +#include <assert.h> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/window_capture_utils.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +class MouseCursorMonitorWin : public MouseCursorMonitor { + public: + explicit MouseCursorMonitorWin(HWND window); + explicit MouseCursorMonitorWin(ScreenId screen); + virtual ~MouseCursorMonitorWin(); + + 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_; + + HCURSOR last_cursor_; +}; + +MouseCursorMonitorWin::MouseCursorMonitorWin(HWND window) + : window_(window), + screen_(kInvalidScreenId), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + desktop_dc_(NULL), + last_cursor_(NULL) { +} + +MouseCursorMonitorWin::MouseCursorMonitorWin(ScreenId screen) + : window_(NULL), + screen_(screen), + callback_(NULL), + mode_(SHAPE_AND_POSITION), + desktop_dc_(NULL), + last_cursor_(NULL) { + assert(screen >= kFullDesktopScreenId); +} + +MouseCursorMonitorWin::~MouseCursorMonitorWin() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); +} + +void MouseCursorMonitorWin::Init(Callback* callback, Mode mode) { + assert(!callback_); + assert(callback); + + callback_ = callback; + mode_ = mode; + + desktop_dc_ = GetDC(NULL); +} + +void MouseCursorMonitorWin::Capture() { + assert(callback_); + + CURSORINFO cursor_info; + cursor_info.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&cursor_info)) { + LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); + return; + } + + if (last_cursor_ != cursor_info.hCursor) { + last_cursor_ = cursor_info.hCursor; + // Note that |cursor_info.hCursor| does not need to be freed. + rtc::scoped_ptr<MouseCursor> cursor( + CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); + if (cursor.get()) + callback_->OnMouseCursor(cursor.release()); + } + + if (mode_ != SHAPE_AND_POSITION) + return; + + 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_, &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 { + assert(screen_ != kInvalidScreenId); + DesktopRect rect = GetScreenRect(); + if (inside) + inside = rect.Contains(position); + position = position.subtract(rect.top_left()); + } + + callback_->OnMouseCursorPosition(inside ? INSIDE : OUTSIDE, position); +} + +DesktopRect MouseCursorMonitorWin::GetScreenRect() { + assert(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); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc new file mode 100644 index 0000000000..fc6d76eb7a --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_monitor_x11.cc @@ -0,0 +1,232 @@ +/* + * 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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" + +#include <X11/extensions/Xfixes.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" +#include "webrtc/system_wrappers/include/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) { + 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)) { + 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 { + +class MouseCursorMonitorX11 : public MouseCursorMonitor, + public SharedXDisplay::XEventHandler { + public: + MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window); + virtual ~MouseCursorMonitorX11(); + + 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_; + + rtc::scoped_ptr<MouseCursor> cursor_shape_; +}; + +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) {} + +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 once per instance of MouseCursorMonitor. + assert(!callback_); + assert(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. + XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask); + x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this); + + CaptureCursor(); + } else { + LOG(LS_INFO) << "X server does not support XFixes."; + } +} + +void MouseCursorMonitorX11::Capture() { + assert(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; + } + + callback_->OnMouseCursorPosition(state, + webrtc::DesktopVector(win_x, win_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() { + assert(have_xfixes_); + + XFixesCursorImage* img; + { + XErrorTrap error_trap(display()); + img = XFixesGetCursorImage(display()); + if (!img || error_trap.GetLastErrorAndDisable() != 0) + return; + } + + rtc::scoped_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; + 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* MouseCursorMonitor::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* MouseCursorMonitor::CreateForScreen( + const DesktopCaptureOptions& options, + ScreenId screen) { + if (!options.x_display()) + return NULL; + return new MouseCursorMonitorX11( + options, DefaultRootWindow(options.x_display()->display())); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mouse_cursor_shape.h b/webrtc/modules/desktop_capture/mouse_cursor_shape.h new file mode 100644 index 0000000000..57120a0b3f --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_shape.h @@ -0,0 +1,17 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ + +// This file is no longer needed, but some code in chromium still includes it. +// TODO(sergeyu): Cleanup dependencies in chromium and remove this file. + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc new file mode 100644 index 0000000000..94d8a27b13 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc @@ -0,0 +1,44 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" + +#include <assert.h> +#include <algorithm> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +ScreenCaptureFrameQueue::ScreenCaptureFrameQueue() : current_(0) {} + +ScreenCaptureFrameQueue::~ScreenCaptureFrameQueue() {} + +void ScreenCaptureFrameQueue::MoveToNextFrame() { + current_ = (current_ + 1) % kQueueLength; + + // Verify that the frame is not shared, i.e. that consumer has released it + // before attempting to capture again. + assert(!frames_[current_].get() || !frames_[current_]->IsShared()); +} + +void ScreenCaptureFrameQueue::ReplaceCurrentFrame(DesktopFrame* frame) { + frames_[current_].reset(SharedDesktopFrame::Wrap(frame)); +} + +void ScreenCaptureFrameQueue::Reset() { + for (int i = 0; i < kQueueLength; ++i) + frames_[i].reset(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.h b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h new file mode 100644 index 0000000000..6cd9e3bfc8 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h @@ -0,0 +1,74 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +class DesktopFrame; +} // namespace webrtc + +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). +class ScreenCaptureFrameQueue { + public: + ScreenCaptureFrameQueue(); + ~ScreenCaptureFrameQueue(); + + // Moves to the next frame in the queue, moving the 'current' frame to become + // the 'previous' one. + void MoveToNextFrame(); + + // 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(DesktopFrame* 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(); + + SharedDesktopFrame* current_frame() const { + return frames_[current_].get(); + } + + SharedDesktopFrame* previous_frame() const { + return frames_[(current_ + kQueueLength - 1) % kQueueLength].get(); + } + + private: + // Index of the current frame. + int current_; + + static const int kQueueLength = 2; + rtc::scoped_ptr<SharedDesktopFrame> frames_[kQueueLength]; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameQueue); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer.cc b/webrtc/modules/desktop_capture/screen_capturer.cc new file mode 100644 index 0000000000..97f69d3baf --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer.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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" + +namespace webrtc { + +ScreenCapturer* ScreenCapturer::Create() { + return Create(DesktopCaptureOptions::CreateDefault()); +} + +#if defined(WEBRTC_LINUX) +ScreenCapturer* ScreenCapturer::CreateWithXDamage( + bool use_update_notifications) { + DesktopCaptureOptions options; + options.set_use_update_notifications(use_update_notifications); + return Create(options); +} +#elif defined(WEBRTC_WIN) +ScreenCapturer* ScreenCapturer::CreateWithDisableAero(bool disable_effects) { + DesktopCaptureOptions options; + options.set_disable_effects(disable_effects); + return Create(options); +} +#endif + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer.h b/webrtc/modules/desktop_capture/screen_capturer.h new file mode 100644 index 0000000000..fbfb0e5898 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer.h @@ -0,0 +1,93 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ + +#include <vector> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class DesktopCaptureOptions; + +// Class used to capture video frames asynchronously. +// +// The full capture sequence is as follows: +// +// (1) Start +// This is when pre-capture steps are executed, such as flagging the +// display to prevent it from sleeping during a session. +// +// (2) CaptureFrame +// This is where the bits for the invalid rects are packaged up and sent +// to the encoder. +// A screen capture is performed if needed. For example, Windows requires +// a capture to calculate the diff from the previous screen, whereas the +// Mac version does not. +// +// Implementation has to ensure the following guarantees: +// 1. Double buffering +// Since data can be read while another capture action is happening. +class ScreenCapturer : public DesktopCapturer { + public: + // Use a struct to represent a screen although it has only an id for now, + // because we may want to add more fields (e.g. description) in the future. + struct Screen { + ScreenId id; + }; + typedef std::vector<Screen> ScreenList; + + // TODO(sergeyu): Remove this class once all dependencies are removed from + // chromium. + class MouseShapeObserver { + }; + + virtual ~ScreenCapturer() {} + + // Creates platform-specific capturer. + // + // TODO(sergeyu): Remove all Create() methods except the first one. + // crbug.com/172183 + static ScreenCapturer* Create(const DesktopCaptureOptions& options); + static ScreenCapturer* Create(); + +#if defined(WEBRTC_LINUX) + // Creates platform-specific capturer and instructs it whether it should use + // X DAMAGE support. + static ScreenCapturer* CreateWithXDamage(bool use_x_damage); +#elif defined(WEBRTC_WIN) + // Creates Windows-specific capturer and instructs it whether or not to + // disable desktop compositing. + static ScreenCapturer* CreateWithDisableAero(bool disable_aero); +#endif // defined(WEBRTC_WIN) + + // TODO(sergeyu): Remove this method once all dependencies are removed from + // chromium. + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) {}; + + // Get the list of screens (not containing kFullDesktopScreenId). Returns + // false in case of a failure. + virtual bool GetScreenList(ScreenList* screens) = 0; + + // Select the screen to be captured. Returns false in case of a failure (e.g. + // if there is no screen with the specified id). If this is never called, the + // full desktop is captured. + virtual bool SelectScreen(ScreenId id) = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.cc b/webrtc/modules/desktop_capture/screen_capturer_helper.cc new file mode 100644 index 0000000000..fa7096d24d --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper.cc @@ -0,0 +1,104 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer_helper.h" + +#include <assert.h> +#include <algorithm> + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +ScreenCapturerHelper::ScreenCapturerHelper() + : invalid_region_lock_(RWLockWrapper::CreateRWLock()), + log_grid_size_(0) { +} + +ScreenCapturerHelper::~ScreenCapturerHelper() { +} + +void ScreenCapturerHelper::ClearInvalidRegion() { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.Clear(); +} + +void ScreenCapturerHelper::InvalidateRegion( + const DesktopRegion& invalid_region) { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.AddRegion(invalid_region); +} + +void ScreenCapturerHelper::InvalidateScreen(const DesktopSize& size) { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.AddRect(DesktopRect::MakeSize(size)); +} + +void ScreenCapturerHelper::TakeInvalidRegion( + DesktopRegion* invalid_region) { + invalid_region->Clear(); + + { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + 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) { + assert(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/webrtc/modules/desktop_capture/screen_capturer_helper.h b/webrtc/modules/desktop_capture/screen_capturer_helper.h new file mode 100644 index 0000000000..da1f2bfeeb --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper.h @@ -0,0 +1,88 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/system_wrappers/include/rw_lock_wrapper.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(); + ~ScreenCapturerHelper(); + + // 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_; + + // A lock protecting |invalid_region_| across threads. + rtc::scoped_ptr<RWLockWrapper> invalid_region_lock_; + + // 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_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerHelper); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc new file mode 100644 index 0000000000..1ebc0afd18 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc @@ -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. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.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/webrtc/modules/desktop_capture/screen_capturer_mac.mm b/webrtc/modules/desktop_capture/screen_capturer_mac.mm new file mode 100644 index 0000000000..a0f0e5464e --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm @@ -0,0 +1,982 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include <stddef.h> +#include <set> + +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <dlfcn.h> +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <OpenGL/CGLMacro.h> +#include <OpenGL/OpenGL.h> + +#include "webrtc/base/macutils.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" +#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { + +namespace { + +// Definitions used to dynamic-link to deprecated OS 10.6 functions. +const char* kApplicationServicesLibraryName = + "/System/Library/Frameworks/ApplicationServices.framework/" + "ApplicationServices"; +typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID); +typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID); +typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID); +const char* kOpenGlLibraryName = + "/System/Library/Frameworks/OpenGL.framework/OpenGL"; +typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj); + +// 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 NULL; + + CFArrayRef all_windows = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + if (!all_windows) + return NULL; + + CFMutableArrayRef returned_array = CFArrayCreateMutable( + NULL, CFArrayGetCount(all_windows), NULL); + + 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 = NULL; + } + 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(NULL, reinterpret_cast<const void **>(&ids), 1, NULL); + 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, + CFDataRef* data_ref) { + 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(); + + CGImageRef excluded_image = CGWindowListCreateImageFromArray( + window_bounds, window_list, kCGWindowImageDefault); + + CGDataProviderRef provider = CGImageGetDataProvider(excluded_image); + *data_ref = CGDataProviderCopyData(provider); + assert(*data_ref); + return excluded_image; +} + +// A class to perform video frame capturing for mac. +class ScreenCapturerMac : public ScreenCapturer { + public: + explicit ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor); + virtual ~ScreenCapturerMac(); + + bool Init(); + + // Overridden from ScreenCapturer: + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + void SetExcludedWindow(WindowId window) override; + bool GetScreenList(ScreenList* screens) override; + bool SelectScreen(ScreenId id) override; + + private: + void GlBlitFast(const DesktopFrame& frame, + const DesktopRegion& region); + void GlBlitSlow(const DesktopFrame& frame); + void CgBlitPreLion(const DesktopFrame& frame, + const DesktopRegion& region); + // Returns false if the selected screen is no longer valid. + bool CgBlitPostLion(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); + void ScreenUpdateMove(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect *rect_array); + static void ScreenRefreshCallback(CGRectCount count, + const CGRect *rect_array, + void *user_parameter); + static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect *rect_array, + void *user_parameter); + void ReleaseBuffers(); + + DesktopFrame* CreateFrame(); + + Callback* callback_; + + CGLContextObj cgl_context_; + ScopedPixelBufferObject pixel_buffer_object_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue 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_; + + // 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_; + + // 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_; + + // Power management assertion to prevent the screen from sleeping. + IOPMAssertionID power_assertion_id_display_; + + // Power management assertion to indicate that the user is active. + IOPMAssertionID power_assertion_id_user_; + + // Dynamically link to deprecated APIs for Mac OS X 10.6 support. + void* app_services_library_; + CGDisplayBaseAddressFunc cg_display_base_address_; + CGDisplayBytesPerRowFunc cg_display_bytes_per_row_; + CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_; + void* opengl_library_; + CGLSetFullScreenFunc cgl_set_full_screen_; + + CGWindowID excluded_window_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); +}; + +// DesktopFrame wrapper that flips wrapped frame upside down by inverting +// stride. +class InvertedDesktopFrame : public DesktopFrame { + public: + // Takes ownership of |frame|. + InvertedDesktopFrame(DesktopFrame* frame) + : DesktopFrame( + frame->size(), -frame->stride(), + frame->data() + (frame->size().height() - 1) * frame->stride(), + frame->shared_memory()), + original_frame_(frame) { + set_dpi(frame->dpi()); + set_capture_time_ms(frame->capture_time_ms()); + mutable_updated_region()->Swap(frame->mutable_updated_region()); + } + virtual ~InvertedDesktopFrame() {} + + private: + rtc::scoped_ptr<DesktopFrame> original_frame_; + + RTC_DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame); +}; + +ScreenCapturerMac::ScreenCapturerMac( + rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor) + : callback_(NULL), + cgl_context_(NULL), + current_display_(0), + dip_to_pixel_scale_(1.0f), + desktop_config_monitor_(desktop_config_monitor), + power_assertion_id_display_(kIOPMNullAssertionID), + power_assertion_id_user_(kIOPMNullAssertionID), + app_services_library_(NULL), + cg_display_base_address_(NULL), + cg_display_bytes_per_row_(NULL), + cg_display_bits_per_pixel_(NULL), + opengl_library_(NULL), + cgl_set_full_screen_(NULL), + excluded_window_(0) { +} + +ScreenCapturerMac::~ScreenCapturerMac() { + if (power_assertion_id_display_ != kIOPMNullAssertionID) { + IOPMAssertionRelease(power_assertion_id_display_); + power_assertion_id_display_ = kIOPMNullAssertionID; + } + if (power_assertion_id_user_ != kIOPMNullAssertionID) { + IOPMAssertionRelease(power_assertion_id_user_); + power_assertion_id_user_ = kIOPMNullAssertionID; + } + + ReleaseBuffers(); + UnregisterRefreshAndMoveHandlers(); + dlclose(app_services_library_); + dlclose(opengl_library_); +} + +bool ScreenCapturerMac::Init() { + if (!RegisterRefreshAndMoveHandlers()) { + return false; + } + desktop_config_monitor_->Lock(); + desktop_config_ = desktop_config_monitor_->desktop_configuration(); + desktop_config_monitor_->Unlock(); + ScreenConfigurationChanged(); + return true; +} + +void ScreenCapturerMac::ReleaseBuffers() { + if (cgl_context_) { + pixel_buffer_object_.Release(); + CGLDestroyContext(cgl_context_); + cgl_context_ = NULL; + } + // 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; + + // Create power management assertions to wake the display and prevent it from + // going to sleep on user idle. + // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above + // instead of the following two assertions. + IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, + CFSTR("Chrome Remote Desktop connection active"), + &power_assertion_id_display_); + // This assertion ensures that the display is woken up if it already asleep + // (as used by Apple Remote Desktop). + IOPMAssertionCreateWithName(CFSTR("UserIsActive"), + kIOPMAssertionLevelOn, + CFSTR("Chrome Remote Desktop connection active"), + &power_assertion_id_user_); +} + +void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + desktop_config_monitor_->Lock(); + MacDesktopConfiguration new_config = + desktop_config_monitor_->desktop_configuration(); + if (!desktop_config_.Equals(new_config)) { + 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(CreateFrame()); + + DesktopFrame* current_frame = queue_.current_frame(); + + bool flip = false; // GL capturers need flipping. + if (rtc::GetOSVersionName() >= rtc::kMacOSLion) { + // Lion requires us to use their new APIs for doing screen capture. These + // APIS currently crash on 10.6.8 if there is no monitor attached. + if (!CgBlitPostLion(*current_frame, region)) { + desktop_config_monitor_->Unlock(); + callback_->OnCaptureCompleted(NULL); + return; + } + } else if (cgl_context_) { + flip = true; + if (pixel_buffer_object_.get() != 0) { + GlBlitFast(*current_frame, region); + } else { + // See comment in ScopedPixelBufferObject::Init about why the slow + // path is always used on 10.5. + GlBlitSlow(*current_frame); + } + } else { + CgBlitPreLion(*current_frame, region); + } + + DesktopFrame* new_frame = queue_.current_frame()->Share(); + *new_frame->mutable_updated_region() = region; + + if (flip) + new_frame = new InvertedDesktopFrame(new_frame); + + 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( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(new_frame); +} + +void ScreenCapturerMac::SetExcludedWindow(WindowId window) { + excluded_window_ = window; +} + +bool ScreenCapturerMac::GetScreenList(ScreenList* screens) { + assert(screens->size() == 0); + if (rtc::GetOSVersionName() < rtc::kMacOSLion) { + // Single monitor cast is not supported on pre OS X 10.7. + Screen screen; + screen.id = kFullDesktopScreenId; + screens->push_back(screen); + return true; + } + + for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin(); + it != desktop_config_.displays.end(); ++it) { + Screen screen; + screen.id = static_cast<ScreenId>(it->id); + screens->push_back(screen); + } + return true; +} + +bool ScreenCapturerMac::SelectScreen(ScreenId id) { + if (rtc::GetOSVersionName() < rtc::kMacOSLion) { + // Ignore the screen selection on unsupported OS. + assert(!current_display_); + return id == kFullDesktopScreenId; + } + + 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; +} + +void ScreenCapturerMac::GlBlitFast(const DesktopFrame& frame, + const DesktopRegion& region) { + // Clip to the size of our current screen. + DesktopRect clip_rect = DesktopRect::MakeSize(frame.size()); + if (queue_.previous_frame()) { + // We are doing double buffer for the capture data so we just need to copy + // the invalid region from the previous capture in the current buffer. + // TODO(hclam): We can reduce the amount of copying here by subtracting + // |capturer_helper_|s region from |last_invalid_region_|. + // http://crbug.com/92354 + + // Since the image obtained from OpenGL is upside-down, need to do some + // magic here to copy the correct rectangle. + const int y_offset = (frame.size().height() - 1) * frame.stride(); + for (DesktopRegion::Iterator i(last_invalid_region_); + !i.IsAtEnd(); i.Advance()) { + DesktopRect copy_rect = i.rect(); + copy_rect.IntersectWith(clip_rect); + if (!copy_rect.is_empty()) { + CopyRect(queue_.previous_frame()->data() + y_offset, + -frame.stride(), + frame.data() + y_offset, + -frame.stride(), + DesktopFrame::kBytesPerPixel, + copy_rect); + } + } + } + last_invalid_region_ = region; + + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get()); + glReadPixels(0, 0, frame.size().width(), frame.size().height(), GL_BGRA, + GL_UNSIGNED_BYTE, 0); + GLubyte* ptr = static_cast<GLubyte*>( + glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB)); + if (ptr == NULL) { + // If the buffer can't be mapped, assume that it's no longer valid and + // release it. + pixel_buffer_object_.Release(); + } else { + // Copy only from the dirty rects. Since the image obtained from OpenGL is + // upside-down we need to do some magic here to copy the correct rectangle. + const int y_offset = (frame.size().height() - 1) * frame.stride(); + for (DesktopRegion::Iterator i(region); + !i.IsAtEnd(); i.Advance()) { + DesktopRect copy_rect = i.rect(); + copy_rect.IntersectWith(clip_rect); + if (!copy_rect.is_empty()) { + CopyRect(ptr + y_offset, + -frame.stride(), + frame.data() + y_offset, + -frame.stride(), + DesktopFrame::kBytesPerPixel, + copy_rect); + } + } + } + if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) { + // If glUnmapBuffer returns false, then the contents of the data store are + // undefined. This might be because the screen mode has changed, in which + // case it will be recreated in ScreenConfigurationChanged, but releasing + // the object here is the best option. Capturing will fall back on + // GlBlitSlow until such time as the pixel buffer object is recreated. + pixel_buffer_object_.Release(); + } + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); +} + +void ScreenCapturerMac::GlBlitSlow(const DesktopFrame& frame) { + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glReadBuffer(GL_FRONT); + glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); + glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment. + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + // Read a block of pixels from the frame buffer. + glReadPixels(0, 0, frame.size().width(), frame.size().height(), + GL_BGRA, GL_UNSIGNED_BYTE, frame.data()); + glPopClientAttrib(); +} + +void ScreenCapturerMac::CgBlitPreLion(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()); + } + + for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { + const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; + + // Use deprecated APIs to determine the display buffer layout. + assert(cg_display_base_address_ && cg_display_bytes_per_row_ && + cg_display_bits_per_pixel_); + uint8_t* display_base_address = reinterpret_cast<uint8_t*>( + (*cg_display_base_address_)(display_config.id)); + assert(display_base_address); + int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_config.id); + int src_bytes_per_pixel = + (*cg_display_bits_per_pixel_)(display_config.id) / 8; + + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-desktop_config_.pixel_bounds.left(), + -desktop_config_.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()); + + // Calculate where in the output buffer the display's origin is. + uint8_t* out_ptr = frame.data() + + (display_bounds.left() * src_bytes_per_pixel) + + (display_bounds.top() * frame.stride()); + + // Copy the dirty region from the display buffer into our desktop buffer. + for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + i.rect()); + } + } +} + +bool ScreenCapturerMac::CgBlitPostLion(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 { + 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 = NULL; + CFDataRef excluded_window_region_data = NULL; + 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, + &excluded_window_region_data); + } + } + + // Create an image containing a snapshot of the display. + CGImageRef image = CGDisplayCreateImage(display_config.id); + if (image == NULL) + continue; + + // 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); + int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; + + // Calculate where in the output buffer the display's origin is. + uint8_t* out_ptr = frame.data() + + (display_bounds.left() * src_bytes_per_pixel) + + (display_bounds.top() * frame.stride()); + + // Copy the dirty region from the display buffer into our desktop buffer. + for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + i.rect()); + } + + // Copy the region of the excluded window to the frame. + if (excluded_image) { + assert(excluded_window_region_data); + display_base_address = CFDataGetBytePtr(excluded_window_region_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()); + out_ptr = frame.data() + + (window_bounds_relative_to_desktop.left() * src_bytes_per_pixel) + + (window_bounds_relative_to_desktop.top() * frame.stride()); + + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + DesktopRect::MakeSize(excluded_window_bounds.size())); + CFRelease(excluded_window_region_data); + CFRelease(excluded_image); + } + + CFRelease(data); + CFRelease(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(); + + // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's + // contents. Although the API exists in OS 10.6, it crashes the caller if + // the machine has no monitor connected, so we fall back to depcreated APIs + // when running on 10.6. + if (rtc::GetOSVersionName() >= rtc::kMacOSLion) { + LOG(LS_INFO) << "Using CgBlitPostLion."; + // No need for any OpenGL support on Lion + return; + } + + // Dynamically link to the deprecated pre-Lion capture APIs. + app_services_library_ = dlopen(kApplicationServicesLibraryName, + RTLD_LAZY); + if (!app_services_library_) { + LOG_F(LS_ERROR) << "Failed to open " << kApplicationServicesLibraryName; + abort(); + } + + opengl_library_ = dlopen(kOpenGlLibraryName, RTLD_LAZY); + if (!opengl_library_) { + LOG_F(LS_ERROR) << "Failed to open " << kOpenGlLibraryName; + abort(); + } + + cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>( + dlsym(app_services_library_, "CGDisplayBaseAddress")); + cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>( + dlsym(app_services_library_, "CGDisplayBytesPerRow")); + cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>( + dlsym(app_services_library_, "CGDisplayBitsPerPixel")); + cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>( + dlsym(opengl_library_, "CGLSetFullScreen")); + if (!(cg_display_base_address_ && cg_display_bytes_per_row_ && + cg_display_bits_per_pixel_ && cgl_set_full_screen_)) { + LOG_F(LS_ERROR); + abort(); + } + + if (desktop_config_.displays.size() > 1) { + LOG(LS_INFO) << "Using CgBlitPreLion (Multi-monitor)."; + return; + } + + CGDirectDisplayID mainDevice = CGMainDisplayID(); + if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { + LOG(LS_INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; + return; + } + + LOG(LS_INFO) << "Using GlBlit"; + + CGLPixelFormatAttribute attributes[] = { + // This function does an early return if GetOSVersionName() >= kMacOSLion, + // this code only runs on 10.6 and can be deleted once 10.6 support is + // dropped. So just keep using kCGLPFAFullScreen even though it was + // deprecated in 10.6 -- it's still functional there, and it's not used on + // newer OS X versions. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + kCGLPFAFullScreen, +#pragma clang diagnostic pop + kCGLPFADisplayMask, + (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), + (CGLPixelFormatAttribute)0 + }; + CGLPixelFormatObj pixel_format = NULL; + GLint matching_pixel_format_count = 0; + CGLError err = CGLChoosePixelFormat(attributes, + &pixel_format, + &matching_pixel_format_count); + assert(err == kCGLNoError); + err = CGLCreateContext(pixel_format, NULL, &cgl_context_); + assert(err == kCGLNoError); + CGLDestroyPixelFormat(pixel_format); + (*cgl_set_full_screen_)(cgl_context_); + CGLSetCurrentContext(cgl_context_); + + size_t buffer_size = screen_pixel_bounds_.width() * + screen_pixel_bounds_.height() * + sizeof(uint32_t); + pixel_buffer_object_.Init(cgl_context_, buffer_size); +} + +bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { + CGError err = CGRegisterScreenRefreshCallback( + ScreenCapturerMac::ScreenRefreshCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGRegisterScreenRefreshCallback " << err; + return false; + } + + err = CGScreenRegisterMoveCallback( + ScreenCapturerMac::ScreenUpdateMoveCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGScreenRegisterMoveCallback " << err; + return false; + } + + return true; +} + +void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { + CGUnregisterScreenRefreshCallback( + ScreenCapturerMac::ScreenRefreshCallback, this); + CGScreenUnregisterMoveCallback( + ScreenCapturerMac::ScreenUpdateMoveCallback, this); +} + +void ScreenCapturerMac::ScreenRefresh(CGRectCount count, + const CGRect* rect_array) { + if (screen_pixel_bounds_.is_empty()) + return; + + DesktopRegion region; + DesktopVector translate_vector = + DesktopVector().subtract(screen_pixel_bounds_.top_left()); + for (CGRectCount i = 0; i < count; ++i) { + // Convert from Density-Independent Pixel to physical pixel coordinates. + DesktopRect rect = ScaleAndRoundCGRect(rect_array[i], dip_to_pixel_scale_); + // Translate from local desktop to capturer framebuffer coordinates. + rect.Translate(translate_vector); + region.AddRect(rect); + } + + helper_.InvalidateRegion(region); +} + +void ScreenCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect* rect_array) { + // Translate |rect_array| to identify the move's destination. + CGRect refresh_rects[count]; + for (CGRectCount i = 0; i < count; ++i) { + refresh_rects[i] = CGRectOffset(rect_array[i], delta.dX, delta.dY); + } + + // Currently we just treat move events the same as refreshes. + ScreenRefresh(count, refresh_rects); +} + +void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count, + const CGRect* rect_array, + void* user_parameter) { + ScreenCapturerMac* capturer = + reinterpret_cast<ScreenCapturerMac*>(user_parameter); + if (capturer->screen_pixel_bounds_.is_empty()) + capturer->ScreenConfigurationChanged(); + capturer->ScreenRefresh(count, rect_array); +} + +void ScreenCapturerMac::ScreenUpdateMoveCallback( + CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect* rect_array, + void* user_parameter) { + ScreenCapturerMac* capturer = + reinterpret_cast<ScreenCapturerMac*>(user_parameter); + capturer->ScreenUpdateMove(delta, count, rect_array); +} + +DesktopFrame* ScreenCapturerMac::CreateFrame() { + rtc::scoped_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.release(); +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { + if (!options.configuration_monitor()) + return NULL; + + rtc::scoped_ptr<ScreenCapturerMac> capturer( + new ScreenCapturerMac(options.configuration_monitor())); + if (!capturer->Init()) + capturer.reset(); + return capturer.release(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc new file mode 100644 index 0000000000..e3d037b1e9 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc @@ -0,0 +1,97 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include <ostream> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/screen_capturer_mock_objects.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(DesktopFrame* frame); + + // Verifies that a rectangle explicitly marked as dirty is propagated + // correctly. + void CaptureDoneCallback2(DesktopFrame* frame); + + protected: + void SetUp() override { capturer_.reset(ScreenCapturer::Create()); } + + rtc::scoped_ptr<ScreenCapturer> capturer_; + MockScreenCapturerCallback callback_; +}; + +void ScreenCapturerMacTest::CaptureDoneCallback1( + DesktopFrame* frame) { + rtc::scoped_ptr<DesktopFrame> owned_frame(frame); + + 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( + DesktopFrame* frame) { + rtc::scoped_ptr<DesktopFrame> owned_frame(frame); + + 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_, OnCaptureCompleted(_)) + .Times(2) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback1)) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback2)); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Return(static_cast<SharedMemory*>(NULL))); + + SCOPED_TRACE(""); + capturer_->Start(&callback_); + + // Check that we get an initial full-screen updated. + capturer_->Capture(DesktopRegion()); + + // Check that subsequent dirty rects are propagated correctly. + capturer_->Capture(DesktopRegion()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h new file mode 100644 index 0000000000..8b83f41252 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h @@ -0,0 +1,47 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +class MockScreenCapturer : public ScreenCapturer { + public: + MockScreenCapturer() {} + virtual ~MockScreenCapturer() {} + + MOCK_METHOD1(Start, void(Callback* callback)); + MOCK_METHOD1(Capture, void(const DesktopRegion& region)); + MOCK_METHOD1(GetScreenList, bool(ScreenList* screens)); + MOCK_METHOD1(SelectScreen, bool(ScreenId id)); + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(MockScreenCapturer); +}; + +class MockScreenCapturerCallback : public ScreenCapturer::Callback { + public: + MockScreenCapturerCallback() {} + virtual ~MockScreenCapturerCallback() {} + + MOCK_METHOD1(CreateSharedMemory, SharedMemory*(size_t)); + MOCK_METHOD1(OnCaptureCompleted, void(DesktopFrame*)); + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(MockScreenCapturerCallback); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_null.cc b/webrtc/modules/desktop_capture/screen_capturer_null.cc new file mode 100644 index 0000000000..a0bc7f13ea --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_null.cc @@ -0,0 +1,20 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +// static +ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { + return NULL; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc new file mode 100644 index 0000000000..a3cf6d93cc --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -0,0 +1,142 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/screen_capturer_mock_objects.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::SaveArg; + +const int kTestSharedMemoryId = 123; + +namespace webrtc { + +class ScreenCapturerTest : public testing::Test { + public: + SharedMemory* CreateSharedMemory(size_t size); + + void SetUp() override { + capturer_.reset( + ScreenCapturer::Create(DesktopCaptureOptions::CreateDefault())); + } + + protected: + rtc::scoped_ptr<ScreenCapturer> capturer_; + MockScreenCapturerCallback callback_; +}; + +class FakeSharedMemory : public SharedMemory { + public: + FakeSharedMemory(char* buffer, size_t size) + : SharedMemory(buffer, size, 0, kTestSharedMemoryId), + buffer_(buffer) { + } + virtual ~FakeSharedMemory() { + delete[] buffer_; + } + private: + char* buffer_; + RTC_DISALLOW_COPY_AND_ASSIGN(FakeSharedMemory); +}; + +SharedMemory* ScreenCapturerTest::CreateSharedMemory(size_t size) { + return new FakeSharedMemory(new char[size], size); +} + +TEST_F(ScreenCapturerTest, GetScreenListAndSelectScreen) { + webrtc::ScreenCapturer::ScreenList screens; + EXPECT_TRUE(capturer_->GetScreenList(&screens)); + for(webrtc::ScreenCapturer::ScreenList::iterator it = screens.begin(); + it != screens.end(); ++it) { + EXPECT_TRUE(capturer_->SelectScreen(it->id)); + } +} + +TEST_F(ScreenCapturerTest, StartCapturer) { + capturer_->Start(&callback_); +} + +TEST_F(ScreenCapturerTest, Capture) { + // Assume that Start() treats the screen as invalid initially. + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)) + .WillOnce(SaveArg<0>(&frame)); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Return(static_cast<SharedMemory*>(NULL))); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + + 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()); + + delete frame; +} + +#if defined(WEBRTC_WIN) + +TEST_F(ScreenCapturerTest, UseSharedBuffers) { + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)) + .WillOnce(SaveArg<0>(&frame)); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &ScreenCapturerTest::CreateSharedMemory)); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + + ASSERT_TRUE(frame); + ASSERT_TRUE(frame->shared_memory()); + EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId); + + delete frame; +} + +TEST_F(ScreenCapturerTest, UseMagnifier) { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_use_magnification_api(true); + capturer_.reset(ScreenCapturer::Create(options)); + + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)).WillOnce(SaveArg<0>(&frame)); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + ASSERT_TRUE(frame); + delete frame; +} + +#endif // defined(WEBRTC_WIN) + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc new file mode 100644 index 0000000000..1f33155656 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc @@ -0,0 +1,30 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +namespace webrtc { + +// static +ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { + rtc::scoped_ptr<ScreenCapturer> gdi_capturer( + new ScreenCapturerWinGdi(options)); + + if (options.allow_use_magnification_api()) + return new ScreenCapturerWinMagnifier(gdi_capturer.Pass()); + + return gdi_capturer.release(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_x11.cc b/webrtc/modules/desktop_capture/screen_capturer_x11.cc new file mode 100644 index 0000000000..3a3c418654 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_x11.cc @@ -0,0 +1,444 @@ +/* + * 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 "webrtc/modules/desktop_capture/screen_capturer.h" + +#include <string.h> +#include <set> + +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xfixes.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "webrtc/base/checks.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { +namespace { + +// A class to perform video frame capturing for Linux. +class ScreenCapturerLinux : public ScreenCapturer, + public SharedXDisplay::XEventHandler { + public: + ScreenCapturerLinux(); + virtual ~ScreenCapturerLinux(); + + // TODO(ajwong): Do we really want this to be synchronous? + bool Init(const DesktopCaptureOptions& options); + + // DesktopCapturer interface. + void Start(Callback* delegate) override; + void Capture(const DesktopRegion& region) override; + + // ScreenCapturer interface. + bool GetScreenList(ScreenList* screens) override; + bool SelectScreen(ScreenId id) override; + + private: + Display* display() { return options_.x_display()->display(); } + + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + void InitXDamage(); + + // 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. + 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_; + + // X11 graphics context. + GC gc_; + Window root_window_; + + // XFixes. + bool has_xfixes_; + int xfixes_event_base_; + int xfixes_error_base_; + + // XDamage information. + bool use_damage_; + Damage damage_handle_; + int damage_event_base_; + int damage_error_base_; + XserverRegion damage_region_; + + // 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 queue_; + + // Invalid region from the previous capture. This is used to synchronize the + // current with the last buffer used. + DesktopRegion last_invalid_region_; + + // |Differ| for use when polling for changes. + rtc::scoped_ptr<Differ> differ_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux); +}; + +ScreenCapturerLinux::ScreenCapturerLinux() + : callback_(NULL), + gc_(NULL), + root_window_(BadValue), + has_xfixes_(false), + xfixes_event_base_(-1), + xfixes_error_base_(-1), + use_damage_(false), + damage_handle_(0), + damage_event_base_(-1), + damage_error_base_(-1), + damage_region_(0) { + helper_.SetLogGridSize(4); +} + +ScreenCapturerLinux::~ScreenCapturerLinux() { + options_.x_display()->RemoveEventHandler(ConfigureNotify, this); + if (use_damage_) { + options_.x_display()->RemoveEventHandler( + damage_event_base_ + XDamageNotify, this); + } + DeinitXlib(); +} + +bool ScreenCapturerLinux::Init(const DesktopCaptureOptions& options) { + options_ = options; + + root_window_ = RootWindow(display(), DefaultScreen(display())); + if (root_window_ == BadValue) { + LOG(LS_ERROR) << "Unable to get the root window"; + DeinitXlib(); + return false; + } + + gc_ = XCreateGC(display(), root_window_, 0, NULL); + if (gc_ == NULL) { + 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 { + 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(display(), DefaultRootWindow(display()))) { + LOG(LS_ERROR) << "Failed to initialize pixel buffer."; + return false; + } + + if (options_.use_update_notifications()) { + InitXDamage(); + } + + return true; +} + +void ScreenCapturerLinux::InitXDamage() { + // Our use of XDamage requires XFixes. + if (!has_xfixes_) { + return; + } + + // Check for XDamage extension. + if (!XDamageQueryExtension(display(), &damage_event_base_, + &damage_error_base_)) { + 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_) { + 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_); + LOG(LS_ERROR) << "Unable to create XFixes region."; + return; + } + + options_.x_display()->AddEventHandler( + damage_event_base_ + XDamageNotify, this); + + use_damage_ = true; + LOG(LS_INFO) << "Using XDamage extension."; +} + +void ScreenCapturerLinux::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + + callback_ = callback; +} + +void ScreenCapturerLinux::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // 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. + callback_->OnCaptureCompleted(NULL); + return; + } + + // If the current frame 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()) { + rtc::scoped_ptr<DesktopFrame> frame( + new BasicDesktopFrame(x_server_pixel_buffer_.window_size())); + queue_.ReplaceCurrentFrame(frame.release()); + } + + // Refresh the Differ helper used by CaptureFrame(), if needed. + DesktopFrame* frame = queue_.current_frame(); + if (!use_damage_ && ( + !differ_.get() || + (differ_->width() != frame->size().width()) || + (differ_->height() != frame->size().height()) || + (differ_->bytes_per_row() != frame->stride()))) { + differ_.reset(new Differ(frame->size().width(), frame->size().height(), + DesktopFrame::kBytesPerPixel, + frame->stride())); + } + + DesktopFrame* result = CaptureScreen(); + last_invalid_region_ = result->updated_region(); + result->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(result); +} + +bool ScreenCapturerLinux::GetScreenList(ScreenList* screens) { + RTC_DCHECK(screens->size() == 0); + // TODO(jiayl): implement screen enumeration. + Screen default_screen; + default_screen.id = 0; + screens->push_back(default_screen); + return true; +} + +bool ScreenCapturerLinux::SelectScreen(ScreenId id) { + // TODO(jiayl): implement screen selection. + return true; +} + +bool ScreenCapturerLinux::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 (event.type == ConfigureNotify) { + ScreenConfigurationChanged(); + return true; + } + return false; +} + +DesktopFrame* ScreenCapturerLinux::CaptureScreen() { + DesktopFrame* frame = queue_.current_frame()->Share(); + assert(x_server_pixel_buffer_.window_size().equals(frame->size())); + + // Pass the screen size to the helper, so it can clip the invalid region if it + // expands that region to a grid. + 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) { + updated_region->AddRect(DesktopRect::MakeXYWH( + rects[i].x, rects[i].y, rects[i].width, rects[i].height)); + } + XFree(rects); + helper_.InvalidateRegion(*updated_region); + + // Capture the damaged portions of the desktop. + helper_.TakeInvalidRegion(updated_region); + + // Clip the damaged portions to the current screen size, just in case some + // spurious XDamage notifications were received for a previous (larger) + // screen size. + updated_region->IntersectWith( + DesktopRect::MakeSize(x_server_pixel_buffer_.window_size())); + + for (DesktopRegion::Iterator it(*updated_region); + !it.IsAtEnd(); it.Advance()) { + x_server_pixel_buffer_.CaptureRect(it.rect(), frame); + } + } else { + // Doing full-screen polling, or this is the first capture after a + // screen-resolution change. In either case, need a full-screen capture. + DesktopRect screen_rect = DesktopRect::MakeSize(frame->size()); + x_server_pixel_buffer_.CaptureRect(screen_rect, frame); + + if (queue_.previous_frame()) { + // Full-screen polling, so calculate the invalid rects here, based on the + // changed pixels between current and previous buffers. + RTC_DCHECK(differ_.get() != NULL); + RTC_DCHECK(queue_.previous_frame()->data()); + differ_->CalcDirtyRegion(queue_.previous_frame()->data(), + frame->data(), updated_region); + } else { + // No previous buffer, so always invalidate the whole screen, whether + // or not DAMAGE is being used. DAMAGE doesn't necessarily send a + // full-screen notification after a screen-resolution change, so + // this is done here. + updated_region->SetRect(screen_rect); + } + } + + return frame; +} + +void ScreenCapturerLinux::ScreenConfigurationChanged() { + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + if (!x_server_pixel_buffer_.Init(display(), DefaultRootWindow(display()))) { + LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen " + "configuration change."; + } +} + +void ScreenCapturerLinux::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()) { + current->CopyPixelsFrom(*last, it.rect().top_left(), it.rect()); + } +} + +void ScreenCapturerLinux::DeinitXlib() { + if (gc_) { + XFreeGC(display(), gc_); + gc_ = NULL; + } + + 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; + } + } +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { + if (!options.x_display()) + return NULL; + + rtc::scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux()); + if (!capturer->Init(options)) + capturer.reset(); + return capturer.release(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.cc b/webrtc/modules/desktop_capture/shared_desktop_frame.cc new file mode 100644 index 0000000000..1f1aefa13b --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_desktop_frame.cc @@ -0,0 +1,80 @@ +/* + * 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 "webrtc/modules/desktop_capture/shared_desktop_frame.h" + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/system_wrappers/include/atomic32.h" + +namespace webrtc { + +class SharedDesktopFrame::Core { + public: + Core(DesktopFrame* frame) : frame_(frame) {} + + DesktopFrame* frame() { return frame_.get(); } + + bool HasOneRef() { return ref_count_.Value() == 1; } + + virtual int32_t AddRef() { + return ++ref_count_; + } + + virtual int32_t Release() { + int32_t ref_count; + ref_count = --ref_count_; + if (ref_count == 0) + delete this; + return ref_count; + } + + private: + virtual ~Core() {} + + Atomic32 ref_count_; + rtc::scoped_ptr<DesktopFrame> frame_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Core); +}; + +SharedDesktopFrame::~SharedDesktopFrame() {} + +// static +SharedDesktopFrame* SharedDesktopFrame::Wrap( + DesktopFrame* desktop_frame) { + rtc::scoped_refptr<Core> core(new Core(desktop_frame)); + return new SharedDesktopFrame(core); +} + +DesktopFrame* SharedDesktopFrame::GetUnderlyingFrame() { + return core_->frame(); +} + +SharedDesktopFrame* SharedDesktopFrame::Share() { + SharedDesktopFrame* result = new SharedDesktopFrame(core_); + result->set_dpi(dpi()); + result->set_capture_time_ms(capture_time_ms()); + *result->mutable_updated_region() = updated_region(); + return result; +} + +bool SharedDesktopFrame::IsShared() { + return !core_->HasOneRef(); +} + +SharedDesktopFrame::SharedDesktopFrame(rtc::scoped_refptr<Core> core) + : DesktopFrame(core->frame()->size(), + core->frame()->stride(), + core->frame()->data(), + core->frame()->shared_memory()), + core_(core) { +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.h b/webrtc/modules/desktop_capture/shared_desktop_frame.h new file mode 100644 index 0000000000..7d18db153c --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_desktop_frame.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 WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ + +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +// SharedDesktopFrame is a DesktopFrame that may have multiple instances all +// sharing the same buffer. +class SharedDesktopFrame : public DesktopFrame { + public: + virtual ~SharedDesktopFrame(); + + static SharedDesktopFrame* Wrap(DesktopFrame* desktop_frame); + + // Returns the underlying instance of DesktopFrame. + DesktopFrame* GetUnderlyingFrame(); + + // Creates a clone of this object. + 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: + class Core; + + SharedDesktopFrame(rtc::scoped_refptr<Core> core); + + rtc::scoped_refptr<Core> core_; + + RTC_DISALLOW_COPY_AND_ASSIGN(SharedDesktopFrame); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ diff --git a/webrtc/modules/desktop_capture/shared_memory.cc b/webrtc/modules/desktop_capture/shared_memory.cc new file mode 100644 index 0000000000..872116eec9 --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_memory.cc @@ -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 "webrtc/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/webrtc/modules/desktop_capture/shared_memory.h b/webrtc/modules/desktop_capture/shared_memory.h new file mode 100644 index 0000000000..631f119b5f --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_memory.h @@ -0,0 +1,68 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ + +#include <stddef.h> + +#if defined(WEBRTC_WIN) +#include <windows.h> +#endif + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.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 implement +// DesktopCapturer::Delegate::CreateSharedMemory() as appropriate. +class 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() {} + + protected: + SharedMemory(void* data, size_t size, Handle handle, int id); + + void* const data_; + const size_t size_; + const Handle handle_; + const int id_; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(SharedMemory); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_MEMORY_H_ + diff --git a/webrtc/modules/desktop_capture/win/cursor.cc b/webrtc/modules/desktop_capture/win/cursor.cc new file mode 100644 index 0000000000..a3acaf822b --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor.cc @@ -0,0 +1,248 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/cursor.h" + +#include <algorithm> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/typedefs.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)) { + 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)) { + LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = " + << GetLastError(); + return NULL; + } + + int width = bitmap_info.bmWidth; + int height = bitmap_info.bmHeight; + rtc::scoped_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)) { + LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = " + << GetLastError(); + return NULL; + } + + uint32_t* mask_plane = mask_data.get(); + rtc::scoped_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)) { + 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/webrtc/modules/desktop_capture/win/cursor.h b/webrtc/modules/desktop_capture/win/cursor.h new file mode 100644 index 0000000000..d521cc0819 --- /dev/null +++ b/webrtc/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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_ +#define WEBRTC_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 // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_ diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur Binary files differnew file mode 100644 index 0000000000..27702b825c --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_24bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur Binary files differnew file mode 100644 index 0000000000..7e0d8596da --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_32bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur Binary files differnew file mode 100644 index 0000000000..fefb09e1a1 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/1_8bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur Binary files differnew file mode 100644 index 0000000000..4f8a094f31 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/2_1bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur Binary files differnew file mode 100644 index 0000000000..ac9cdbfbb3 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/2_32bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur Binary files differnew file mode 100644 index 0000000000..efdbee5415 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/3_32bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur b/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur Binary files differnew file mode 100644 index 0000000000..9678d55446 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_test_data/3_4bpp.cur diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest.cc b/webrtc/modules/desktop_capture/win/cursor_unittest.cc new file mode 100644 index 0000000000..32bab13e00 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/cursor_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 "testing/gmock/include/gmock/gmock.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor_unittest_resources.h" +#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.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); + rtc::scoped_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; + rtc::scoped_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/webrtc/modules/desktop_capture/win/cursor_unittest_resources.h b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.h new file mode 100644 index 0000000000..89545c165f --- /dev/null +++ b/webrtc/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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_ +#define WEBRTC_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 // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_ diff --git a/webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc b/webrtc/modules/desktop_capture/win/cursor_unittest_resources.rc new file mode 100644 index 0000000000..4f41489ecd --- /dev/null +++ b/webrtc/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 "webrtc/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/webrtc/modules/desktop_capture/win/desktop.cc b/webrtc/modules/desktop_capture/win/desktop.cc new file mode 100644 index 0000000000..97bbfb717b --- /dev/null +++ b/webrtc/modules/desktop_capture/win/desktop.cc @@ -0,0 +1,110 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/desktop.h" + +#include <vector> + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) { +} + +Desktop::~Desktop() { + if (own_ && desktop_ != NULL) { + if (!::CloseDesktop(desktop_)) { + 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)) { + 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_)) { + 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 = OpenDesktop(desktop_name, 0, FALSE, desired_access); + if (desktop == NULL) { + LOG(LS_ERROR) << "Failed to open the desktop '" << 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) { + 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/webrtc/modules/desktop_capture/win/desktop.h b/webrtc/modules/desktop_capture/win/desktop.h new file mode 100644 index 0000000000..dc3b8c61b9 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/desktop.h @@ -0,0 +1,64 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ + +#include <windows.h> +#include <string> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" + +namespace webrtc { + +class Desktop { + public: + ~Desktop(); + + // 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_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Desktop); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ diff --git a/webrtc/modules/desktop_capture/win/scoped_gdi_object.h b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h new file mode 100644 index 0000000000..1cac63e43d --- /dev/null +++ b/webrtc/modules/desktop_capture/win/scoped_gdi_object.h @@ -0,0 +1,95 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ + +#include <windows.h> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.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_); + } + + 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_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScopedGDIObject); +}; + +// The traits class that uses DeleteObject() to close a handle. +template <typename T> +class DeleteObjectTraits { + public: + // Closes the handle. + static void Close(T handle) { + if (handle) + DeleteObject(handle); + } + + private: + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DeleteObjectTraits); +}; + +// The traits class that uses DestroyCursor() to close a handle. +class DestroyCursorTraits { + public: + // Closes the handle. + static void Close(HCURSOR handle) { + if (handle) + DestroyCursor(handle); + } + + private: + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DestroyCursorTraits); +}; + +typedef ScopedGDIObject<HBITMAP, DeleteObjectTraits<HBITMAP> > ScopedBitmap; +typedef ScopedGDIObject<HCURSOR, DestroyCursorTraits> ScopedCursor; + +} // namespace win +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_ diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc new file mode 100644 index 0000000000..12f9e89e96 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc @@ -0,0 +1,57 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" + +#include "webrtc/system_wrappers/include/logging.h" + +#include "webrtc/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(); + + rtc::scoped_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/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h new file mode 100644 index 0000000000..df8652ac9d --- /dev/null +++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h @@ -0,0 +1,53 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ + +#include <windows.h> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" + +namespace webrtc { + +class Desktop; + +class ScopedThreadDesktop { + public: + ScopedThreadDesktop(); + ~ScopedThreadDesktop(); + + // 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 + rtc::scoped_ptr<Desktop> assigned_; + + // The desktop handle assigned to the calling thread at creation. + rtc::scoped_ptr<Desktop> initial_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScopedThreadDesktop); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.cc b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc new file mode 100644 index 0000000000..1b33545277 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc @@ -0,0 +1,92 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/screen_capture_utils.h" + +#include <assert.h> +#include <windows.h> + +namespace webrtc { + +bool GetScreenList(ScreenCapturer::ScreenList* screens) { + assert(screens->size() == 0); + + BOOL enum_result = TRUE; + for (int device_index = 0;; ++device_index) { + DISPLAY_DEVICE device; + device.cb = sizeof(device); + enum_result = EnumDisplayDevices(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; + + ScreenCapturer::Screen screen; + screen.id = device_index; + screens->push_back(screen); + } + return true; +} + +bool IsScreenValid(ScreenId screen, std::wstring* device_key) { + if (screen == kFullDesktopScreenId) { + *device_key = L""; + return true; + } + + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL enum_result = EnumDisplayDevices(NULL, screen, &device, 0); + if (enum_result) + *device_key = device.DeviceKey; + + return !!enum_result; +} + +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key) { + 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(); + + // 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(); + + 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); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.h b/webrtc/modules/desktop_capture/win/screen_capture_utils.h new file mode 100644 index 0000000000..42473e047b --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.h @@ -0,0 +1,35 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +// Output the list of active screens into |screens|. Returns true if succeeded, +// or false if it fails to enumerate the display devices. +bool GetScreenList(ScreenCapturer::ScreenList* screens); + +// 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(ScreenId screen, std::wstring* device_key); + +// 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. +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc new file mode 100644 index 0000000000..3cf64879f9 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc @@ -0,0 +1,270 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" + +#include <assert.h> + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.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) + : callback_(NULL), + current_screen_id_(kFullDesktopScreenId), + desktop_dc_(NULL), + memory_dc_(NULL), + dwmapi_library_(NULL), + composition_func_(NULL), + set_thread_execution_state_failed_(false) { + if (options.disable_effects()) { + // Load dwmapi.dll dynamically since it is not available on XP. + if (!dwmapi_library_) + dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); + + if (dwmapi_library_) { + composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>( + GetProcAddress(dwmapi_library_, "DwmEnableComposition")); + } + } +} + +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::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + + // Make sure the GDI capture resources are up-to-date. + PrepareCaptureResources(); + + if (!CaptureImage()) { + callback_->OnCaptureCompleted(NULL); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || + (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), + ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector( + GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); +} + +bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + if (valid) + current_screen_id_ = id; + return valid; +} + +void ScreenCapturerWinGdi::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + 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. + rtc::scoped_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 (memory_dc_) { + DeleteDC(memory_dc_); + memory_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()); + + // Re-assert our vote to disable Aero. + // See crbug.com/124018 and crbug.com/129906. + if (composition_func_ != NULL) { + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); + } + } + + // If the display bounds have changed then recreate GDI resources. + // TODO(wez): Also check for pixel format changes. + DesktopRect screen_rect(DesktopRect::MakeXYWH( + GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN))); + if (!screen_rect.equals(desktop_dc_rect_)) { + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + desktop_dc_rect_ = DesktopRect(); + } + + if (desktop_dc_ == NULL) { + assert(memory_dc_ == NULL); + + // Create GDI device contexts to capture from the desktop into memory. + desktop_dc_ = GetDC(NULL); + if (!desktop_dc_) + abort(); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + if (!memory_dc_) + abort(); + + desktop_dc_rect_ = screen_rect; + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + } +} + +bool ScreenCapturerWinGdi::CaptureImage() { + DesktopRect screen_rect = + GetScreenRect(current_screen_id_, current_device_key_); + if (screen_rect.is_empty()) + 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())) { + assert(desktop_dc_ != NULL); + assert(memory_dc_ != NULL); + + size_t buffer_size = size.width() * size.height() * + DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + rtc::scoped_ptr<DesktopFrame> buffer( + DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); + if (!buffer.get()) + return false; + queue_.ReplaceCurrentFrame(buffer.release()); + } + + // 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 != NULL) { + BitBlt(memory_dc_, + 0, 0, screen_rect.width(), screen_rect.height(), + desktop_dc_, + screen_rect.left(), screen_rect.top(), + SRCCOPY | CAPTUREBLT); + + // 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 true; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h new file mode 100644 index 0000000000..202b9aaa87 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h @@ -0,0 +1,89 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include <windows.h> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" + +namespace webrtc { + +class Differ; + +// ScreenCapturerWinGdi captures 32bit RGB using GDI. +// +// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer. +class ScreenCapturerWinGdi : public ScreenCapturer { + public: + explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options); + virtual ~ScreenCapturerWinGdi(); + + // Overridden from ScreenCapturer: + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + bool GetScreenList(ScreenList* screens) override; + bool SelectScreen(ScreenId id) override; + + private: + typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); + + // 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_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + ScopedThreadDesktop desktop_; + + // GDI resources used for screen capture. + HDC desktop_dc_; + HDC memory_dc_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Rectangle describing the bounds of the desktop device context, relative to + // the primary display's top-left. + DesktopRect desktop_dc_rect_; + + // Class to calculate the difference between two screen bitmaps. + rtc::scoped_ptr<Differ> differ_; + + HMODULE dwmapi_library_; + DwmEnableCompositionFunc composition_func_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinGdi); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 0000000000..db40478023 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,449 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +#include <assert.h> + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { + +// kMagnifierWindowClass has to be "Magnifier" according to the Magnification +// API. The other strings can be anything. +static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost"; +static LPCTSTR kHostWindowName = L"MagnifierHost"; +static LPCTSTR kMagnifierWindowClass = L"Magnifier"; +static LPCTSTR kMagnifierWindowName = L"MagnifierWindow"; + +Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES); + +ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier( + rtc::scoped_ptr<ScreenCapturer> fallback_capturer) + : fallback_capturer_(fallback_capturer.Pass()), + fallback_capturer_started_(false), + callback_(NULL), + current_screen_id_(kFullDesktopScreenId), + excluded_window_(NULL), + set_thread_execution_state_failed_(false), + desktop_dc_(NULL), + mag_lib_handle_(NULL), + mag_initialize_func_(NULL), + mag_uninitialize_func_(NULL), + set_window_source_func_(NULL), + set_window_filter_list_func_(NULL), + set_image_scaling_callback_func_(NULL), + host_window_(NULL), + magnifier_window_(NULL), + magnifier_initialized_(false), + magnifier_capture_succeeded_(true) { +} + +ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() { + // DestroyWindow must be called before MagUninitialize. magnifier_window_ is + // destroyed automatically when host_window_ is destroyed. + if (host_window_) + DestroyWindow(host_window_); + + if (magnifier_initialized_) + mag_uninitialize_func_(); + + if (mag_lib_handle_) + FreeLibrary(mag_lib_handle_); + + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); +} + +void ScreenCapturerWinMagnifier::Start(Callback* callback) { + assert(!callback_); + assert(callback); + callback_ = callback; + + InitializeMagnifier(); +} + +void ScreenCapturerWinMagnifier::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + // Switch to the desktop receiving user input if different from the current + // one. + rtc::scoped_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()); + } + + bool succeeded = false; + + // Do not try to use the magnifier if it failed before and in multi-screen + // setup (where the API crashes sometimes). + if (magnifier_initialized_ && (GetSystemMetrics(SM_CMONITORS) == 1) && + magnifier_capture_succeeded_) { + DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_); + CreateCurrentFrameIfNecessary(rect.size()); + + // CaptureImage may fail in some situations, e.g. windows8 metro mode. + succeeded = CaptureImage(rect); + } + + // Defer to the fallback capturer if magnifier capturer did not work. + if (!succeeded) { + LOG_F(LS_WARNING) << "Switching to the fallback screen capturer."; + StartFallbackCapturer(); + fallback_capturer_->Capture(region); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion( + last_frame->data(), current_frame->data(), ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); +} + +bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + + // Set current_screen_id_ even if the fallback capturer is being used, so we + // can switch back to the magnifier when possible. + if (valid) + current_screen_id_ = id; + + if (fallback_capturer_started_) + fallback_capturer_->SelectScreen(id); + + return valid; +} + +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) { + assert(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) { + 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()}; + + // 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) { + 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) { + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + + ScreenCapturerWinMagnifier* owner = + reinterpret_cast<ScreenCapturerWinMagnifier*>( + TlsGetValue(tls_index_.Value())); + + owner->OnCaptured(srcdata, srcheader); + + return TRUE; +} + +bool ScreenCapturerWinMagnifier::InitializeMagnifier() { + assert(!magnifier_initialized_); + + desktop_dc_ = GetDC(NULL); + + mag_lib_handle_ = LoadLibrary(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_) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "library functions missing."; + return false; + } + + BOOL result = mag_initialize_func_(); + if (!result) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagInitialize " << GetLastError(); + return false; + } + + HMODULE hInstance = NULL; + 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_(); + 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. + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = &DefWindowProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = kMagnifierHostClass; + + // Ignore the error which may happen when the class is already registered. + RegisterClassEx(&wcex); + + // Create the host window. + host_window_ = CreateWindowEx(WS_EX_LAYERED, + kMagnifierHostClass, + kHostWindowName, + 0, + 0, 0, 0, 0, + NULL, + NULL, + hInstance, + NULL); + if (!host_window_) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from creating host window " << GetLastError(); + return false; + } + + // Create the magnifier control. + magnifier_window_ = CreateWindow(kMagnifierWindowClass, + kMagnifierWindowName, + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, + host_window_, + NULL, + hInstance, + NULL); + if (!magnifier_window_) { + mag_uninitialize_func_(); + 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_(); + 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_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagSetWindowFilterList " + << GetLastError(); + return false; + } + } + + if (tls_index_.Value() == TLS_OUT_OF_INDEXES) { + // More than one threads may get here at the same time, but only one will + // write to tls_index_ using CompareExchange. + DWORD new_tls_index = TlsAlloc(); + if (!tls_index_.CompareExchange(new_tls_index, TLS_OUT_OF_INDEXES)) + TlsFree(new_tls_index); + } + + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + TlsSetValue(tls_index_.Value(), this); + + 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) { + 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)) { + size_t buffer_size = + size.width() * size.height() * DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + rtc::scoped_ptr<DesktopFrame> buffer; + if (shared_memory) { + buffer.reset(new SharedMemoryDesktopFrame( + size, size.width() * DesktopFrame::kBytesPerPixel, shared_memory)); + } else { + buffer.reset(new BasicDesktopFrame(size)); + } + queue_.ReplaceCurrentFrame(buffer.release()); + } +} + +void ScreenCapturerWinMagnifier::StartFallbackCapturer() { + assert(fallback_capturer_); + if (!fallback_capturer_started_) { + fallback_capturer_started_ = true; + + fallback_capturer_->Start(callback_); + fallback_capturer_->SelectScreen(current_screen_id_); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h new file mode 100644 index 0000000000..f084e25442 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h @@ -0,0 +1,153 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ + +#include <windows.h> +#include <magnification.h> +#include <wincodec.h> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/include/atomic32.h" + +namespace webrtc { + +class DesktopFrame; +class DesktopRect; +class Differ; + +// 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. +class ScreenCapturerWinMagnifier : public ScreenCapturer { + public: + // |fallback_capturer| will be used to capture the screen if a non-primary + // screen is being captured, or the OS does not support Magnification API, or + // the magnifier capturer fails (e.g. in Windows8 Metro mode). + explicit ScreenCapturerWinMagnifier( + rtc::scoped_ptr<ScreenCapturer> fallback_capturer); + virtual ~ScreenCapturerWinMagnifier(); + + // Overridden from ScreenCapturer: + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + bool GetScreenList(ScreenList* screens) override; + bool SelectScreen(ScreenId 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); + + // Start the fallback capturer and select the screen. + void StartFallbackCapturer(); + + static Atomic32 tls_index_; + + rtc::scoped_ptr<ScreenCapturer> fallback_capturer_; + bool fallback_capturer_started_; + Callback* callback_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + HWND excluded_window_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Class to calculate the difference between two screen bitmaps. + rtc::scoped_ptr<Differ> differ_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + ScopedThreadDesktop desktop_; + + // Used for getting the screen dpi. + HDC desktop_dc_; + + HMODULE mag_lib_handle_; + MagInitializeFunc mag_initialize_func_; + MagUninitializeFunc mag_uninitialize_func_; + MagSetWindowSourceFunc set_window_source_func_; + MagSetWindowFilterListFunc set_window_filter_list_func_; + MagSetImageScalingCallbackFunc set_image_scaling_callback_func_; + + // The hidden window hosting the magnifier control. + HWND host_window_; + // The magnifier control that captures the screen. + HWND magnifier_window_; + + // True if the magnifier control has been successfully initialized. + bool magnifier_initialized_; + + // True if the last OnMagImageScalingCallback was called and handled + // successfully. Reset at the beginning of each CaptureImage call. + bool magnifier_capture_succeeded_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinMagnifier); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ diff --git a/webrtc/modules/desktop_capture/win/window_capture_utils.cc b/webrtc/modules/desktop_capture/win/window_capture_utils.cc new file mode 100644 index 0000000000..83922ea7f8 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/window_capture_utils.cc @@ -0,0 +1,69 @@ +/* + * 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 "webrtc/modules/desktop_capture/win/window_capture_utils.h" + +namespace webrtc { + +bool +GetCroppedWindowRect(HWND window, + DesktopRect* cropped_rect, + DesktopRect* original_rect) { + RECT rect; + if (!GetWindowRect(window, &rect)) { + return false; + } + WINDOWPLACEMENT window_placement; + window_placement.length = sizeof(window_placement); + if (!GetWindowPlacement(window, &window_placement)) { + return false; + } + + *original_rect = DesktopRect::MakeLTRB( + rect.left, rect.top, rect.right, rect.bottom); + + if (window_placement.showCmd == SW_SHOWMAXIMIZED) { + DesktopSize border = DesktopSize(GetSystemMetrics(SM_CXSIZEFRAME), + GetSystemMetrics(SM_CYSIZEFRAME)); + *cropped_rect = DesktopRect::MakeLTRB( + rect.left + border.width(), + rect.top, + rect.right - border.width(), + rect.bottom - border.height()); + } else { + *cropped_rect = *original_rect; + } + return true; +} + +AeroChecker::AeroChecker() : dwmapi_library_(nullptr), func_(nullptr) { + // Try to load dwmapi.dll dynamically since it is not available on XP. + dwmapi_library_ = LoadLibrary(L"dwmapi.dll"); + if (dwmapi_library_) { + func_ = reinterpret_cast<DwmIsCompositionEnabledFunc>( + GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled")); + } +} + +AeroChecker::~AeroChecker() { + if (dwmapi_library_) { + FreeLibrary(dwmapi_library_); + } +} + +bool AeroChecker::IsAeroEnabled() { + BOOL result = FALSE; + if (func_) { + func_(&result); + } + return result != FALSE; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/window_capture_utils.h b/webrtc/modules/desktop_capture/win/window_capture_utils.h new file mode 100644 index 0000000000..7c80490f60 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/window_capture_utils.h @@ -0,0 +1,40 @@ +/* + * 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 <windows.h> + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Output the window rect, with the left/right/bottom frame border cropped if +// the window is maximized. |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. +bool GetCroppedWindowRect(HWND window, + DesktopRect* cropped_rect, + DesktopRect* original_rect); + +typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled); +class AeroChecker { + public: + AeroChecker(); + ~AeroChecker(); + + bool IsAeroEnabled(); + + private: + HMODULE dwmapi_library_; + DwmIsCompositionEnabledFunc func_; + + RTC_DISALLOW_COPY_AND_ASSIGN(AeroChecker); +}; + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_capturer.cc b/webrtc/modules/desktop_capture/window_capturer.cc new file mode 100644 index 0000000000..c5176d5e60 --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer.cc @@ -0,0 +1,22 @@ + /* + * 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 "webrtc/modules/desktop_capture/window_capturer.h" + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" + +namespace webrtc { + +// static +WindowCapturer* WindowCapturer::Create() { + return Create(DesktopCaptureOptions::CreateDefault()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_capturer.h b/webrtc/modules/desktop_capture/window_capturer.h new file mode 100644 index 0000000000..9ba441a8ec --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer.h @@ -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. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_ + +#include <string> +#include <vector> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class DesktopCaptureOptions; + +class WindowCapturer : public DesktopCapturer { + public: + typedef webrtc::WindowId WindowId; + + struct Window { + WindowId id; + + // Title of the window in UTF-8 encoding. + std::string title; + }; + + typedef std::vector<Window> WindowList; + + static WindowCapturer* Create(const DesktopCaptureOptions& options); + + // TODO(sergeyu): Remove this method. crbug.com/172183 + static WindowCapturer* Create(); + + virtual ~WindowCapturer() {} + + // Get list of windows. Returns false in case of a failure. + virtual bool GetWindowList(WindowList* windows) = 0; + + // Select window to be captured. Returns false in case of a failure (e.g. if + // there is no window with the specified id). + virtual bool SelectWindow(WindowId id) = 0; + + // Bring the selected window to the front. Returns false in case of a + // failure or no window selected. + virtual bool BringSelectedWindowToFront() = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_CAPTURER_H_ + diff --git a/webrtc/modules/desktop_capture/window_capturer_mac.mm b/webrtc/modules/desktop_capture/window_capturer_mac.mm new file mode 100644 index 0000000000..806fc5c1d4 --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer_mac.mm @@ -0,0 +1,241 @@ +/* + * 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 "webrtc/modules/desktop_capture/window_capturer.h" + +#include <assert.h> +#include <ApplicationServices/ApplicationServices.h> +#include <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> + +#include "webrtc/base/macutils.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" +#include "webrtc/modules/desktop_capture/mac/window_list_utils.h" +#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/system_wrappers/include/tick_util.h" + +namespace webrtc { + +namespace { + +// Returns true if the window exists. +bool IsWindowValid(CGWindowID id) { + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); + 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 WindowCapturer { + public: + explicit WindowCapturerMac(rtc::scoped_refptr<FullScreenChromeWindowDetector> + full_screen_chrome_window_detector); + virtual ~WindowCapturerMac(); + + // WindowCapturer interface. + bool GetWindowList(WindowList* windows) override; + bool SelectWindow(WindowId id) override; + bool BringSelectedWindowToFront() override; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + + private: + Callback* callback_; + + // The window being captured. + CGWindowID window_id_; + + rtc::scoped_refptr<FullScreenChromeWindowDetector> + full_screen_chrome_window_detector_; + + RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerMac); +}; + +WindowCapturerMac::WindowCapturerMac(rtc::scoped_refptr< + FullScreenChromeWindowDetector> full_screen_chrome_window_detector) + : callback_(NULL), + window_id_(0), + full_screen_chrome_window_detector_(full_screen_chrome_window_detector) { +} + +WindowCapturerMac::~WindowCapturerMac() { +} + +bool WindowCapturerMac::GetWindowList(WindowList* windows) { + // Only get on screen, non-desktop windows. + CFArrayRef window_array = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, + kCGNullWindowID); + if (!window_array) + return false; + + // 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)); + CFStringRef window_title = reinterpret_cast<CFStringRef>( + CFDictionaryGetValue(window, kCGWindowName)); + CFNumberRef window_id = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowNumber)); + CFNumberRef window_layer = reinterpret_cast<CFNumberRef>( + CFDictionaryGetValue(window, kCGWindowLayer)); + if (window_title && window_id && window_layer) { + // Skip windows with layer=0 (menu, dock). + int layer; + CFNumberGetValue(window_layer, kCFNumberIntType, &layer); + if (layer != 0) + continue; + + int id; + CFNumberGetValue(window_id, kCFNumberIntType, &id); + WindowCapturer::Window window; + window.id = id; + if (!rtc::ToUtf8(window_title, &(window.title)) || + window.title.empty()) { + continue; + } + windows->push_back(window); + } + } + + CFRelease(window_array); + return true; +} + +bool WindowCapturerMac::SelectWindow(WindowId id) { + if (!IsWindowValid(id)) + return false; + window_id_ = id; + return true; +} + +bool WindowCapturerMac::BringSelectedWindowToFront() { + if (!window_id_) + return false; + + CGWindowID ids[1]; + ids[0] = window_id_; + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL); + + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { + // Could not find the window. It might have been closed. + 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; +} + +void WindowCapturerMac::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerMac::Capture(const DesktopRegion& region) { + if (!IsWindowValid(window_id_)) { + callback_->OnCaptureCompleted(NULL); + return; + } + + CGWindowID on_screen_window = window_id_; + if (full_screen_chrome_window_detector_) { + CGWindowID full_screen_window = + full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_); + + if (full_screen_window != kCGNullWindowID) + on_screen_window = full_screen_window; + } + + CGImageRef window_image = CGWindowListCreateImage( + CGRectNull, kCGWindowListOptionIncludingWindow, + on_screen_window, kCGWindowImageBoundsIgnoreFraming); + + if (!window_image) { + callback_->OnCaptureCompleted(NULL); + return; + } + + int bits_per_pixel = CGImageGetBitsPerPixel(window_image); + if (bits_per_pixel != 32) { + LOG(LS_ERROR) << "Unsupported window image depth: " << bits_per_pixel; + CFRelease(window_image); + callback_->OnCaptureCompleted(NULL); + return; + } + + int width = CGImageGetWidth(window_image); + int height = CGImageGetHeight(window_image); + CGDataProviderRef provider = CGImageGetDataProvider(window_image); + CFDataRef cf_data = CGDataProviderCopyData(provider); + DesktopFrame* frame = new BasicDesktopFrame( + DesktopSize(width, height)); + + int src_stride = CGImageGetBytesPerRow(window_image); + const uint8_t* src_data = CFDataGetBytePtr(cf_data); + for (int y = 0; y < height; ++y) { + memcpy(frame->data() + frame->stride() * y, src_data + src_stride * y, + DesktopFrame::kBytesPerPixel * width); + } + + CFRelease(cf_data); + CFRelease(window_image); + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + callback_->OnCaptureCompleted(frame); + + if (full_screen_chrome_window_detector_) + full_screen_chrome_window_detector_->UpdateWindowListIfNeeded(window_id_); +} + +} // namespace + +// static +WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) { + return new WindowCapturerMac(options.full_screen_chrome_window_detector()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_capturer_null.cc b/webrtc/modules/desktop_capture/window_capturer_null.cc new file mode 100755 index 0000000000..b74f17e39b --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer_null.cc @@ -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. + */ + +#include "webrtc/modules/desktop_capture/window_capturer.h" + +#include <assert.h> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +namespace { + +class WindowCapturerNull : public WindowCapturer { + public: + WindowCapturerNull(); + virtual ~WindowCapturerNull(); + + // WindowCapturer interface. + bool GetWindowList(WindowList* windows) override; + bool SelectWindow(WindowId id) override; + bool BringSelectedWindowToFront() override; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + + private: + Callback* callback_; + + RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerNull); +}; + +WindowCapturerNull::WindowCapturerNull() + : callback_(NULL) { +} + +WindowCapturerNull::~WindowCapturerNull() { +} + +bool WindowCapturerNull::GetWindowList(WindowList* windows) { + // Not implemented yet. + return false; +} + +bool WindowCapturerNull::SelectWindow(WindowId id) { + // Not implemented yet. + return false; +} + +bool WindowCapturerNull::BringSelectedWindowToFront() { + // Not implemented yet. + return false; +} + +void WindowCapturerNull::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerNull::Capture(const DesktopRegion& region) { + // Not implemented yet. + callback_->OnCaptureCompleted(NULL); +} + +} // namespace + +// static +WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) { + return new WindowCapturerNull(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_capturer_unittest.cc b/webrtc/modules/desktop_capture/window_capturer_unittest.cc new file mode 100644 index 0000000000..445a4e9848 --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer_unittest.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 "webrtc/modules/desktop_capture/window_capturer.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +class WindowCapturerTest : public testing::Test, + public DesktopCapturer::Callback { + public: + void SetUp() override { + capturer_.reset( + WindowCapturer::Create(DesktopCaptureOptions::CreateDefault())); + } + + void TearDown() override {} + + // DesktopCapturer::Callback interface + SharedMemory* CreateSharedMemory(size_t size) override { return NULL; } + + void OnCaptureCompleted(DesktopFrame* frame) override { frame_.reset(frame); } + + protected: + rtc::scoped_ptr<WindowCapturer> capturer_; + rtc::scoped_ptr<DesktopFrame> frame_; +}; + +// Verify that we can enumerate windows. +TEST_F(WindowCapturerTest, Enumerate) { + WindowCapturer::WindowList windows; + EXPECT_TRUE(capturer_->GetWindowList(&windows)); + + // Verify that window titles are set. + for (WindowCapturer::WindowList::iterator it = windows.begin(); + it != windows.end(); ++it) { + EXPECT_FALSE(it->title.empty()); + } +} + +// 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, Capture) { + WindowCapturer::WindowList windows; + capturer_->Start(this); + EXPECT_TRUE(capturer_->GetWindowList(&windows)); + + // Verify that we can select and capture each window. + for (WindowCapturer::WindowList::iterator it = windows.begin(); + it != windows.end(); ++it) { + frame_.reset(); + if (capturer_->SelectWindow(it->id)) { + capturer_->Capture(DesktopRegion()); + } + + // If we failed to capture a window make sure it no longer exists. + if (!frame_.get()) { + WindowCapturer::WindowList new_list; + EXPECT_TRUE(capturer_->GetWindowList(&new_list)); + for (WindowCapturer::WindowList::iterator 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/webrtc/modules/desktop_capture/window_capturer_win.cc b/webrtc/modules/desktop_capture/window_capturer_win.cc new file mode 100644 index 0000000000..c0d71167a5 --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer_win.cc @@ -0,0 +1,257 @@ +/* + * 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 "webrtc/modules/desktop_capture/window_capturer.h" + +#include <assert.h> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/win32.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/win/window_capture_utils.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +namespace { + +BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) { + WindowCapturer::WindowList* list = + reinterpret_cast<WindowCapturer::WindowList*>(param); + + // Skip windows that are invisible, minimized, have no title, or are owned, + // unless they have the app window style set. + int len = GetWindowTextLength(hwnd); + HWND owner = GetWindow(hwnd, GW_OWNER); + LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) || + (owner && !(exstyle & WS_EX_APPWINDOW))) { + return TRUE; + } + + // Skip the Program Manager window and the Start button. + const size_t kClassLength = 256; + WCHAR class_name[kClassLength]; + const int class_name_length = GetClassName(hwnd, class_name, kClassLength); + RTC_DCHECK(class_name_length) + << "Error retrieving the application's class name"; + + // Skip Program Manager window and the Start button. This is the same logic + // that's used in Win32WindowPicker in libjingle. Consider filtering other + // windows as well (e.g. toolbars). + if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0) + return TRUE; + + // Windows 8 introduced a "Modern App" identified by their class name being + // either ApplicationFrameWindow or windows.UI.Core.coreWindow. The + // associated windows cannot be captured, so we skip them. + // http://crbug.com/526883. + if (rtc::IsWindows8OrLater() && + (wcscmp(class_name, L"ApplicationFrameWindow") == 0 || + wcscmp(class_name, L"Windows.UI.Core.CoreWindow") == 0)) { + return TRUE; + } + + WindowCapturer::Window window; + window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd); + + const size_t kTitleLength = 500; + WCHAR window_title[kTitleLength]; + // Truncate the title if it's longer than kTitleLength. + GetWindowText(hwnd, window_title, kTitleLength); + window.title = rtc::ToUtf8(window_title); + + // Skip windows when we failed to convert the title or it is empty. + if (window.title.empty()) + return TRUE; + + list->push_back(window); + + return TRUE; +} + +class WindowCapturerWin : public WindowCapturer { + public: + WindowCapturerWin(); + virtual ~WindowCapturerWin(); + + // WindowCapturer interface. + bool GetWindowList(WindowList* windows) override; + bool SelectWindow(WindowId id) override; + bool BringSelectedWindowToFront() override; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + + private: + Callback* callback_; + + // HWND and HDC for the currently selected window or NULL if window is not + // selected. + HWND window_; + + DesktopSize previous_size_; + + AeroChecker aero_checker_; + + RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin); +}; + +WindowCapturerWin::WindowCapturerWin() + : callback_(NULL), + window_(NULL) { +} + +WindowCapturerWin::~WindowCapturerWin() { +} + +bool WindowCapturerWin::GetWindowList(WindowList* windows) { + WindowList result; + LPARAM param = reinterpret_cast<LPARAM>(&result); + if (!EnumWindows(&WindowsEnumerationHandler, param)) + return false; + windows->swap(result); + return true; +} + +bool WindowCapturerWin::SelectWindow(WindowId id) { + HWND window = reinterpret_cast<HWND>(id); + if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window)) + return false; + window_ = window; + previous_size_.set(0, 0); + return true; +} + +bool WindowCapturerWin::BringSelectedWindowToFront() { + if (!window_) + return false; + + if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_)) + return false; + + return SetForegroundWindow(window_) != 0; +} + +void WindowCapturerWin::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerWin::Capture(const DesktopRegion& region) { + if (!window_) { + LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError(); + callback_->OnCaptureCompleted(NULL); + return; + } + + // Stop capturing if the window has been closed or hidden. + if (!IsWindow(window_) || !IsWindowVisible(window_)) { + callback_->OnCaptureCompleted(NULL); + return; + } + + // Return a 1x1 black frame if the window is minimized, to match the behavior + // on Mac. + if (IsIconic(window_)) { + BasicDesktopFrame* frame = new BasicDesktopFrame(DesktopSize(1, 1)); + memset(frame->data(), 0, frame->stride() * frame->size().height()); + + previous_size_ = frame->size(); + callback_->OnCaptureCompleted(frame); + return; + } + + DesktopRect original_rect; + DesktopRect cropped_rect; + if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) { + LOG(LS_WARNING) << "Failed to get window info: " << GetLastError(); + callback_->OnCaptureCompleted(NULL); + return; + } + + HDC window_dc = GetWindowDC(window_); + if (!window_dc) { + LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError(); + callback_->OnCaptureCompleted(NULL); + return; + } + + rtc::scoped_ptr<DesktopFrameWin> frame( + DesktopFrameWin::Create(cropped_rect.size(), NULL, window_dc)); + if (!frame.get()) { + ReleaseDC(window_, window_dc); + callback_->OnCaptureCompleted(NULL); + return; + } + + 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. + + if (!aero_checker_.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, + cropped_rect.left() - original_rect.left(), + cropped_rect.top() - original_rect.top(), + SRCCOPY); + } + + SelectObject(mem_dc, previous_object); + DeleteDC(mem_dc); + ReleaseDC(window_, window_dc); + + previous_size_ = frame->size(); + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + if (!result) { + LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed."; + frame.reset(); + } + + callback_->OnCaptureCompleted(frame.release()); +} + +} // namespace + +// static +WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) { + return new WindowCapturerWin(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_capturer_x11.cc b/webrtc/modules/desktop_capture/window_capturer_x11.cc new file mode 100755 index 0000000000..f0d2c1284c --- /dev/null +++ b/webrtc/modules/desktop_capture/window_capturer_x11.cc @@ -0,0 +1,436 @@ +/* + * 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 "webrtc/modules/desktop_capture/window_capturer.h" + +#include <assert.h> +#include <string.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xcomposite.h> +#include <X11/extensions/Xrender.h> +#include <X11/Xutil.h> + +#include <algorithm> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/x11/shared_x_display.h" +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +namespace { + +// Convenience wrapper for XGetWindowProperty() results. +template <class PropertyType> +class XWindowProperty { + public: + XWindowProperty(Display* display, Window window, Atom property) + : is_valid_(false), + size_(0), + data_(NULL) { + 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_ = NULL; + return; + } + if (sizeof(PropertyType) * kBitsPerByte != actual_format) { + size_ = 0; + return; + } + + is_valid_ = true; + } + + ~XWindowProperty() { + if (data_) + XFree(data_); + } + + // 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_; } + const PropertyType* data() const { + return reinterpret_cast<PropertyType*>(data_); + } + PropertyType* data() { + return reinterpret_cast<PropertyType*>(data_); + } + + private: + bool is_valid_; + unsigned long size_; // NOLINT: type required by XGetWindowProperty + unsigned char* data_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XWindowProperty); +}; + +class WindowCapturerLinux : public WindowCapturer, + public SharedXDisplay::XEventHandler { + public: + WindowCapturerLinux(const DesktopCaptureOptions& options); + virtual ~WindowCapturerLinux(); + + // WindowCapturer interface. + bool GetWindowList(WindowList* windows) override; + bool SelectWindow(WindowId id) override; + bool BringSelectedWindowToFront() override; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void Capture(const DesktopRegion& region) override; + + // SharedXDisplay::XEventHandler interface. + bool HandleXEvent(const XEvent& event) override; + + private: + Display* display() { return x_display_->display(); } + + // 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(::Window window); + + // Returns true if the |window| is a desktop element. + bool IsDesktopElement(::Window window); + + // Returns window title for the specified X |window|. + bool GetWindowTitle(::Window window, std::string* title); + + Callback* callback_; + + rtc::scoped_refptr<SharedXDisplay> x_display_; + + Atom wm_state_atom_; + Atom window_type_atom_; + Atom normal_window_type_atom_; + bool has_composite_extension_; + + ::Window selected_window_; + XServerPixelBuffer x_server_pixel_buffer_; + + RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerLinux); +}; + +WindowCapturerLinux::WindowCapturerLinux(const DesktopCaptureOptions& options) + : callback_(NULL), + x_display_(options.x_display()), + has_composite_extension_(false), + selected_window_(0) { + // Create Atoms so we don't need to do it every time they are used. + wm_state_atom_ = XInternAtom(display(), "WM_STATE", True); + window_type_atom_ = XInternAtom(display(), "_NET_WM_WINDOW_TYPE", True); + normal_window_type_atom_ = XInternAtom( + display(), "_NET_WM_WINDOW_TYPE_NORMAL", True); + + 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 { + LOG(LS_INFO) << "Xcomposite extension not available or too old."; + } + + x_display_->AddEventHandler(ConfigureNotify, this); +} + +WindowCapturerLinux::~WindowCapturerLinux() { + x_display_->RemoveEventHandler(ConfigureNotify, this); +} + +bool WindowCapturerLinux::GetWindowList(WindowList* windows) { + WindowList result; + + XErrorTrap error_trap(display()); + + 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; + int status = XQueryTree(display(), root_window, &root_window, &parent, + &children, &num_children); + if (status == 0) { + LOG(LS_ERROR) << "Failed to query for child windows for screen " + << screen; + continue; + } + + for (unsigned int i = 0; i < num_children; ++i) { + // Iterate in reverse order to return windows from front to back. + ::Window app_window = + GetApplicationWindow(children[num_children - 1 - i]); + if (app_window && !IsDesktopElement(app_window)) { + Window w; + w.id = app_window; + if (GetWindowTitle(app_window, &w.title)) + result.push_back(w); + } + } + + if (children) + XFree(children); + } + + windows->swap(result); + + return true; +} + +bool WindowCapturerLinux::SelectWindow(WindowId id) { + if (!x_server_pixel_buffer_.Init(display(), 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 WindowCapturerLinux::BringSelectedWindowToFront() { + 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) { + 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 WindowCapturerLinux::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; +} + +void WindowCapturerLinux::Capture(const DesktopRegion& region) { + if (!x_server_pixel_buffer_.IsWindowValid()) { + LOG(LS_INFO) << "The window is no longer valid."; + callback_->OnCaptureCompleted(NULL); + return; + } + + x_display_->ProcessPendingXEvents(); + + 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. + LOG(LS_INFO) << "No Xcomposite extension detected."; + callback_->OnCaptureCompleted(NULL); + return; + } + + DesktopFrame* frame = + new BasicDesktopFrame(x_server_pixel_buffer_.window_size()); + + x_server_pixel_buffer_.Synchronize(); + x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()), + frame); + + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + callback_->OnCaptureCompleted(frame); +} + +bool WindowCapturerLinux::HandleXEvent(const XEvent& event) { + if (event.type == ConfigureNotify) { + XConfigureEvent xce = event.xconfigure; + if (!DesktopSize(xce.width, xce.height).equals( + x_server_pixel_buffer_.window_size())) { + if (!x_server_pixel_buffer_.Init(display(), selected_window_)) { + LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing."; + } + return true; + } + } + return false; +} + +::Window WindowCapturerLinux::GetApplicationWindow(::Window window) { + // Get WM_STATE property of the window. + XWindowProperty<uint32_t> window_state(display(), window, wm_state_atom_); + + // WM_STATE is considered to be set to WithdrawnState when it missing. + int32_t state = window_state.is_valid() ? + *window_state.data() : WithdrawnState; + + if (state == NormalState) { + // Window has WM_STATE==NormalState. Return it. + return window; + } else if (state == IconicState) { + // Window is in minimized. Skip it. + return 0; + } + + // 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)) { + 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(children[i]); + if (app_window) + break; + } + + if (children) + XFree(children); + return app_window; +} + +bool WindowCapturerLinux::IsDesktopElement(::Window window) { + 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(display(), window, window_type_atom_); + 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, normal_window_type_atom_)); + return !is_normal; + } + + // Fall back on using the hint. + XClassHint class_hint; + Status status = XGetClassHint(display(), window, &class_hint); + bool result = false; + if (status == 0) { + // No hints, assume this is a normal application window. + return result; + } + + if (strcmp("gnome-panel", class_hint.res_name) == 0 || + strcmp("desktop_window", class_hint.res_name) == 0) { + result = true; + } + XFree(class_hint.res_name); + XFree(class_hint.res_class); + return result; +} + +bool WindowCapturerLinux::GetWindowTitle(::Window window, std::string* title) { + int status; + bool result = false; + XTextProperty window_name; + window_name.value = NULL; + if (window) { + status = XGetWMName(display(), window, &window_name); + if (status && window_name.value && window_name.nitems) { + int cnt; + char **list = NULL; + status = Xutf8TextPropertyToTextList(display(), &window_name, &list, + &cnt); + if (status >= Success && cnt && *list) { + if (cnt > 1) { + 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; +} + +} // namespace + +// static +WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) { + if (!options.x_display()) + return NULL; + return new WindowCapturerLinux(options); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.cc b/webrtc/modules/desktop_capture/x11/shared_x_display.cc new file mode 100644 index 0000000000..3eb5eb10a9 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/shared_x_display.cc @@ -0,0 +1,87 @@ +/* + * 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 "webrtc/modules/desktop_capture/x11/shared_x_display.h" + +#include <algorithm> + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +SharedXDisplay::SharedXDisplay(Display* display) + : display_(display) { + assert(display_); +} + +SharedXDisplay::~SharedXDisplay() { + assert(event_handlers_.empty()); + XCloseDisplay(display_); +} + +// static +rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create( + const std::string& display_name) { + Display* display = + XOpenDisplay(display_name.empty() ? NULL : display_name.c_str()); + if (!display) { + LOG(LS_ERROR) << "Unable to open display"; + return NULL; + } + return 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; + } + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/shared_x_display.h b/webrtc/modules/desktop_capture/x11/shared_x_display.h new file mode 100644 index 0000000000..d905b9e51c --- /dev/null +++ b/webrtc/modules/desktop_capture/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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ + +#include <map> +#include <vector> + +#include <assert.h> +#include <X11/Xlib.h> + +#include <string> + +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/system_wrappers/include/atomic32.h" + +namespace webrtc { + +// A ref-counted object to store XDisplay connection. +class SharedXDisplay { + public: + class XEventHandler { + public: + virtual ~XEventHandler() {} + + // Processes XEvent. Returns true if the event has been handled. + virtual bool HandleXEvent(const XEvent& event) = 0; + }; + + // Takes ownership of |display|. + explicit SharedXDisplay(Display* display); + + // 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( + const std::string& 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(); + + void AddRef() { ++ref_count_; } + void Release() { + if (--ref_count_ == 0) + delete this; + } + + 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(); + + private: + typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap; + + ~SharedXDisplay(); + + Atomic32 ref_count_; + Display* display_; + + EventHandlersMap event_handlers_; + + RTC_DISALLOW_COPY_AND_ASSIGN(SharedXDisplay); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_SHARED_X_DISPLAY_H_ diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.cc b/webrtc/modules/desktop_capture/x11/x_error_trap.cc new file mode 100644 index 0000000000..458fbe61a0 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_error_trap.cc @@ -0,0 +1,69 @@ +/* + * 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 "webrtc/modules/desktop_capture/x11/x_error_trap.h" + +#include <assert.h> + +#if defined(TOOLKIT_GTK) +#include <gdk/gdk.h> +#endif // !defined(TOOLKIT_GTK) + +namespace webrtc { + +namespace { + +#if !defined(TOOLKIT_GTK) + +// TODO(sergeyu): This code is not thread safe. Fix it. Bug 2202. +static bool g_xserver_error_trap_enabled = false; +static int g_last_xserver_error_code = 0; + +int XServerErrorHandler(Display* display, XErrorEvent* error_event) { + assert(g_xserver_error_trap_enabled); + g_last_xserver_error_code = error_event->error_code; + return 0; +} + +#endif // !defined(TOOLKIT_GTK) + +} // namespace + +XErrorTrap::XErrorTrap(Display* display) + : original_error_handler_(NULL), + enabled_(true) { +#if defined(TOOLKIT_GTK) + gdk_error_trap_push(); +#else // !defined(TOOLKIT_GTK) + assert(!g_xserver_error_trap_enabled); + original_error_handler_ = XSetErrorHandler(&XServerErrorHandler); + g_xserver_error_trap_enabled = true; + g_last_xserver_error_code = 0; +#endif // !defined(TOOLKIT_GTK) +} + +int XErrorTrap::GetLastErrorAndDisable() { + enabled_ = false; +#if defined(TOOLKIT_GTK) + return gdk_error_trap_push(); +#else // !defined(TOOLKIT_GTK) + assert(g_xserver_error_trap_enabled); + XSetErrorHandler(original_error_handler_); + g_xserver_error_trap_enabled = false; + return g_last_xserver_error_code; +#endif // !defined(TOOLKIT_GTK) +} + +XErrorTrap::~XErrorTrap() { + if (enabled_) + GetLastErrorAndDisable(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/x_error_trap.h b/webrtc/modules/desktop_capture/x11/x_error_trap.h new file mode 100644 index 0000000000..f1f6e11c63 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_error_trap.h @@ -0,0 +1,39 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ + +#include <X11/Xlib.h> + +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +// Helper class that registers X Window error handler. Caller can use +// GetLastErrorAndDisable() to get the last error that was caught, if any. +class XErrorTrap { + public: + explicit XErrorTrap(Display* display); + ~XErrorTrap(); + + // Returns last error and removes unregisters the error handler. + int GetLastErrorAndDisable(); + + private: + XErrorHandler original_error_handler_; + bool enabled_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XErrorTrap); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_ERROR_TRAP_H_ diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc new file mode 100644 index 0000000000..bcfcb7e027 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc @@ -0,0 +1,337 @@ +/* + * 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 "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" + +#include <assert.h> +#include <string.h> +#include <sys/shm.h> + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/x11/x_error_trap.h" +#include "webrtc/system_wrappers/include/logging.h" + +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; +} + +} // namespace + +namespace webrtc { + +XServerPixelBuffer::XServerPixelBuffer() + : display_(NULL), window_(0), + x_image_(NULL), + shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) { +} + +XServerPixelBuffer::~XServerPixelBuffer() { + Release(); +} + +void XServerPixelBuffer::Release() { + if (x_image_) { + XDestroyImage(x_image_); + x_image_ = NULL; + } + if (shm_pixmap_) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + } + if (shm_gc_) { + XFreeGC(display_, shm_gc_); + shm_gc_ = NULL; + } + if (shm_segment_info_) { + if (shm_segment_info_->shmaddr != reinterpret_cast<char*>(-1)) + 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_ = NULL; + } + window_ = 0; +} + +bool XServerPixelBuffer::Init(Display* display, Window window) { + Release(); + display_ = display; + + XWindowAttributes attributes; + { + XErrorTrap error_trap(display_); + if (!XGetWindowAttributes(display_, window, &attributes) || + error_trap.GetLastErrorAndDisable() != 0) { + return false; + } + } + + window_size_ = DesktopSize(attributes.width, attributes.height); + 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 = reinterpret_cast<char*>(-1); + shm_segment_info_->readOnly = False; + x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap, + 0, shm_segment_info_, window_size_.width(), + window_size_.height()); + if (x_image_) { + shm_segment_info_->shmid = shmget( + IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height, + IPC_CREAT | 0600); + if (shm_segment_info_->shmid != -1) { + shm_segment_info_->shmaddr = x_image_->data = + reinterpret_cast<char*>(shmat(shm_segment_info_->shmid, 0, 0)); + if (x_image_->data != reinterpret_cast<char*>(-1)) { + 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) { + LOG(LS_VERBOSE) << "Using X shared memory segment " + << shm_segment_info_->shmid; + } + } + } else { + LOG(LS_WARNING) << "Failed to get shared memory segment. " + "Performance may be degraded."; + } + } + + if (!using_shm) { + LOG(LS_WARNING) << "Not using shared memory. Performance may be degraded."; + Release(); + return; + } + + if (have_pixmaps) + have_pixmaps = InitPixmaps(default_depth); + + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + shm_segment_info_->shmid = -1; + + 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_size_.width(), + window_size_.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(display_, window_, x_image_, 0, 0, AllPlanes); + } +} + +void XServerPixelBuffer::CaptureRect(const DesktopRect& rect, + DesktopFrame* frame) { + assert(rect.right() <= window_size_.width()); + assert(rect.bottom() <= window_size_.height()); + + uint8_t* data; + + if (shm_segment_info_) { + 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); + } + data = reinterpret_cast<uint8_t*>(x_image_->data) + + rect.top() * x_image_->bytes_per_line + + rect.left() * x_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); + data = reinterpret_cast<uint8_t*>(x_image_->data); + } + + if (IsXImageRGBFormat(x_image_)) { + FastBlit(data, rect, frame); + } else { + SlowBlit(data, rect, frame); + } +} + +void XServerPixelBuffer::FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + uint8_t* src_pos = image; + int src_stride = x_image_->bytes_per_line; + int dst_x = rect.left(), dst_y = rect.top(); + + 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 XServerPixelBuffer::SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + int src_stride = x_image_->bytes_per_line; + int dst_x = rect.left(), dst_y = rect.top(); + 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; + uint8_t* src_pos = image; + 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 webrtc diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h new file mode 100644 index 0000000000..d1e6632f08 --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h @@ -0,0 +1,85 @@ +/* + * 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 WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +#include <X11/Xutil.h> +#include <X11/extensions/XShm.h> + +namespace webrtc { + +class DesktopFrame; + +// A class to allow the X server's pixel buffer to be accessed as efficiently +// as possible. +class XServerPixelBuffer { + public: + XServerPixelBuffer(); + ~XServerPixelBuffer(); + + 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(Display* display, Window window); + + bool is_initialized() { return window_ != 0; } + + // Returns the size of the window the buffer was initialized for. + const DesktopSize& window_size() { return window_size_; } + + // 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(). + void CaptureRect(const DesktopRect& rect, DesktopFrame* frame); + + private: + void InitShm(const XWindowAttributes& attributes); + bool InitPixmaps(int depth); + + // We expose two forms of blitting to handle variations in the pixel format. + // In FastBlit(), the operation is effectively a memcpy. + void FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + void SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + + Display* display_; + Window window_; + DesktopSize window_size_; + XImage* x_image_; + XShmSegmentInfo* shm_segment_info_; + Pixmap shm_pixmap_; + GC shm_gc_; + + RTC_DISALLOW_COPY_AND_ASSIGN(XServerPixelBuffer); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ |