diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn index b0297e96d6..3b025c5095 100644 --- a/webrtc/modules/desktop_capture/BUILD.gn +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -251,6 +251,10 @@ rtc_static_library("desktop_capture") { "win/window_capture_utils.h", "window_capturer_mac.mm", "window_capturer_win.cc", + "window_under_point.h", + "window_under_point_linux.cc", + "window_under_point_mac.mm", + "window_under_point_win.cc", ] if (use_x11) { diff --git a/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc index b7c71c835c..e14efe609c 100644 --- a/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc +++ b/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc @@ -186,6 +186,9 @@ bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() { // Check if the window is occluded by any other window, excluding the child // windows, context menus, and |excluded_window_|. + // TODO(zijiehe): EnumWindows enumerates root window only, so the window may + // be covered by its own child window. See bug + // https://bugs.chromium.org/p/webrtc/issues/detail?id=8062 EnumWindows(&TopWindowVerifier, reinterpret_cast(&context)); return context.is_top_window; } diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h index 71834a96f0..5af69b8760 100644 --- a/webrtc/modules/desktop_capture/desktop_capturer.h +++ b/webrtc/modules/desktop_capture/desktop_capturer.h @@ -104,6 +104,10 @@ class DesktopCapturer { // Gets a list of sources current capturer supports. Returns false in case of // a failure. + // For DesktopCapturer implementations to capture screens, this function + // should return monitors. + // For DesktopCapturer implementations to capture windows, this function + // should only return root windows owned by applications. virtual bool GetSourceList(SourceList* sources); // Selects a source to be captured. Returns false in case of a failure (e.g. diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.cc b/webrtc/modules/desktop_capture/mac/window_list_utils.cc index 7dcfae93f8..88b8367d05 100644 --- a/webrtc/modules/desktop_capture/mac/window_list_utils.cc +++ b/webrtc/modules/desktop_capture/mac/window_list_utils.cc @@ -12,13 +12,24 @@ #include +#include "webrtc/rtc_base/checks.h" #include "webrtc/rtc_base/macutils.h" +static_assert( + static_cast(kCGNullWindowID) == webrtc::kNullWindowId, + "kNullWindowId needs to equal to kCGNullWindowID."); + namespace webrtc { -bool GetWindowList(DesktopCapturer::SourceList* windows, +bool GetWindowList(rtc::FunctionView on_window, bool ignore_minimized) { + RTC_DCHECK(on_window); + // Only get on screen, non-desktop windows. + // According to + // https://developer.apple.com/documentation/coregraphics/cgwindowlistoption/1454105-optiononscreenonly , + // when kCGWindowListOptionOnScreenOnly is used, the order of windows are in + // decreasing z-order. CFArrayRef window_array = CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); @@ -34,38 +45,51 @@ bool GetWindowList(DesktopCapturer::SourceList* windows, // 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) { + for (CFIndex i = 0; i < count; i++) { CFDictionaryRef window = reinterpret_cast( CFArrayGetValueAtIndex(window_array, i)); + if (!window) { + continue; + } + CFStringRef window_title = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowName)); + if (!window_title) { + continue; + } + CFNumberRef window_id = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + continue; + } + CFNumberRef window_layer = reinterpret_cast( 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; + if (!window_layer) { + continue; + } - int id; - CFNumberGetValue(window_id, kCFNumberIntType, &id); + // Skip windows with layer=0 (menu, dock). + // TODO(zijiehe): The windows with layer != 0 are skipped, is this a bug in + // code (not likely) or a bug in comments? What's the meaning of window + // layer number in the first place. + int layer; + if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)) { + continue; + } + if (layer != 0) { + continue; + } - // Skip windows that are minimized and not full screen. - if (ignore_minimized && IsWindowMinimized(id) && - !IsWindowFullScreen(desktop_config, window)) { - continue; - } + // Skip windows that are minimized and not full screen. + if (ignore_minimized && IsWindowMinimized(window) && + !IsWindowFullScreen(desktop_config, window)) { + continue; + } - DesktopCapturer::Source window; - window.id = id; - if (!rtc::ToUtf8(window_title, &(window.title)) || - window.title.empty()) { - continue; - } - windows->push_back(window); + if (!on_window(window)) { + break; } } @@ -73,6 +97,20 @@ bool GetWindowList(DesktopCapturer::SourceList* windows, return true; } +bool GetWindowList(DesktopCapturer::SourceList* windows, + bool ignore_minimized) { + return GetWindowList( + [windows](CFDictionaryRef window) { + WindowId id = GetWindowId(window); + std::string title = GetWindowTitle(window); + if (id != kNullWindowId && !title.empty()) { + windows->push_back(DesktopCapturer::Source{ id, title }); + } + return true; + }, + ignore_minimized); +} + // Returns true if the window is occupying a full screen. bool IsWindowFullScreen( const MacDesktopConfiguration& desktop_config, @@ -86,7 +124,7 @@ bool IsWindowFullScreen( CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) { for (MacDisplayConfigurations::const_iterator it = desktop_config.displays.begin(); - it != desktop_config.displays.end(); ++it) { + it != desktop_config.displays.end(); it++) { if (it->bounds.equals(DesktopRect::MakeXYWH(bounds.origin.x, bounds.origin.y, bounds.size.width, @@ -100,6 +138,12 @@ bool IsWindowFullScreen( return fullscreen; } +bool IsWindowMinimized(CFDictionaryRef window) { + CFBooleanRef on_screen = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowIsOnscreen)); + return !CFBooleanGetValue(on_screen); +} + // Returns true if the window is minimized. bool IsWindowMinimized(CGWindowID id) { CFArrayRef window_id_array = @@ -109,12 +153,8 @@ bool IsWindowMinimized(CGWindowID id) { bool minimized = false; if (window_array && CFArrayGetCount(window_array)) { - CFDictionaryRef window = reinterpret_cast( - CFArrayGetValueAtIndex(window_array, 0)); - CFBooleanRef on_screen = reinterpret_cast( - CFDictionaryGetValue(window, kCGWindowIsOnscreen)); - - minimized = !on_screen; + minimized = IsWindowMinimized(reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0))); } CFRelease(window_id_array); @@ -123,6 +163,47 @@ bool IsWindowMinimized(CGWindowID id) { return minimized; } +std::string GetWindowTitle(CFDictionaryRef window) { + CFStringRef title = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowName)); + std::string result; + if (title && rtc::ToUtf8(title, &result)) { + return result; + } + return std::string(); +} +WindowId GetWindowId(CFDictionaryRef window) { + CFNumberRef window_id = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowNumber)); + if (!window_id) { + return kNullWindowId; + } + + WindowId id; + if (!CFNumberGetValue(window_id, kCFNumberIntType, &id)) { + return kNullWindowId; + } + + return id; +} + +DesktopRect GetWindowBounds(CFDictionaryRef window) { + CFDictionaryRef window_bounds = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowBounds)); + if (!window_bounds) { + return DesktopRect(); + } + + CGRect gc_window_rect; + if (!CGRectMakeWithDictionaryRepresentation(window_bounds, &gc_window_rect)) { + return DesktopRect(); + } + + return DesktopRect::MakeXYWH(gc_window_rect.origin.x, + gc_window_rect.origin.y, + gc_window_rect.size.width, + gc_window_rect.size.height); +} } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.h b/webrtc/modules/desktop_capture/mac/window_list_utils.h index 7cc457109a..6492315181 100644 --- a/webrtc/modules/desktop_capture/mac/window_list_utils.h +++ b/webrtc/modules/desktop_capture/mac/window_list_utils.h @@ -13,11 +13,22 @@ #include +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" #include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" #include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/rtc_base/function_view.h" namespace webrtc { +// Iterates all on-screen windows in decreasing z-order and sends them +// one-by-one to |on_window| function. If |on_window| returns false, this +// function returns immediately. GetWindowList() returns false if native APIs +// failed. Menus, dock, minimized windows and any windows which do not have a +// valid window id or title will be ignored. +bool GetWindowList(rtc::FunctionView on_window, + bool ignore_minimized); + // Another helper function to get the on-screen windows. bool GetWindowList(DesktopCapturer::SourceList* windows, bool ignore_minimized); @@ -25,11 +36,24 @@ bool GetWindowList(DesktopCapturer::SourceList* windows, bool ignore_minimized); bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, CFDictionaryRef window); +// Returns true if the |window| is minimized. +bool IsWindowMinimized(CFDictionaryRef window); + // Returns true if the window is minimized. bool IsWindowMinimized(CGWindowID id); +// Returns utf-8 encoded title of |window|. If |window| is not a window or no +// valid title can be retrieved, this function returns an empty string. +std::string GetWindowTitle(CFDictionaryRef window); + +// Returns id of |window|. If |window| is not a window or the window id cannot +// be retrieved, this function returns kNullWindowId. +WindowId GetWindowId(CFDictionaryRef window); + +// Returns the bounds of |window|. If |window| is not a window or the bounds +// cannot be retrieved, this function returns an empty DesktopRect. +DesktopRect GetWindowBounds(CFDictionaryRef window); } // namespace webrtc #endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_LIST_UTILS_H_ - diff --git a/webrtc/modules/desktop_capture/window_capturer_win.cc b/webrtc/modules/desktop_capture/window_capturer_win.cc index 4fffd6bcc3..29fbebe2db 100644 --- a/webrtc/modules/desktop_capture/window_capturer_win.cc +++ b/webrtc/modules/desktop_capture/window_capturer_win.cc @@ -115,6 +115,7 @@ WindowCapturerWin::~WindowCapturerWin() {} bool WindowCapturerWin::GetSourceList(SourceList* sources) { SourceList result; LPARAM param = reinterpret_cast(&result); + // EnumWindows only enumerates root windows. if (!EnumWindows(&WindowsEnumerationHandler, param)) return false; sources->swap(result); diff --git a/webrtc/modules/desktop_capture/window_capturer_x11.cc b/webrtc/modules/desktop_capture/window_capturer_x11.cc index 62c258ae85..28f5b96ed7 100644 --- a/webrtc/modules/desktop_capture/window_capturer_x11.cc +++ b/webrtc/modules/desktop_capture/window_capturer_x11.cc @@ -327,6 +327,8 @@ bool WindowCapturerLinux::HandleXEvent(const XEvent& event) { return false; } +// TODO(zijiehe): This function should return the ancestor window of |window| +// other than the root_window. ::Window WindowCapturerLinux::GetApplicationWindow(::Window window) { int32_t state = GetWindowState(window); if (state == NormalState) { diff --git a/webrtc/modules/desktop_capture/window_under_point.h b/webrtc/modules/desktop_capture/window_under_point.h new file mode 100644 index 0000000000..5e61207c57 --- /dev/null +++ b/webrtc/modules/desktop_capture/window_under_point.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_UNDER_POINT_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_UNDER_POINT_H_ + +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Returns the id of the visible window under |point|. This function returns +// kNullWindowId if no window is under |point| and the platform does not have +// "root window" concept, i.e. the visible area under |point| is the desktop. +WindowId GetWindowUnderPoint(DesktopVector point); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_UNDER_POINT_H_ diff --git a/webrtc/modules/desktop_capture/window_under_point_linux.cc b/webrtc/modules/desktop_capture/window_under_point_linux.cc new file mode 100644 index 0000000000..7b1d0f70ef --- /dev/null +++ b/webrtc/modules/desktop_capture/window_under_point_linux.cc @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/window_under_point.h" + +namespace webrtc { + +WindowId GetWindowUnderPoint(DesktopVector point) { + // TODO(zijiehe): Implementation required. + return kNullWindowId; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_under_point_mac.mm b/webrtc/modules/desktop_capture/window_under_point_mac.mm new file mode 100644 index 0000000000..7fe57f32cb --- /dev/null +++ b/webrtc/modules/desktop_capture/window_under_point_mac.mm @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "webrtc/modules/desktop_capture/mac/window_list_utils.h" +#include "webrtc/modules/desktop_capture/window_under_point.h" + +namespace webrtc { + +WindowId GetWindowUnderPoint(DesktopVector point) { + WindowId id; + if (!GetWindowList([&id, point](CFDictionaryRef window) { + DesktopRect bounds = GetWindowBounds(window); + if (bounds.Contains(point)) { + id = GetWindowId(window); + return false; + } + return true; + }, + true)) { + return kNullWindowId; + } + return id; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/window_under_point_win.cc b/webrtc/modules/desktop_capture/window_under_point_win.cc new file mode 100644 index 0000000000..a5e7dc1d8f --- /dev/null +++ b/webrtc/modules/desktop_capture/window_under_point_win.cc @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/window_under_point.h" + +#include + +namespace webrtc { + +WindowId GetWindowUnderPoint(DesktopVector point) { + HWND window = WindowFromPoint(POINT { point.x(), point.y() }); + if (!window) { + return kNullWindowId; + } + + // The difference between GA_ROOTOWNER and GA_ROOT can be found at + // https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/Hirr_DkuZdw. + // In short, we should use GA_ROOT, since we only care about the root window + // but not the owner. + window = GetAncestor(window, GA_ROOT); + if (!window) { + return kNullWindowId; + } + + return reinterpret_cast(window); +} + +} // namespace webrtc