tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cropping_window_capturer_win.cc (13640B)


      1 /*
      2 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
      3 *
      4 *  Use of this source code is governed by a BSD-style license
      5 *  that can be found in the LICENSE file in the root of the source
      6 *  tree. An additional intellectual property rights grant can be found
      7 *  in the file PATENTS.  All contributing project authors may
      8 *  be found in the AUTHORS file in the root of the source tree.
      9 */
     10 
     11 #include <algorithm>
     12 #include <memory>
     13 #include <utility>
     14 
     15 #include "api/scoped_refptr.h"
     16 #include "modules/desktop_capture/cropping_window_capturer.h"
     17 #include "modules/desktop_capture/desktop_capture_options.h"
     18 #include "modules/desktop_capture/desktop_capture_types.h"
     19 #include "modules/desktop_capture/desktop_capturer.h"
     20 #include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
     21 #include "modules/desktop_capture/desktop_geometry.h"
     22 #include "modules/desktop_capture/full_screen_window_detector.h"
     23 #include "modules/desktop_capture/win/screen_capture_utils.h"
     24 #include "modules/desktop_capture/win/selected_window_context.h"
     25 #include "modules/desktop_capture/win/window_capture_utils.h"
     26 #include "rtc_base/checks.h"
     27 #include "rtc_base/logging.h"
     28 #include "rtc_base/trace_event.h"
     29 #include "rtc_base/win/windows_version.h"
     30 
     31 namespace webrtc {
     32 
     33 namespace {
     34 
     35 // Used to pass input data for verifying the selected window is on top.
     36 struct TopWindowVerifierContext : public SelectedWindowContext {
     37  TopWindowVerifierContext(HWND selected_window,
     38                           HWND excluded_window,
     39                           DesktopRect selected_window_rect,
     40                           WindowCaptureHelperWin* window_capture_helper)
     41      : SelectedWindowContext(selected_window,
     42                              selected_window_rect,
     43                              window_capture_helper),
     44        excluded_window(excluded_window) {
     45    RTC_DCHECK_NE(selected_window, excluded_window);
     46  }
     47 
     48  // Determines whether the selected window is on top (not occluded by any
     49  // windows except for those it owns or any excluded window).
     50  bool IsTopWindow() {
     51    if (!IsSelectedWindowValid()) {
     52      return false;
     53    }
     54 
     55    // Enumerate all top-level windows above the selected window in Z-order,
     56    // checking whether any overlaps it. This uses FindWindowEx rather than
     57    // EnumWindows because the latter excludes certain system windows (e.g. the
     58    // Start menu & other taskbar menus) that should be detected here to avoid
     59    // inadvertent capture.
     60    int num_retries = 0;
     61    while (true) {
     62      HWND hwnd = nullptr;
     63      while ((hwnd = FindWindowEx(nullptr, hwnd, nullptr, nullptr))) {
     64        if (hwnd == selected_window()) {
     65          // Windows are enumerated in top-down Z-order, so we can stop
     66          // enumerating upon reaching the selected window & report it's on top.
     67          return true;
     68        }
     69 
     70        // Ignore the excluded window.
     71        if (hwnd == excluded_window) {
     72          continue;
     73        }
     74 
     75        // Ignore windows that aren't visible on the current desktop.
     76        if (!window_capture_helper()->IsWindowVisibleOnCurrentDesktop(hwnd)) {
     77          continue;
     78        }
     79 
     80        // Ignore Chrome notification windows, especially the notification for
     81        // the ongoing window sharing. Notes:
     82        // - This only works with notifications from Chrome, not other Apps.
     83        // - All notifications from Chrome will be ignored.
     84        // - This may cause part or whole of notification window being cropped
     85        // into the capturing of the target window if there is overlapping.
     86        if (window_capture_helper()->IsWindowChromeNotification(hwnd)) {
     87          continue;
     88        }
     89 
     90        // Ignore windows owned by the selected window since we want to capture
     91        // them.
     92        if (IsWindowOwnedBySelectedWindow(hwnd)) {
     93          continue;
     94        }
     95 
     96        // Check whether this window intersects with the selected window.
     97        if (IsWindowOverlappingSelectedWindow(hwnd)) {
     98          // If intersection is not empty, the selected window is not on top.
     99          return false;
    100        }
    101      }
    102 
    103      DWORD lastError = GetLastError();
    104      if (lastError == ERROR_SUCCESS) {
    105        // The enumeration completed successfully without finding the selected
    106        // window (which may have been closed).
    107        RTC_LOG(LS_WARNING) << "Failed to find selected window (only expected "
    108                               "if it was closed)";
    109        RTC_DCHECK(!IsWindow(selected_window()));
    110        return false;
    111      } else if (lastError == ERROR_INVALID_WINDOW_HANDLE) {
    112        // This error may occur if a window is closed around the time it's
    113        // enumerated; retry the enumeration in this case up to 10 times
    114        // (this should be a rare race & unlikely to recur).
    115        if (++num_retries <= 10) {
    116          RTC_LOG(LS_WARNING) << "Enumeration failed due to race with a window "
    117                                 "closing; retrying - retry #"
    118                              << num_retries;
    119          continue;
    120        } else {
    121          RTC_LOG(LS_ERROR)
    122              << "Exhausted retry allowance around window enumeration failures "
    123                 "due to races with windows closing";
    124        }
    125      }
    126 
    127      // The enumeration failed with an unexpected error (or more repeats of
    128      // an infrequently-expected error than anticipated). After logging this &
    129      // firing an assert when enabled, report that the selected window isn't
    130      // topmost to avoid inadvertent capture of other windows.
    131      RTC_LOG(LS_ERROR) << "Failed to enumerate windows: " << lastError;
    132      RTC_DCHECK_NOTREACHED();
    133      return false;
    134    }
    135  }
    136 
    137  const HWND excluded_window;
    138 };
    139 
    140 class CroppingWindowCapturerWin : public CroppingWindowCapturer {
    141 public:
    142  explicit CroppingWindowCapturerWin(const DesktopCaptureOptions& options)
    143      : CroppingWindowCapturer(options),
    144        enumerate_current_process_windows_(
    145            options.enumerate_current_process_windows()),
    146        full_screen_window_detector_(options.full_screen_window_detector()) {}
    147 
    148  void CaptureFrame() override;
    149 
    150 private:
    151  bool ShouldUseScreenCapturer() override;
    152  DesktopRect GetWindowRectInVirtualScreen() override;
    153 
    154  // Returns either selected by user sourceId or sourceId provided by
    155  // FullScreenWindowDetector
    156  WindowId GetWindowToCapture() const;
    157 
    158  // The region from GetWindowRgn in the desktop coordinate if the region is
    159  // rectangular, or the rect from GetWindowRect if the region is not set.
    160  DesktopRect window_region_rect_;
    161 
    162  WindowCaptureHelperWin window_capture_helper_;
    163 
    164  bool enumerate_current_process_windows_;
    165 
    166  scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_;
    167 
    168  // Used to make sure that we only log the usage of fullscreen detection once.
    169  mutable bool fullscreen_usage_logged_ = false;
    170 };
    171 
    172 void CroppingWindowCapturerWin::CaptureFrame() {
    173  DesktopCapturer* win_capturer = window_capturer();
    174  if (win_capturer) {
    175    // Feed the actual list of windows into full screen window detector.
    176    if (full_screen_window_detector_) {
    177      full_screen_window_detector_->UpdateWindowListIfNeeded(
    178          selected_window(), [this](DesktopCapturer::SourceList* sources) {
    179            // Get the list of top level windows, including ones with empty
    180            // title. win_capturer_->GetSourceList can't be used here
    181            // cause it filters out the windows with empty titles and
    182            // it uses responsiveness check which could lead to performance
    183            // issues.
    184            SourceList result;
    185            int window_list_flags =
    186                enumerate_current_process_windows_
    187                    ? GetWindowListFlags::kNone
    188                    : GetWindowListFlags::kIgnoreCurrentProcessWindows;
    189 
    190            if (!GetWindowList(window_list_flags, &result))
    191              return false;
    192 
    193            // Filter out windows not visible on current desktop
    194            std::erase_if(
    195                result, [this](const auto& source) {
    196                  HWND hwnd = reinterpret_cast<HWND>(source.id);
    197                  return !window_capture_helper_
    198                              .IsWindowVisibleOnCurrentDesktop(hwnd);
    199                });
    200 
    201            sources->swap(result);
    202            return true;
    203          });
    204    }
    205    win_capturer->SelectSource(GetWindowToCapture());
    206  }
    207 
    208  CroppingWindowCapturer::CaptureFrame();
    209 }
    210 
    211 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
    212  if (rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN8 &&
    213      window_capture_helper_.IsAeroEnabled()) {
    214    return false;
    215  }
    216 
    217  const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture());
    218  // Check if the window is visible on current desktop.
    219  if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
    220    return false;
    221  }
    222 
    223  // Check if the window is a translucent layered window.
    224  const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
    225  if (window_ex_style & WS_EX_LAYERED) {
    226    COLORREF color_ref_key = 0;
    227    BYTE alpha = 0;
    228    DWORD flags = 0;
    229 
    230    // GetLayeredWindowAttributes fails if the window was setup with
    231    // UpdateLayeredWindow. We have no way to know the opacity of the window in
    232    // that case. This happens for Stiky Note (crbug/412726).
    233    if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
    234      return false;
    235 
    236    // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
    237    // the previous GetLayeredWindowAttributes to fail. So we only need to check
    238    // the window wide color key or alpha.
    239    if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
    240      return false;
    241    }
    242  }
    243 
    244  if (!GetWindowRect(selected, &window_region_rect_)) {
    245    return false;
    246  }
    247 
    248  DesktopRect content_rect;
    249  if (!GetWindowContentRect(selected, &content_rect)) {
    250    return false;
    251  }
    252 
    253  DesktopRect region_rect;
    254  // Get the window region and check if it is rectangular.
    255  const int region_type =
    256      GetWindowRegionTypeWithBoundary(selected, &region_rect);
    257 
    258  // Do not use the screen capturer if the region is empty or not rectangular.
    259  if (region_type == COMPLEXREGION || region_type == NULLREGION) {
    260    return false;
    261  }
    262 
    263  if (region_type == SIMPLEREGION) {
    264    // The `region_rect` returned from GetRgnBox() is always in window
    265    // coordinate.
    266    region_rect.Translate(window_region_rect_.left(),
    267                          window_region_rect_.top());
    268    // MSDN: The window region determines the area *within* the window where the
    269    // system permits drawing.
    270    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
    271    //
    272    // `region_rect` should always be inside of `window_region_rect_`. So after
    273    // the intersection, `window_region_rect_` == `region_rect`. If so, what's
    274    // the point of the intersecting operations? Why cannot we directly retrieve
    275    // `window_region_rect_` from GetWindowRegionTypeWithBoundary() function?
    276    // TODO(zijiehe): Figure out the purpose of these intersections.
    277    window_region_rect_.IntersectWith(region_rect);
    278    content_rect.IntersectWith(region_rect);
    279  }
    280 
    281  // Check if the client area is out of the screen area. When the window is
    282  // maximized, only its client area is visible in the screen, the border will
    283  // be hidden. So we are using `content_rect` here.
    284  if (!GetFullscreenRect().ContainsRect(content_rect)) {
    285    return false;
    286  }
    287 
    288  // Check if the window is occluded by any other window, excluding the child
    289  // windows, context menus, and `excluded_window_`.
    290  // `content_rect` is preferred, see the comments on
    291  // IsWindowIntersectWithSelectedWindow().
    292  TopWindowVerifierContext context(selected,
    293                                   reinterpret_cast<HWND>(excluded_window()),
    294                                   content_rect, &window_capture_helper_);
    295  return context.IsTopWindow();
    296 }
    297 
    298 DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
    299  TRACE_EVENT0("webrtc",
    300               "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
    301  DesktopRect window_rect;
    302  HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture());
    303  if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect,
    304                            /*original_rect*/ nullptr)) {
    305    RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
    306    return window_rect;
    307  }
    308  window_rect.IntersectWith(window_region_rect_);
    309 
    310  // Convert `window_rect` to be relative to the top-left of the virtual screen.
    311  DesktopRect screen_rect(GetFullscreenRect());
    312  window_rect.IntersectWith(screen_rect);
    313  window_rect.Translate(-screen_rect.left(), -screen_rect.top());
    314  return window_rect;
    315 }
    316 
    317 WindowId CroppingWindowCapturerWin::GetWindowToCapture() const {
    318  const auto selected_source = selected_window();
    319  const auto full_screen_source =
    320      full_screen_window_detector_
    321          ? full_screen_window_detector_->FindFullScreenWindow(selected_source)
    322          : 0;
    323  if (full_screen_source && full_screen_source != selected_source &&
    324      !fullscreen_usage_logged_) {
    325    fullscreen_usage_logged_ = true;
    326    LogDesktopCapturerFullscreenDetectorUsage();
    327  }
    328  return full_screen_source ? full_screen_source : selected_source;
    329 }
    330 
    331 }  // namespace
    332 
    333 // static
    334 std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
    335    const DesktopCaptureOptions& options) {
    336  std::unique_ptr<DesktopCapturer> capturer(
    337      new CroppingWindowCapturerWin(options));
    338  if (capturer && options.detect_updated_region()) {
    339    capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
    340  }
    341 
    342  return capturer;
    343 }
    344 
    345 }  // namespace webrtc