[desktopCaptuer Win] exclude windows not on current virtual desktop

Since Windows 10, Windows starts to support virtual desktops. The
problem is when one virtual desktop is not the current one, we can still
enumerate the windows on it, which are still marked as visible by OS.
This causes troubles to decide if a window is on top to be cropped out.

This cl is to utilize a COM API, IsWindowOnCurrentVirtualDesktop of
VirtualDesktopManager, to make sure only the windows on current desktop
will be enumerated.

Bug: chromium:796112
Change-Id: I6e0546e90fbdb37365a8d98694ded0e30791628e
Reviewed-on: https://webrtc-review.googlesource.com/65882
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Commit-Queue: Brave Yao <braveyao@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22842}
diff --git a/modules/desktop_capture/cropping_window_capturer_win.cc b/modules/desktop_capture/cropping_window_capturer_win.cc
index 6707236..2c504a0 100644
--- a/modules/desktop_capture/cropping_window_capturer_win.cc
+++ b/modules/desktop_capture/cropping_window_capturer_win.cc
@@ -24,10 +24,12 @@
 struct TopWindowVerifierContext {
   TopWindowVerifierContext(HWND selected_window,
                            HWND excluded_window,
-                           DesktopRect selected_window_rect)
+                           DesktopRect selected_window_rect,
+                           WindowCaptureHelperWin* window_capture_helper)
       : selected_window(selected_window),
         excluded_window(excluded_window),
         selected_window_rect(selected_window_rect),
+        window_capture_helper(window_capture_helper),
         is_top_window(false) {
     RTC_DCHECK_NE(selected_window, excluded_window);
   }
@@ -35,6 +37,7 @@
   const HWND selected_window;
   const HWND excluded_window;
   const DesktopRect selected_window_rect;
+  WindowCaptureHelperWin* window_capture_helper;
   bool is_top_window;
 };
 
@@ -54,8 +57,8 @@
     return TRUE;
   }
 
-  // Ignore hidden or minimized window.
-  if (IsIconic(hwnd) || !IsWindowVisible(hwnd)) {
+  // Ignore invisible window on current desktop.
+  if (!context->window_capture_helper->IsWindowVisibleOnCurrentDesktop(hwnd)) {
     return TRUE;
   }
 
@@ -140,17 +143,17 @@
   // rectangular, or the rect from GetWindowRect if the region is not set.
   DesktopRect window_region_rect_;
 
-  AeroChecker aero_checker_;
+  WindowCaptureHelperWin window_capture_helper_;
 };
 
 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
