Implement WindowUnderPoint() for Mac OSX and Windows

WindowUnderPoint() is a platform independent function to return the id of the
first window in z-order under a certain DesktopVector. It equals to
GetAncestor(WindowFromPoint(point), GA_ROOT)
on Windows.

This CL includes the change to Windows / Mac OSX only to control the size in a
reasonable range. Implementation for Linux will be added in a coming change.

Bug: webrtc:7950
Change-Id: I57e423294fc8aeaa12d05cb626a1912240b2d4d0
Reviewed-on: https://chromium-review.googlesource.com/595022
Commit-Queue: Zijie He <zijiehe@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#19263}
This commit is contained in:
Zijie He 2017-08-07 15:25:01 -07:00 committed by Commit Bot
parent 3ac7b1edb7
commit b010a3242b
11 changed files with 264 additions and 30 deletions

View File

@ -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) {

View File

@ -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<LPARAM>(&context));
return context.is_top_window;
}

View File

@ -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.

View File

@ -12,13 +12,24 @@
#include <ApplicationServices/ApplicationServices.h>
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/macutils.h"
static_assert(
static_cast<webrtc::WindowId>(kCGNullWindowID) == webrtc::kNullWindowId,
"kNullWindowId needs to equal to kCGNullWindowID.");
namespace webrtc {
bool GetWindowList(DesktopCapturer::SourceList* windows,
bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> 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<CFDictionaryRef>(
CFArrayGetValueAtIndex(window_array, i));
if (!window) {
continue;
}
CFStringRef window_title = reinterpret_cast<CFStringRef>(
CFDictionaryGetValue(window, kCGWindowName));
if (!window_title) {
continue;
}
CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
CFDictionaryGetValue(window, kCGWindowNumber));
if (!window_id) {
continue;
}
CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
CFDictionaryGetValue(window, kCGWindowLayer));
if (window_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<CFBooleanRef>(
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<CFDictionaryRef>(
CFArrayGetValueAtIndex(window_array, 0));
CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>(
CFDictionaryGetValue(window, kCGWindowIsOnscreen));
minimized = !on_screen;
minimized = IsWindowMinimized(reinterpret_cast<CFDictionaryRef>(
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<CFStringRef>(
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<CFNumberRef>(
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<CFDictionaryRef>(
CFDictionaryGetValue(window, kCGWindowBounds));
if (!window_bounds) {
return DesktopRect();
}
CGRect gc_window_rect;
if (!CGRectMakeWithDictionaryRepresentation(window_bounds, &gc_window_rect)) {
return DesktopRect();
}
return DesktopRect::MakeXYWH(gc_window_rect.origin.x,
gc_window_rect.origin.y,
gc_window_rect.size.width,
gc_window_rect.size.height);
}
} // namespace webrtc

View File

@ -13,11 +13,22 @@
#include <ApplicationServices/ApplicationServices.h>
#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<bool(CFDictionaryRef)> 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_

View File

@ -115,6 +115,7 @@ WindowCapturerWin::~WindowCapturerWin() {}
bool WindowCapturerWin::GetSourceList(SourceList* sources) {
SourceList result;
LPARAM param = reinterpret_cast<LPARAM>(&result);
// EnumWindows only enumerates root windows.
if (!EnumWindows(&WindowsEnumerationHandler, param))
return false;
sources->swap(result);

View File

@ -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) {

View File

@ -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_

View File

@ -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

View File

@ -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 <CoreFoundation/CoreFoundation.h>
#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

View File

@ -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 <windows.h>
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<WindowId>(window);
}
} // namespace webrtc