tor-browser

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

screen_capturer_mac.mm (22598B)


      1 /*
      2 *  Copyright (c) 2013 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 <utility>
     12 
     13 #include "modules/desktop_capture/mac/screen_capturer_mac.h"
     14 
     15 #include "modules/desktop_capture/mac/desktop_frame_provider.h"
     16 #include "modules/desktop_capture/mac/window_list_utils.h"
     17 #include "rtc_base/checks.h"
     18 #include "rtc_base/logging.h"
     19 #include "rtc_base/time_utils.h"
     20 #include "rtc_base/trace_event.h"
     21 #include "sdk/objc/helpers/scoped_cftyperef.h"
     22 
     23 namespace webrtc {
     24 
     25 namespace {
     26 
     27 // Scales all coordinates of a rect by a specified factor.
     28 DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) {
     29  return DesktopRect::MakeLTRB(
     30      static_cast<int>(floor(rect.origin.x * scale)),
     31      static_cast<int>(floor(rect.origin.y * scale)),
     32      static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)),
     33      static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale)));
     34 }
     35 
     36 // Copy pixels in the `rect` from `src_place` to `dest_plane`. `rect` should be
     37 // relative to the origin of `src_plane` and `dest_plane`.
     38 void CopyRect(const uint8_t* src_plane,
     39              int src_plane_stride,
     40              uint8_t* dest_plane,
     41              int dest_plane_stride,
     42              int bytes_per_pixel,
     43              const DesktopRect& rect) {
     44  // Get the address of the starting point.
     45  const int src_y_offset = src_plane_stride * rect.top();
     46  const int dest_y_offset = dest_plane_stride * rect.top();
     47  const int x_offset = bytes_per_pixel * rect.left();
     48  src_plane += src_y_offset + x_offset;
     49  dest_plane += dest_y_offset + x_offset;
     50 
     51  // Copy pixels in the rectangle line by line.
     52  const int bytes_per_line = bytes_per_pixel * rect.width();
     53  const int height = rect.height();
     54  for (int i = 0; i < height; ++i) {
     55    memcpy(dest_plane, src_plane, bytes_per_line);
     56    src_plane += src_plane_stride;
     57    dest_plane += dest_plane_stride;
     58  }
     59 }
     60 
     61 // Returns an array of CGWindowID for all the on-screen windows except
     62 // `window_to_exclude`, or NULL if the window is not found or it fails. The
     63 // caller should release the returned CFArrayRef.
     64 CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) {
     65  if (!window_to_exclude) return nullptr;
     66 
     67  CFArrayRef all_windows = CGWindowListCopyWindowInfo(
     68      kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
     69  if (!all_windows) return nullptr;
     70 
     71  CFMutableArrayRef returned_array =
     72      CFArrayCreateMutable(nullptr, CFArrayGetCount(all_windows), nullptr);
     73 
     74  bool found = false;
     75  for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) {
     76    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
     77        CFArrayGetValueAtIndex(all_windows, i));
     78 
     79    CGWindowID id = GetWindowId(window);
     80    if (id == window_to_exclude) {
     81      found = true;
     82      continue;
     83    }
     84    CFArrayAppendValue(returned_array, reinterpret_cast<void*>(id));
     85  }
     86  CFRelease(all_windows);
     87 
     88  if (!found) {
     89    CFRelease(returned_array);
     90    returned_array = nullptr;
     91  }
     92  return returned_array;
     93 }
     94 
     95 // Returns the bounds of `window` in physical pixels, enlarged by a small amount
     96 // on four edges to take account of the border/shadow effects.
     97 DesktopRect GetExcludedWindowPixelBounds(CGWindowID window,
     98                                         float dip_to_pixel_scale) {
     99  // The amount of pixels to add to the actual window bounds to take into
    100  // account of the border/shadow effects.
    101  static const int kBorderEffectSize = 20;
    102  CGRect rect;
    103  CGWindowID ids[1];
    104  ids[0] = window;
    105 
    106  CFArrayRef window_id_array =
    107      CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr);
    108  CFArrayRef window_array =
    109      CGWindowListCreateDescriptionFromArray(window_id_array);
    110 
    111  if (CFArrayGetCount(window_array) > 0) {
    112    CFDictionaryRef win = reinterpret_cast<CFDictionaryRef>(
    113        CFArrayGetValueAtIndex(window_array, 0));
    114    CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
    115        CFDictionaryGetValue(win, kCGWindowBounds));
    116    CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect);
    117  }
    118 
    119  CFRelease(window_id_array);
    120  CFRelease(window_array);
    121 
    122  rect.origin.x -= kBorderEffectSize;
    123  rect.origin.y -= kBorderEffectSize;
    124  rect.size.width += kBorderEffectSize * 2;
    125  rect.size.height += kBorderEffectSize * 2;
    126  // `rect` is in DIP, so convert to physical pixels.
    127  return ScaleAndRoundCGRect(rect, dip_to_pixel_scale);
    128 }
    129 
    130 // Create an image of the given region using the given `window_list`.
    131 // `pixel_bounds` should be in the primary display's coordinate in physical
    132 // pixels.
    133 webrtc::ScopedCFTypeRef<CGImageRef> CreateExcludedWindowRegionImage(
    134    const DesktopRect& pixel_bounds,
    135    float dip_to_pixel_scale,
    136    CFArrayRef window_list) {
    137  CGRect window_bounds;
    138  // The origin is in DIP while the size is in physical pixels. That's what
    139  // CGWindowListCreateImageFromArray expects.
    140  window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale;
    141  window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale;
    142  window_bounds.size.width = pixel_bounds.width();
    143  window_bounds.size.height = pixel_bounds.height();
    144 
    145  return webrtc::ScopedCFTypeRef<CGImageRef>(CGWindowListCreateImageFromArray(
    146      window_bounds, window_list, kCGWindowImageDefault));
    147 }
    148 
    149 }  // namespace
    150 
    151 ScreenCapturerMac::ScreenCapturerMac(
    152    webrtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor,
    153    bool detect_updated_region,
    154    bool allow_iosurface)
    155    : detect_updated_region_(detect_updated_region),
    156      desktop_config_monitor_(desktop_config_monitor),
    157      desktop_frame_provider_(allow_iosurface) {
    158  RTC_LOG(LS_INFO) << "Allow IOSurface: " << allow_iosurface;
    159  thread_checker_.Detach();
    160 }
    161 
    162 ScreenCapturerMac::~ScreenCapturerMac() {
    163  RTC_DCHECK(thread_checker_.IsCurrent());
    164  ReleaseBuffers();
    165  UnregisterRefreshAndMoveHandlers();
    166 }
    167 
    168 bool ScreenCapturerMac::Init() {
    169  TRACE_EVENT0("webrtc", "ScreenCapturerMac::Init");
    170  desktop_config_ = desktop_config_monitor_->desktop_configuration();
    171  return true;
    172 }
    173 
    174 void ScreenCapturerMac::ReleaseBuffers() {
    175  // The buffers might be in use by the encoder, so don't delete them here.
    176  // Instead, mark them as "needs update"; next time the buffers are used by
    177  // the capturer, they will be recreated if necessary.
    178  queue_.Reset();
    179 }
    180 
    181 void ScreenCapturerMac::Start(Callback* callback) {
    182  RTC_DCHECK(thread_checker_.IsCurrent());
    183  RTC_DCHECK(!callback_);
    184  RTC_DCHECK(callback);
    185  TRACE_EVENT_INSTANT1("webrtc",
    186                       "ScreenCapturermac::Start",
    187                       TRACE_EVENT_SCOPE_GLOBAL,
    188                       "target display id ",
    189                       current_display_);
    190 
    191  callback_ = callback;
    192  update_screen_configuration_ = false;
    193  // Start and operate CGDisplayStream handler all from capture thread.
    194  if (!RegisterRefreshAndMoveHandlers()) {
    195    RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers.";
    196    callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
    197    return;
    198  }
    199  ScreenConfigurationChanged();
    200 }
    201 
    202 void ScreenCapturerMac::CaptureFrame() {
    203  RTC_DCHECK(thread_checker_.IsCurrent());
    204  TRACE_EVENT0("webrtc", "creenCapturerMac::CaptureFrame");
    205  int64_t capture_start_time_nanos = TimeNanos();
    206 
    207  queue_.MoveToNextFrame();
    208  if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
    209    RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
    210  }
    211 
    212  MacDesktopConfiguration new_config =
    213      desktop_config_monitor_->desktop_configuration();
    214  if (update_screen_configuration_ || !desktop_config_.Equals(new_config)) {
    215    update_screen_configuration_ = false;
    216    desktop_config_ = new_config;
    217    // If the display configuraiton has changed then refresh capturer data
    218    // structures. Occasionally, the refresh and move handlers are lost when
    219    // the screen mode changes, so re-register them here.
    220    UnregisterRefreshAndMoveHandlers();
    221    if (!RegisterRefreshAndMoveHandlers()) {
    222      RTC_LOG(LS_ERROR) << "Failed to register refresh and move handlers.";
    223      callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
    224      return;
    225    }
    226    ScreenConfigurationChanged();
    227  }
    228 
    229  // When screen is zoomed in/out, OSX only updates the part of Rects currently
    230  // displayed on screen, with relative location to current top-left on screen.
    231  // This will cause problems when we copy the dirty regions to the captured
    232  // image. So we invalidate the whole screen to copy all the screen contents.
    233  // With CGI method, the zooming will be ignored and the whole screen contents
    234  // will be captured as before.
    235  // With IOSurface method, the zoomed screen contents will be captured.
    236  if (UAZoomEnabled()) {
    237    helper_.InvalidateScreen(screen_pixel_bounds_.size());
    238  }
    239 
    240  DesktopRegion region;
    241  helper_.TakeInvalidRegion(&region);
    242 
    243  // If the current buffer is from an older generation then allocate a new one.
    244  // Note that we can't reallocate other buffers at this point, since the caller
    245  // may still be reading from them.
    246  if (!queue_.current_frame())
    247    queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(CreateFrame()));
    248 
    249  DesktopFrame* current_frame = queue_.current_frame();
    250 
    251  if (!CgBlit(*current_frame, region)) {
    252    callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
    253    return;
    254  }
    255  std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share();
    256  if (detect_updated_region_) {
    257    *new_frame->mutable_updated_region() = region;
    258  } else {
    259    new_frame->mutable_updated_region()->AddRect(
    260        DesktopRect::MakeSize(new_frame->size()));
    261  }
    262 
    263  if (current_display_) {
    264    const MacDisplayConfiguration* config =
    265        desktop_config_.FindDisplayConfigurationById(current_display_);
    266    if (config) {
    267      new_frame->set_top_left(config->bounds.top_left().subtract(
    268          desktop_config_.bounds.top_left()));
    269    }
    270  }
    271 
    272  helper_.set_size_most_recent(new_frame->size());
    273 
    274  new_frame->set_capture_time_ms(
    275      (webrtc::TimeNanos() - capture_start_time_nanos) /
    276      webrtc::kNumNanosecsPerMillisec);
    277  callback_->OnCaptureResult(Result::SUCCESS, std::move(new_frame));
    278 }
    279 
    280 void ScreenCapturerMac::SetExcludedWindow(WindowId window) {
    281  excluded_window_ = window;
    282 }
    283 
    284 bool ScreenCapturerMac::GetSourceList(SourceList* screens) {
    285  RTC_DCHECK(screens->size() == 0);
    286 
    287  for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin();
    288       it != desktop_config_.displays.end();
    289       ++it) {
    290    Source value = {it->id, 0, std::string()};
    291    screens->push_back(value);
    292  }
    293  return true;
    294 }
    295 
    296 bool ScreenCapturerMac::SelectSource(SourceId id) {
    297  if (id == kFullDesktopScreenId) {
    298    current_display_ = 0;
    299  } else {
    300    const MacDisplayConfiguration* config =
    301        desktop_config_.FindDisplayConfigurationById(
    302            static_cast<CGDirectDisplayID>(id));
    303    if (!config) return false;
    304    current_display_ = config->id;
    305  }
    306 
    307  ScreenConfigurationChanged();
    308  return true;
    309 }
    310 
    311 bool ScreenCapturerMac::CgBlit(const DesktopFrame& frame,
    312                               const DesktopRegion& region) {
    313  // If not all screen region is dirty, copy the entire contents of the previous
    314  // capture buffer, to capture over.
    315  if (queue_.previous_frame() &&
    316      !region.Equals(DesktopRegion(screen_pixel_bounds_))) {
    317    memcpy(frame.data(),
    318           queue_.previous_frame()->data(),
    319           frame.stride() * frame.size().height());
    320  }
    321 
    322  MacDisplayConfigurations displays_to_capture;
    323  if (current_display_) {
    324    // Capturing a single screen. Note that the screen id may change when
    325    // screens are added or removed.
    326    const MacDisplayConfiguration* config =
    327        desktop_config_.FindDisplayConfigurationById(current_display_);
    328    if (config) {
    329      displays_to_capture.push_back(*config);
    330    } else {
    331      RTC_LOG(LS_ERROR) << "The selected screen cannot be found for capturing.";
    332      return false;
    333    }
    334  } else {
    335    // Capturing the whole desktop.
    336    displays_to_capture = desktop_config_.displays;
    337  }
    338 
    339  // Create the window list once for all displays.
    340  CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_);
    341 
    342  for (size_t i = 0; i < displays_to_capture.size(); ++i) {
    343    const MacDisplayConfiguration& display_config = displays_to_capture[i];
    344 
    345    // Capturing mixed-DPI on one surface is hard, so we only return displays
    346    // that match the "primary" display's DPI. The primary display is always
    347    // the first in the list.
    348    if (i > 0 &&
    349        display_config.dip_to_pixel_scale !=
    350            displays_to_capture[0].dip_to_pixel_scale) {
    351      continue;
    352    }
    353    // Determine the display's position relative to the desktop, in pixels.
    354    DesktopRect display_bounds = display_config.pixel_bounds;
    355    display_bounds.Translate(-screen_pixel_bounds_.left(),
    356                             -screen_pixel_bounds_.top());
    357 
    358    // Determine which parts of the blit region, if any, lay within the monitor.
    359    DesktopRegion copy_region = region;
    360    copy_region.IntersectWith(display_bounds);
    361    if (copy_region.is_empty()) continue;
    362 
    363    // Translate the region to be copied into display-relative coordinates.
    364    copy_region.Translate(-display_bounds.left(), -display_bounds.top());
    365 
    366    DesktopRect excluded_window_bounds;
    367    webrtc::ScopedCFTypeRef<CGImageRef> excluded_image;
    368    if (excluded_window_ && window_list) {
    369      // Get the region of the excluded window relative the primary display.
    370      excluded_window_bounds = GetExcludedWindowPixelBounds(
    371          excluded_window_, display_config.dip_to_pixel_scale);
    372      excluded_window_bounds.IntersectWith(display_config.pixel_bounds);
    373 
    374      // Create the image under the excluded window first, because it's faster
    375      // than captuing the whole display.
    376      if (!excluded_window_bounds.is_empty()) {
    377        excluded_image =
    378            CreateExcludedWindowRegionImage(excluded_window_bounds,
    379                                            display_config.dip_to_pixel_scale,
    380                                            window_list);
    381      }
    382    }
    383 
    384    std::unique_ptr<DesktopFrame> frame_source =
    385        desktop_frame_provider_.TakeLatestFrameForDisplay(display_config.id);
    386    if (!frame_source) {
    387      continue;
    388    }
    389    RTC_CHECK_EQ(frame_source->pixel_format(), frame.pixel_format());
    390 
    391    const uint8_t* display_base_address = frame_source->data();
    392    int src_bytes_per_row = frame_source->stride();
    393    RTC_DCHECK(display_base_address);
    394 
    395    // `frame_source` size may be different from display_bounds in case the
    396    // screen was resized recently.
    397    copy_region.IntersectWith(frame_source->rect());
    398 
    399    // Copy the dirty region from the display buffer into our desktop buffer.
    400    uint8_t* out_ptr = frame.GetFrameDataAtPos(display_bounds.top_left());
    401    for (DesktopRegion::Iterator it(copy_region); !it.IsAtEnd(); it.Advance()) {
    402      CopyRect(display_base_address,
    403               src_bytes_per_row,
    404               out_ptr,
    405               frame.stride(),
    406               DesktopFrame::kBytesPerPixel,
    407               it.rect());
    408    }
    409 
    410    if (excluded_image) {
    411      CGDataProviderRef provider = CGImageGetDataProvider(excluded_image.get());
    412      webrtc::ScopedCFTypeRef<CFDataRef> excluded_image_data(
    413          CGDataProviderCopyData(provider));
    414      RTC_DCHECK(excluded_image_data);
    415      display_base_address = CFDataGetBytePtr(excluded_image_data.get());
    416      src_bytes_per_row = CGImageGetBytesPerRow(excluded_image.get());
    417 
    418      // Translate the bounds relative to the desktop, because `frame` data
    419      // starts from the desktop top-left corner.
    420      DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds);
    421      window_bounds_relative_to_desktop.Translate(-screen_pixel_bounds_.left(),
    422                                                  -screen_pixel_bounds_.top());
    423 
    424      DesktopRect rect_to_copy =
    425          DesktopRect::MakeSize(excluded_window_bounds.size());
    426      rect_to_copy.IntersectWith(
    427          DesktopRect::MakeWH(CGImageGetWidth(excluded_image.get()),
    428                              CGImageGetHeight(excluded_image.get())));
    429 
    430      if (CGImageGetBitsPerPixel(excluded_image.get()) / 8 ==
    431          DesktopFrame::kBytesPerPixel) {
    432        CopyRect(display_base_address,
    433                 src_bytes_per_row,
    434                 frame.GetFrameDataAtPos(
    435                     window_bounds_relative_to_desktop.top_left()),
    436                 frame.stride(),
    437                 DesktopFrame::kBytesPerPixel,
    438                 rect_to_copy);
    439      }
    440    }
    441  }
    442  if (window_list) CFRelease(window_list);
    443  return true;
    444 }
    445 
    446 void ScreenCapturerMac::ScreenConfigurationChanged() {
    447  if (current_display_) {
    448    const MacDisplayConfiguration* config =
    449        desktop_config_.FindDisplayConfigurationById(current_display_);
    450    screen_pixel_bounds_ = config ? config->pixel_bounds : DesktopRect();
    451    dip_to_pixel_scale_ = config ? config->dip_to_pixel_scale : 1.0f;
    452  } else {
    453    screen_pixel_bounds_ = desktop_config_.pixel_bounds;
    454    dip_to_pixel_scale_ = desktop_config_.dip_to_pixel_scale;
    455  }
    456 
    457  // Release existing buffers, which will be of the wrong size.
    458  ReleaseBuffers();
    459 
    460  // Clear the dirty region, in case the display is down-sizing.
    461  helper_.ClearInvalidRegion();
    462 
    463  // Re-mark the entire desktop as dirty.
    464  helper_.InvalidateScreen(screen_pixel_bounds_.size());
    465 
    466  // Make sure the frame buffers will be reallocated.
    467  queue_.Reset();
    468 }
    469 
    470 bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() {
    471  RTC_DCHECK(thread_checker_.IsCurrent());
    472  if (!desktop_frame_provider_.allow_iosurface()) {
    473    return true;
    474  }
    475 
    476  desktop_config_ = desktop_config_monitor_->desktop_configuration();
    477  for (const auto& config : desktop_config_.displays) {
    478    size_t pixel_width = config.pixel_bounds.width();
    479    size_t pixel_height = config.pixel_bounds.height();
    480    if (pixel_width == 0 || pixel_height == 0) continue;
    481    CGDirectDisplayID display_id = config.id;
    482    DesktopVector display_origin = config.pixel_bounds.top_left();
    483 
    484    CGDisplayStreamFrameAvailableHandler handler = ^(
    485        CGDisplayStreamFrameStatus status,
    486        uint64_t /* display_time */,
    487        IOSurfaceRef frame_surface,
    488        CGDisplayStreamUpdateRef updateRef) {
    489      RTC_DCHECK(thread_checker_.IsCurrent());
    490      if (status == kCGDisplayStreamFrameStatusStopped) return;
    491 
    492      // Only pay attention to frame updates.
    493      if (status != kCGDisplayStreamFrameStatusFrameComplete) return;
    494 
    495      size_t count = 0;
    496      const CGRect* rects = CGDisplayStreamUpdateGetRects(
    497          updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
    498      if (count != 0) {
    499        // According to CGDisplayStream.h, it's safe to call
    500        // CGDisplayStreamStop() from within the callback.
    501        ScreenRefresh(display_id, count, rects, display_origin, frame_surface);
    502      }
    503    };
    504 
    505    webrtc::ScopedCFTypeRef<CFDictionaryRef> properties_dict(
    506        CFDictionaryCreate(kCFAllocatorDefault,
    507                           (const void*[]){kCGDisplayStreamShowCursor},
    508                           (const void*[]){kCFBooleanFalse},
    509                           1,
    510                           &kCFTypeDictionaryKeyCallBacks,
    511                           &kCFTypeDictionaryValueCallBacks));
    512 
    513    CGDisplayStreamRef display_stream =
    514        CGDisplayStreamCreate(display_id,
    515                              pixel_width,
    516                              pixel_height,
    517                              'BGRA',
    518                              properties_dict.get(),
    519                              handler);
    520 
    521    if (display_stream) {
    522      CGError error = CGDisplayStreamStart(display_stream);
    523      if (error != kCGErrorSuccess) return false;
    524 
    525      CFRunLoopSourceRef source =
    526          CGDisplayStreamGetRunLoopSource(display_stream);
    527      CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    528      display_streams_.push_back(display_stream);
    529    }
    530  }
    531 
    532  return true;
    533 }
    534 
    535 void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() {
    536  RTC_DCHECK(thread_checker_.IsCurrent());
    537 
    538  for (CGDisplayStreamRef stream : display_streams_) {
    539    CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(stream);
    540    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    541    CGDisplayStreamStop(stream);
    542    CFRelease(stream);
    543  }
    544  display_streams_.clear();
    545 
    546  // Release obsolete io surfaces.
    547  desktop_frame_provider_.Release();
    548 }
    549 
    550 void ScreenCapturerMac::ScreenRefresh(CGDirectDisplayID display_id,
    551                                      CGRectCount count,
    552                                      const CGRect* rect_array,
    553                                      DesktopVector display_origin,
    554                                      IOSurfaceRef io_surface) {
    555  if (screen_pixel_bounds_.is_empty()) ScreenConfigurationChanged();
    556 
    557  // The refresh rects are in display coordinates. We want to translate to
    558  // framebuffer coordinates. If a specific display is being captured, then no
    559  // change is necessary. If all displays are being captured, then we want to
    560  // translate by the origin of the display.
    561  DesktopVector translate_vector;
    562  if (!current_display_) translate_vector = display_origin;
    563 
    564  DesktopRegion region;
    565  for (CGRectCount i = 0; i < count; ++i) {
    566    // All rects are already in physical pixel coordinates.
    567    DesktopRect rect = DesktopRect::MakeXYWH(rect_array[i].origin.x,
    568                                             rect_array[i].origin.y,
    569                                             rect_array[i].size.width,
    570                                             rect_array[i].size.height);
    571 
    572    rect.Translate(translate_vector);
    573 
    574    region.AddRect(rect);
    575  }
    576  // Always having the latest iosurface before invalidating a region.
    577  // See https://bugs.chromium.org/p/webrtc/issues/detail?id=8652 for details.
    578  desktop_frame_provider_.InvalidateIOSurface(
    579      display_id,
    580      webrtc::ScopedCFTypeRef<IOSurfaceRef>(io_surface,
    581                                            webrtc::RetainPolicy::RETAIN));
    582  helper_.InvalidateRegion(region);
    583 }
    584 
    585 std::unique_ptr<DesktopFrame> ScreenCapturerMac::CreateFrame() {
    586  std::unique_ptr<DesktopFrame> frame(
    587      new BasicDesktopFrame(screen_pixel_bounds_.size()));
    588  frame->set_dpi(DesktopVector(kStandardDPI * dip_to_pixel_scale_,
    589                               kStandardDPI * dip_to_pixel_scale_));
    590  return frame;
    591 }
    592 
    593 }  // namespace webrtc