-  if (!rtc::IsWindows8OrLater() && aero_checker_.IsAeroEnabled()) {
+  if (!rtc::IsWindows8OrLater() && window_capture_helper_.IsAeroEnabled()) {
     return false;
   }
 
   const HWND selected = reinterpret_cast<HWND>(selected_window());
-  // Check if the window is hidden or minimized.
-  if (IsIconic(selected) || !IsWindowVisible(selected)) {
+  // Check if the window is visible on current desktop.
+  if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
     return false;
   }
 
@@ -223,8 +226,9 @@
   // windows, context menus, and |excluded_window_|.
   // |content_rect| is preferred, see the comments in TopWindowVerifier()
   // function.
-  TopWindowVerifierContext context(
-      selected, reinterpret_cast<HWND>(excluded_window()), content_rect);
+  TopWindowVerifierContext context(selected,
+                                   reinterpret_cast<HWND>(excluded_window()),
+                                   content_rect, &window_capture_helper_);
   const LPARAM enum_param = reinterpret_cast<LPARAM>(&context);
   EnumWindows(&TopWindowVerifier, enum_param);
   if (!context.is_top_window) {
diff --git a/modules/desktop_capture/win/window_capture_utils.cc b/modules/desktop_capture/win/window_capture_utils.cc
index 41c62dc..8929bb7 100644
--- a/modules/desktop_capture/win/window_capture_utils.cc
+++ b/modules/desktop_capture/win/window_capture_utils.cc
@@ -12,6 +12,7 @@
 
 #include "modules/desktop_capture/win/scoped_gdi_object.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
 #include "rtc_base/win32.h"
 
 namespace webrtc {
@@ -130,22 +131,34 @@
   return true;
 }
 
-AeroChecker::AeroChecker() : dwmapi_library_(nullptr), func_(nullptr) {
+// WindowCaptureHelperWin implementation.
+WindowCaptureHelperWin::WindowCaptureHelperWin()
+    : dwmapi_library_(nullptr),
+      func_(nullptr),
+      virtual_desktop_manager_(nullptr) {
   // Try to load dwmapi.dll dynamically since it is not available on XP.
   dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
   if (dwmapi_library_) {
     func_ = reinterpret_cast<DwmIsCompositionEnabledFunc>(
         GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
   }
+
+  if (rtc::IsWindows10OrLater()) {
+    if (FAILED(::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr,
+                                  CLSCTX_ALL,
+                                  IID_PPV_ARGS(&virtual_desktop_manager_)))) {
+      RTC_LOG(LS_WARNING) << "Fail to create instance of VirtualDesktopManager";
+    }
+  }
 }
 
-AeroChecker::~AeroChecker() {
+WindowCaptureHelperWin::~WindowCaptureHelperWin() {
   if (dwmapi_library_) {
     FreeLibrary(dwmapi_library_);
   }
 }
 
-bool AeroChecker::IsAeroEnabled() {
+bool WindowCaptureHelperWin::IsAeroEnabled() {
   BOOL result = FALSE;
   if (func_) {
     func_(&result);
@@ -153,4 +166,22 @@
   return result != FALSE;
 }
 
+bool WindowCaptureHelperWin::IsWindowOnCurrentDesktop(HWND hwnd) {
+  // Make sure the window is on the current virtual desktop.
+  if (virtual_desktop_manager_) {
+    BOOL on_current_desktop;
+    if (SUCCEEDED(virtual_desktop_manager_->IsWindowOnCurrentVirtualDesktop(
+            hwnd, &on_current_desktop)) &&
+        !on_current_desktop) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WindowCaptureHelperWin::IsWindowVisibleOnCurrentDesktop(HWND hwnd) {
+  return !::IsIconic(hwnd) && ::IsWindowVisible(hwnd) &&
+         IsWindowOnCurrentDesktop(hwnd);
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/win/window_capture_utils.h b/modules/desktop_capture/win/window_capture_utils.h
index c38995a..7cb5097 100644
--- a/modules/desktop_capture/win/window_capture_utils.h
+++ b/modules/desktop_capture/win/window_capture_utils.h
@@ -8,7 +8,9 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
+#include <shlobj.h>
 #include <windows.h>
+#include <wrl/client.h>
 
 #include "modules/desktop_capture/desktop_geometry.h"
 #include "rtc_base/constructormagic.h"
@@ -58,18 +60,23 @@
 bool IsWindowMaximized(HWND window, bool* result);
 
 typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
-class AeroChecker {
+class WindowCaptureHelperWin {
  public:
-  AeroChecker();
-  ~AeroChecker();
+  WindowCaptureHelperWin();
+  ~WindowCaptureHelperWin();
 
   bool IsAeroEnabled();
+  bool IsWindowOnCurrentDesktop(HWND hwnd);
+  bool IsWindowVisibleOnCurrentDesktop(HWND hwnd);
 
  private:
   HMODULE dwmapi_library_;
   DwmIsCompositionEnabledFunc func_;
 
-  RTC_DISALLOW_COPY_AND_ASSIGN(AeroChecker);
+  // Only used on Win10+.
+  Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_;
+
+  RTC_DISALLOW_COPY_AND_ASSIGN(WindowCaptureHelperWin);
 };
 
 }  // namespace webrtc
diff --git a/modules/desktop_capture/window_capturer_win.cc b/modules/desktop_capture/window_capturer_win.cc
index 414eddf..d1a672b 100644
--- a/modules/desktop_capture/window_capturer_win.cc
+++ b/modules/desktop_capture/window_capturer_win.cc
@@ -131,7 +131,7 @@
 
   DesktopSize previous_size_;
 
-  AeroChecker aero_checker_;
+  WindowCaptureHelperWin window_capture_helper_;
 
   // This map is used to avoid flickering for the case when SelectWindow() calls
   // are interleaved with Capture() calls.
@@ -151,6 +151,15 @@
   // EnumWindows only enumerates root windows.
   if (!EnumWindows(&WindowsEnumerationHandler, param))
     return false;
+
+  for (auto it = result.begin(); it != result.end();) {
+    if (!window_capture_helper_.IsWindowOnCurrentDesktop(
+            reinterpret_cast<HWND>(it->id))) {
+      it = result.erase(it);
+    } else {
+      ++it;
+    }
+  }
   sources->swap(result);
 
   std::map<HWND, DesktopSize> new_map;
@@ -220,12 +229,11 @@
     return;
   }
 
-  // Return a 1x1 black frame if the window is minimized or invisible, to match
-  // behavior on mace. Window can be temporarily invisible during the
-  // transition of full screen mode on/off.
+  // Return a 1x1 black frame if the window is minimized or invisible on current
+  // desktop, to match behavior on mace. Window can be temporarily invisible
+  // during the transition of full screen mode on/off.
   if (original_rect.is_empty() ||
-      IsIconic(window_) ||
-      !IsWindowVisible(window_)) {
+      !window_capture_helper_.IsWindowVisibleOnCurrentDesktop(window_)) {
     std::unique_ptr<DesktopFrame> frame(
         new BasicDesktopFrame(DesktopSize(1, 1)));
     memset(frame->data(), 0, frame->stride() * frame->size().height());
@@ -295,7 +303,8 @@
   // capturing - it somehow affects what we get from BitBlt() on the subsequent
   // captures.
 
-  if (!aero_checker_.IsAeroEnabled() || !previous_size_.equals(frame->size())) {
+  if (!window_capture_helper_.IsAeroEnabled() ||
+      !previous_size_.equals(frame->size())) {
     result = PrintWindow(window_, mem_dc, 0);
   }
 
diff --git a/rtc_base/win32.h b/rtc_base/win32.h
index 78f66a7..4e91687 100644
--- a/rtc_base/win32.h
+++ b/rtc_base/win32.h
@@ -54,6 +54,7 @@
 enum WindowsMajorVersions {
   kWindows2000 = 5,
   kWindowsVista = 6,
+  kWindows10 = 10,
 };
 bool GetOsVersion(int* major, int* minor, int* build);
 
@@ -74,6 +75,11 @@
           (major > kWindowsVista || (major == kWindowsVista && minor >= 2)));
 }
 
+inline bool IsWindows10OrLater() {
+  int major;
+  return (GetOsVersion(&major, nullptr, nullptr) && (major >= kWindows10));
+}
+
 // Determine the current integrity level of the process.
 bool GetCurrentProcessIntegrityLevel(int* level);