[Window Capturer] Implement scaling in GetWindowBounds()

On Mac OSX system, if retina screen is used, the GetWindowBounds() returns
pre-scaled values instead of system coordinates. So this fix considers
per-monitor scale-factor, and stretchs the DesktopRect.

Bug: chromium:778049
Change-Id: I9dc51e08235eba9b3ef6378eaa15737aa444b0c8
Reviewed-on: https://webrtc-review.googlesource.com/17600
Commit-Queue: Zijie He <zijiehe@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#20578}
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index b63ebb6..43324d1 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -293,6 +293,7 @@
     "win/window_capture_utils.cc",
     "win/window_capture_utils.h",
     "window_capturer_win.cc",
+    "window_finder.cc",
     "window_finder.h",
     "window_finder_win.cc",
     "window_finder_win.h",
diff --git a/modules/desktop_capture/mac/window_list_utils.cc b/modules/desktop_capture/mac/window_list_utils.cc
index 005b1c6..f73193e 100644
--- a/modules/desktop_capture/mac/window_list_utils.cc
+++ b/modules/desktop_capture/mac/window_list_utils.cc
@@ -12,6 +12,9 @@
 
 #include <ApplicationServices/ApplicationServices.h>
 
+#include <algorithm>
+#include <iterator>
+
 #include "rtc_base/checks.h"
 #include "rtc_base/macutils.h"
 
@@ -57,6 +60,37 @@
   return result;
 }
 
+// Scales the |rect| according to the DIP to physical pixel scale of |rect|.
+// |rect| is in unscaled system coordinate, i.e. it's device-independent and the
+// primary monitor starts from (0, 0). If |rect| overlaps multiple monitors, the
+// returned size may not be accurate when monitors have different DIP settings.
+// If |rect| is entirely out of the display, this function returns |rect|.
+DesktopRect ApplyScaleFactorOfRect(
+    const MacDesktopConfiguration& desktop_config,
+    DesktopRect rect) {
+  // TODO(http://crbug.com/778049): How does Mac OSX decide the scale factor
+  // if one window is across two monitors with different DPIs.
+  float scales[] = {
+      GetScaleFactorAtPosition(desktop_config, rect.top_left()),
+      GetScaleFactorAtPosition(desktop_config,
+          DesktopVector(rect.left() + rect.width() / 2,
+                        rect.top() + rect.height() / 2)),
+      GetScaleFactorAtPosition(
+            desktop_config, DesktopVector(rect.right(), rect.bottom())),
+  };
+  // Since GetScaleFactorAtPosition() returns 1 if the position is out of the
+  // display, we always prefer a value which not equals to 1.
+  float scale = *std::max_element(std::begin(scales), std::end(scales));
+  if (scale == 1) {
+    scale = *std::min_element(std::begin(scales), std::end(scales));
+  }
+
+  return DesktopRect::MakeXYWH(rect.left() * scale,
+                               rect.top() * scale,
+                               rect.width() * scale,
+                               rect.height() * scale);
+}
+
 }  // namespace
 
 bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> on_window,
@@ -227,6 +261,19 @@
   return id;
 }
 
+float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config,
+                               DesktopVector position) {
+  // Find the dpi to physical pixel scale for the screen where the mouse cursor
+  // is.
+  for (auto it = desktop_config.displays.begin();
+       it != desktop_config.displays.end(); ++it) {
+    if (it->bounds.Contains(position)) {
+      return it->dip_to_pixel_scale;
+    }
+  }
+  return 1;
+}
+
 DesktopRect GetWindowBounds(CFDictionaryRef window) {
   CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>(
       CFDictionaryGetValue(window, kCGWindowBounds));
@@ -245,6 +292,12 @@
                                gc_window_rect.size.height);
 }
 
