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}
diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn
index b0297e9..3b025c5 100644
--- a/webrtc/modules/desktop_capture/BUILD.gn
+++ b/webrtc/modules/desktop_capture/BUILD.gn
@@ -251,6 +251,10 @@
     "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 b7c71c8..e14efe6 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 @@
 
   // 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;
 }
diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h
index 71834a9..5af69b8 100644
--- a/webrtc/modules/desktop_capture/desktop_capturer.h
+++ b/webrtc/modules/desktop_capture/desktop_capturer.h
@@ -104,6 +104,10 @@
 
   // 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 7dcfae9..88b8367 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 <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 @@
   // 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 @@
   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 @@
       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 @@
   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 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 @@
   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
diff --git a/webrtc/modules/desktop_capture/mac/window_list_utils.h b/webrtc/modules/desktop_capture/mac/window_list_utils.h
index 7cc4571..6492315 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 <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 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 4fffd6b..29fbebe 100644
--- a/webrtc/modules/desktop_capture/window_capturer_win.cc
+++ b/webrtc/modules/desktop_capture/window_capturer_win.cc
@@ -115,6 +115,7 @@
 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);
diff --git a/webrtc/modules/desktop_capture/window_capturer_x11.cc b/webrtc/modules/desktop_capture/window_capturer_x11.cc
index 62c258a..28f5b96 100644
--- a/webrtc/modules/desktop_capture/window_capturer_x11.cc
+++ b/webrtc/modules/desktop_capture/window_capturer_x11.cc
@@ -327,6 +327,8 @@
   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 0000000..5e61207
--- /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 0000000..7b1d0f7
--- /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 0000000..7fe57f3
--- /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 <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
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 0000000..a5e7dc1
--- /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 <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