diff --git a/webrtc/base/win32.h b/webrtc/base/win32.h index bf5da254ce..07e1e1ea51 100644 --- a/webrtc/base/win32.h +++ b/webrtc/base/win32.h @@ -110,6 +110,13 @@ inline bool IsWindowsXpOrLater() { (major == kWindows2000 && minor >= 1))); } +inline bool IsWindows8OrLater() { + int major, minor; + return (GetOsVersion(&major, &minor, NULL) && + (major > kWindowsVista || + (major == kWindowsVista && minor >= 2))); +} + // Determine the current integrity level of the process. bool GetCurrentProcessIntegrityLevel(int* level); @@ -125,5 +132,5 @@ bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable); } // namespace rtc -#endif // WEBRTC_WIN +#endif // WEBRTC_WIN #endif // WEBRTC_BASE_WIN32_H_ diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn index 9067177165..6c8d9e200c 100644 --- a/webrtc/modules/desktop_capture/BUILD.gn +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -14,6 +14,11 @@ use_desktop_capture_differ_sse2 = 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_types.h", 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..0216768914 --- /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: + scoped_ptr frame_; + + 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..f78b26b04d --- /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/interface/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) { + scoped_ptr 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; + } + + scoped_ptr window_frame( + CreateCroppedDesktopFrame(screen_frame.release(), window_rect)); + callback_->OnCaptureCompleted(window_frame.release()); +} + +#if !defined(OS_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..2a5154e91f --- /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/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/modules/desktop_capture/window_capturer.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.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. + virtual void Start(DesktopCapturer::Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetExcludedWindow(WindowId window) OVERRIDE; + + // WindowCapturer implementation. + virtual bool GetWindowList(WindowList* windows) OVERRIDE; + virtual bool SelectWindow(WindowId id) OVERRIDE; + virtual bool BringSelectedWindowToFront() OVERRIDE; + + // DesktopCapturer::Callback implementation, passed to |screen_capturer_| to + // intercept the capture result. + virtual SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; + virtual 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_; + scoped_ptr window_capturer_; + scoped_ptr 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..fc1858e03c --- /dev/null +++ b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc @@ -0,0 +1,216 @@ +/* + * 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/interface/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(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: + virtual bool ShouldUseScreenCapturer() OVERRIDE; + virtual 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_; +}; + +bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() { + if (!rtc::IsWindows8OrLater()) + return false; + + // Check if the window is a translucent layered window. + HWND selected = reinterpret_cast(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(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 > + 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(&context)); + return context.is_top_window; +} + +DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() { + DesktopRect original_rect; + DesktopRect window_rect; + HWND hwnd = reinterpret_cast(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_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 1b702f27ee..49f9347ad7 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -16,6 +16,11 @@ '<(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", diff --git a/webrtc/modules/desktop_capture/desktop_frame.cc b/webrtc/modules/desktop_capture/desktop_frame.cc index f26dc9371b..cb32ef7826 100644 --- a/webrtc/modules/desktop_capture/desktop_frame.cc +++ b/webrtc/modules/desktop_capture/desktop_frame.cc @@ -32,8 +32,7 @@ void DesktopFrame::CopyPixelsFrom(uint8_t* src_buffer, int src_stride, const DesktopRect& dest_rect) { assert(DesktopRect::MakeSize(size()).ContainsRect(dest_rect)); - uint8_t* dest = data() + stride() * dest_rect.top() + - DesktopFrame::kBytesPerPixel * dest_rect.left(); + 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; @@ -47,11 +46,14 @@ void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame, assert(DesktopRect::MakeSize(src_frame.size()).ContainsRect( DesktopRect::MakeOriginSize(src_pos, dest_rect.size()))); - CopyPixelsFrom(src_frame.data() + src_frame.stride() * src_pos.y() + - DesktopFrame::kBytesPerPixel * src_pos.x(), + 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()], diff --git a/webrtc/modules/desktop_capture/desktop_frame.h b/webrtc/modules/desktop_capture/desktop_frame.h index 781d108055..7bcc346f85 100644 --- a/webrtc/modules/desktop_capture/desktop_frame.h +++ b/webrtc/modules/desktop_capture/desktop_frame.h @@ -67,6 +67,9 @@ class DesktopFrame { 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, diff --git a/webrtc/modules/desktop_capture/window_capturer.h b/webrtc/modules/desktop_capture/window_capturer.h index ad75c88d5d..9ba441a8ec 100644 --- a/webrtc/modules/desktop_capture/window_capturer.h +++ b/webrtc/modules/desktop_capture/window_capturer.h @@ -52,11 +52,7 @@ class WindowCapturer : public DesktopCapturer { // Bring the selected window to the front. Returns false in case of a // failure or no window selected. - // TODO(jiayl): remove the default impl when FakeWindowCapturer is updated in - // Chromium. - virtual bool BringSelectedWindowToFront() { - return true; - } + virtual bool BringSelectedWindowToFront() = 0; }; } // namespace webrtc