+DesktopRect GetWindowBounds(const MacDesktopConfiguration& desktop_config,
+                            CFDictionaryRef window) {
+  DesktopRect rect = GetWindowBounds(window);
+  return ApplyScaleFactorOfRect(desktop_config, rect);
+}
+
 DesktopRect GetWindowBounds(CGWindowID id) {
   DesktopRect result;
   if (GetWindowRef(id,
@@ -256,4 +309,10 @@
   return DesktopRect();
 }
 
+DesktopRect GetWindowBounds(const MacDesktopConfiguration& desktop_config,
+                            CGWindowID id) {
+  DesktopRect rect = GetWindowBounds(id);
+  return ApplyScaleFactorOfRect(desktop_config, rect);
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/mac/window_list_utils.h b/modules/desktop_capture/mac/window_list_utils.h
index 7fb0cee..8a79f7e 100644
--- a/modules/desktop_capture/mac/window_list_utils.h
+++ b/modules/desktop_capture/mac/window_list_utils.h
@@ -52,17 +52,38 @@
 // be retrieved, this function returns kNullWindowId.
 WindowId GetWindowId(CFDictionaryRef window);
 
+// Returns the DIP to physical pixel scale at |position|. |position| is in
+// *unscaled* system coordinate, i.e. it's device-independent and the primary
+// monitor starts from (0, 0). If |position| is out of the system display, this
+// function returns 1.
+float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config,
+                               DesktopVector position);
+
 // Returns the bounds of |window|. If |window| is not a window or the bounds
 // cannot be retrieved, this function returns an empty DesktopRect. The returned
 // DesktopRect is in system coordinate, i.e. the primary monitor always starts
 // from (0, 0).
+// Deprecated: This function should be avoided in favor of the overload with
+// MacDesktopConfiguration.
 DesktopRect GetWindowBounds(CFDictionaryRef window);
 
+// Same as GetWindowBounds(CFDictionaryRef), but this function stretches the
+// result with the scale factor.
+DesktopRect GetWindowBounds(const MacDesktopConfiguration& desktop_config,
+                            CFDictionaryRef window);
+
 // Returns the bounds of window with |id|. If |id| does not represent a window
 // or the bounds cannot be retrieved, this function returns an empty
 // DesktopRect. The returned DesktopRect is in system coordinates.
+// Deprecated: This function should be avoided in favor of the overload with
+// MacDesktopConfiguration.
 DesktopRect GetWindowBounds(CGWindowID id);
 
+// Same as GetWindowBounds(CGWindowID), but this function stretches the result
+// with the scale factor.
+DesktopRect GetWindowBounds(const MacDesktopConfiguration& desktop_config,
+                            CGWindowID id);
+
 }  // namespace webrtc
 
 #endif  // MODULES_DESKTOP_CAPTURE_MAC_WINDOW_LIST_UTILS_H_
diff --git a/modules/desktop_capture/mouse_cursor_monitor_mac.mm b/modules/desktop_capture/mouse_cursor_monitor_mac.mm
index da995a2..b4a80e9 100644
--- a/modules/desktop_capture/mouse_cursor_monitor_mac.mm
+++ b/modules/desktop_capture/mouse_cursor_monitor_mac.mm
@@ -24,6 +24,7 @@
 #include "modules/desktop_capture/mac/desktop_configuration.h"
 #include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
 #include "modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
+#include "modules/desktop_capture/mac/window_list_utils.h"
 #include "modules/desktop_capture/mouse_cursor.h"
 #include "rtc_base/macutils.h"
 #include "rtc_base/scoped_ref_ptr.h"
@@ -122,17 +123,7 @@
   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;
-    }
-  }
+  float scale = GetScaleFactorAtPosition(configuration, position);
 
   CaptureImage(scale);
 
diff --git a/modules/desktop_capture/window_capturer_mac.mm b/modules/desktop_capture/window_capturer_mac.mm
index a6b5bf9..aa55fd5 100644
--- a/modules/desktop_capture/window_capturer_mac.mm
+++ b/modules/desktop_capture/window_capturer_mac.mm
@@ -83,7 +83,8 @@
     rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor)
     : full_screen_chrome_window_detector_(
           std::move(full_screen_chrome_window_detector)),
-      configuration_monitor_(std::move(configuration_monitor)) {}
+      configuration_monitor_(std::move(configuration_monitor)),
+      window_finder_(configuration_monitor_) {}
 
 WindowCapturerMac::~WindowCapturerMac() {}
 
@@ -204,12 +205,15 @@
 
   frame->mutable_updated_region()->SetRect(
       DesktopRect::MakeSize(frame->size()));
-  DesktopVector top_left = GetWindowBounds(on_screen_window).top_left();
+  DesktopVector top_left;
   if (configuration_monitor_) {
     configuration_monitor_->Lock();
     auto configuration = configuration_monitor_->desktop_configuration();
     configuration_monitor_->Unlock();
+    top_left = GetWindowBounds(configuration, on_screen_window).top_left();
     top_left = top_left.subtract(configuration.bounds.top_left());
+  } else {
+    top_left = GetWindowBounds(on_screen_window).top_left();
   }
   frame->set_top_left(top_left);
 
diff --git a/modules/desktop_capture/window_finder.cc b/modules/desktop_capture/window_finder.cc
new file mode 100644
index 0000000..86127d4
--- /dev/null
+++ b/modules/desktop_capture/window_finder.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 "modules/desktop_capture/window_finder.h"
+
+namespace webrtc {
+
+WindowFinder::Options::Options() = default;
+WindowFinder::Options::~Options() = default;
+WindowFinder::Options::Options(const WindowFinder::Options& other) = default;
+WindowFinder::Options::Options(WindowFinder::Options&& other) = default;
+
+}  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder.h b/modules/desktop_capture/window_finder.h
index 360278a..1a78145 100644
--- a/modules/desktop_capture/window_finder.h
+++ b/modules/desktop_capture/window_finder.h
@@ -15,6 +15,11 @@
 
 #include "modules/desktop_capture/desktop_capture_types.h"
 #include "modules/desktop_capture/desktop_geometry.h"
+#include "rtc_base/scoped_ref_ptr.h"
+
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+#include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
+#endif
 
 namespace webrtc {
 
@@ -35,10 +40,18 @@
   // starts from (0, 0).
   virtual WindowId GetWindowUnderPoint(DesktopVector point) = 0;
 
-  struct Options {
+  struct Options final {
+    Options();
+    ~Options();
+    Options(const Options& other);
+    Options(Options&& other);
+
 #if defined(USE_X11)
     XAtomCache* cache = nullptr;
 #endif
+#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
+    rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor;
+#endif
   };
 
   // Creates a platform-independent WindowFinder implementation. This function
diff --git a/modules/desktop_capture/window_finder_mac.h b/modules/desktop_capture/window_finder_mac.h
index 30c841b..db6e926 100644
--- a/modules/desktop_capture/window_finder_mac.h
+++ b/modules/desktop_capture/window_finder_mac.h
@@ -12,17 +12,24 @@
 #define MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_MAC_H_
 
 #include "modules/desktop_capture/window_finder.h"
+#include "rtc_base/scoped_ref_ptr.h"
 
 namespace webrtc {
 
+class DesktopConfigurationMonitor;
+
 // The implementation of WindowFinder for Mac OSX.
 class WindowFinderMac final : public WindowFinder {
  public:
-  WindowFinderMac();
+  explicit WindowFinderMac(
+      rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor);
   ~WindowFinderMac() override;
 
   // WindowFinder implementation.
   WindowId GetWindowUnderPoint(DesktopVector point) override;
+
+ private:
+  const rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
 };
 
 }  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder_mac.mm b/modules/desktop_capture/window_finder_mac.mm
index 94f30e5..6df0d4d 100644
--- a/modules/desktop_capture/window_finder_mac.mm
+++ b/modules/desktop_capture/window_finder_mac.mm
@@ -12,18 +12,37 @@
 
 #include <CoreFoundation/CoreFoundation.h>
 
+#include <utility>
+
 #include "modules/desktop_capture/mac/window_list_utils.h"
+#include "modules/desktop_capture/mac/desktop_configuration.h"
+#include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
 #include "rtc_base/ptr_util.h"
 
 namespace webrtc {
 
-WindowFinderMac::WindowFinderMac() = default;
+WindowFinderMac::WindowFinderMac(
+    rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor)
+    : configuration_monitor_(std::move(configuration_monitor)) {}
 WindowFinderMac::~WindowFinderMac() = default;
 
 WindowId WindowFinderMac::GetWindowUnderPoint(DesktopVector point) {
   WindowId id = kNullWindowId;
-  GetWindowList([&id, point](CFDictionaryRef window) {
-                  DesktopRect bounds = GetWindowBounds(window);
+  MacDesktopConfiguration configuration_holder;
+  MacDesktopConfiguration* configuration = nullptr;
+  if (configuration_monitor_) {
+    configuration_monitor_->Lock();
+    configuration_holder = configuration_monitor_->desktop_configuration();
+    configuration_monitor_->Unlock();
+    configuration = &configuration_holder;
+  }
+  GetWindowList([&id, point, configuration](CFDictionaryRef window) {
+                  DesktopRect bounds;
+                  if (configuration) {
+                    bounds = GetWindowBounds(*configuration, window);
+                  } else {
+                    bounds = GetWindowBounds(window);
+                  }
                   if (bounds.Contains(point)) {
                     id = GetWindowId(window);
                     return false;
@@ -37,7 +56,7 @@
 // static
 std::unique_ptr<WindowFinder> WindowFinder::Create(
     const WindowFinder::Options& options) {
-  return rtc::MakeUnique<WindowFinderMac>();
+  return rtc::MakeUnique<WindowFinderMac>(options.configuration_monitor);
 }
 
 }  // namespace webrtc