Makes ScreenCapturerMac exclude the window specified in DesktopCapturer::SetExcludedWindow.
No behavior change for now since Chromium has not been updated to call SetExcludedWindow.

BUG=2789
R=sergeyu@chromium.org

Review URL: https://webrtc-codereview.appspot.com/10299004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5792 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
index 05e2a9b..2547ba3 100644
--- a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
+++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
@@ -142,6 +142,10 @@
   desktop_capturer_->Capture(region);
 }
 
+void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) {
+  desktop_capturer_->SetExcludedWindow(window);
+}
+
 SharedMemory* DesktopAndCursorComposer::CreateSharedMemory(size_t size) {
   return callback_->CreateSharedMemory(size);
 }
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
index 4f7c85b..3fac021 100644
--- a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
+++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
@@ -34,6 +34,7 @@
   // DesktopCapturer interface.
   virtual void Start(DesktopCapturer::Callback* callback) OVERRIDE;
   virtual void Capture(const DesktopRegion& region) OVERRIDE;
+  virtual void SetExcludedWindow(WindowId window) OVERRIDE;
 
  private:
   // DesktopCapturer::Callback interface.
diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h
index bcb664e..7ad1636 100644
--- a/webrtc/modules/desktop_capture/desktop_capturer.h
+++ b/webrtc/modules/desktop_capture/desktop_capturer.h
@@ -13,6 +13,8 @@
 
 #include <stddef.h>
 
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+
 namespace webrtc {
 
 class DesktopFrame;
@@ -52,6 +54,11 @@
   // the top left corner of the capture target. Pending capture operations are
   // canceled when DesktopCapturer is deleted.
   virtual void Capture(const DesktopRegion& region) = 0;
+
+  // Sets the window to be excluded from the captured image in the future
+  // Capture calls. Used to exclude the screenshare notification window for
+  // screen capturing.
+  virtual void SetExcludedWindow(WindowId window) {}
 };
 
 }  // namespace webrtc
diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac.mm b/webrtc/modules/desktop_capture/screen_capturer_mac.mm
index 100309f..2d57339 100644
--- a/webrtc/modules/desktop_capture/screen_capturer_mac.mm
+++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm
@@ -63,7 +63,8 @@
     static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale)));
 }
 
-// Copy pixels in the |rect| from |src_place| to |dest_plane|.
+// Copy pixels in the |rect| from |src_place| to |dest_plane|. |rect| should be
+// relative to the origin of |src_plane| and |dest_plane|.
 void CopyRect(const uint8_t* src_plane,
               int src_plane_stride,
               uint8_t* dest_plane,
@@ -87,6 +88,105 @@
   }
 }
 
+// Returns an array of CGWindowID for all the on-screen windows except
+// |window_to_exclude|, or NULL if the window is not found or it fails. The
+// caller should release the returned CFArrayRef.
+CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) {
+  if (!window_to_exclude)
+    return NULL;
+
+  CFArrayRef all_windows = CGWindowListCopyWindowInfo(
+      kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
+  if (!all_windows)
+    return NULL;
+
+  CFMutableArrayRef returned_array = CFArrayCreateMutable(
+      NULL, CFArrayGetCount(all_windows), NULL);
+
+  bool found = false;
+  for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) {
+    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+        CFArrayGetValueAtIndex(all_windows, i));
+
+    CFNumberRef id_ref = reinterpret_cast<CFNumberRef>(
+        CFDictionaryGetValue(window, kCGWindowNumber));
+
+    CGWindowID id;
+    CFNumberGetValue(id_ref, kCFNumberIntType, &id);
+    if (id == window_to_exclude) {
+      found = true;
+      continue;
+    }
+    CFArrayAppendValue(returned_array, reinterpret_cast<void *>(id));
+  }
+  CFRelease(all_windows);
+
+  if (!found) {
+    CFRelease(returned_array);
+    returned_array = NULL;
+  }
+  return returned_array;
+}
+
+// Returns the bounds of |window| in physical pixels, enlarged by a small amount
+// on four edges to take account of the border/shadow effects.
+DesktopRect GetExcludedWindowPixelBounds(CGWindowID window,
+                                         float dip_to_pixel_scale) {
+  // The amount of pixels to add to the actual window bounds to take into
+  // account of the border/shadow effects.
+  static const int kBorderEffectSize = 20;
+  CGRect rect;
+  CGWindowID ids[1];
+  ids[0] = window;
+
+  CFArrayRef window_id_array =
+      CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+  CFArrayRef window_array =
+      CGWindowListCreateDescriptionFromArray(window_id_array);
+
+  if (CFArrayGetCount(window_array) > 0) {
+    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+        CFArrayGetValueAtIndex(window_array, 0));
+    CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
+        CFDictionaryGetValue(window, kCGWindowBounds));
+    CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect);
+  }
+
+  CFRelease(window_id_array);
+  CFRelease(window_array);
+
+  rect.origin.x -= kBorderEffectSize;
+  rect.origin.y -= kBorderEffectSize;
+  rect.size.width += kBorderEffectSize * 2;
+  rect.size.height += kBorderEffectSize * 2;
+  // |rect| is in DIP, so convert to physical pixels.
+  return ScaleAndRoundCGRect(rect, dip_to_pixel_scale);
+}
+
+// Create an image of the given region using the given |window_list|.
+// |pixel_bounds| should be in the primary display's coordinate in physical
+// pixels. The caller should release the returned CGImageRef and CFDataRef.
+CGImageRef CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds,
+                                           float dip_to_pixel_scale,
+                                           CFArrayRef window_list,
+                                           CFDataRef* data_ref) {
+  CGRect window_bounds;
+  // The origin is in DIP while the size is in physical pixels. That's what
+  // CGWindowListCreateImageFromArray expects.
+  window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale;
+  window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale;
+  window_bounds.size.width = pixel_bounds.width();
+  window_bounds.size.height = pixel_bounds.height();
+
+  CGImageRef excluded_image = CGWindowListCreateImageFromArray(
+      window_bounds, window_list, kCGWindowImageDefault);
+
+  CGDataProviderRef provider = CGImageGetDataProvider(excluded_image);
+  *data_ref = CGDataProviderCopyData(provider);
+  assert(*data_ref);
+  return excluded_image;
+}
+
 // A class to perform video frame capturing for mac.
 class ScreenCapturerMac : public ScreenCapturer {
  public:
@@ -99,6 +199,7 @@
   // Overridden from ScreenCapturer:
   virtual void Start(Callback* callback) OVERRIDE;
   virtual void Capture(const DesktopRegion& region) OVERRIDE;
+  virtual void SetExcludedWindow(WindowId window) OVERRIDE;
   virtual void SetMouseShapeObserver(
       MouseShapeObserver* mouse_shape_observer) OVERRIDE;
   virtual bool GetScreenList(ScreenList* screens) OVERRIDE;
@@ -186,6 +287,8 @@
   void* opengl_library_;
   CGLSetFullScreenFunc cgl_set_full_screen_;
 
+  CGWindowID excluded_window_;
+
   DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac);
 };
 
@@ -227,7 +330,8 @@
       cg_display_bytes_per_row_(NULL),
       cg_display_bits_per_pixel_(NULL),
       opengl_library_(NULL),
-      cgl_set_full_screen_(NULL) {
+      cgl_set_full_screen_(NULL),
+      excluded_window_(0) {
 }
 
 ScreenCapturerMac::~ScreenCapturerMac() {
@@ -291,8 +395,7 @@
                               &power_assertion_id_user_);
 }
 
-void ScreenCapturerMac::Capture(
-    const DesktopRegion& region_to_capture) {
+void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) {
   TickTime capture_start_time = TickTime::Now();
 
   queue_.MoveToNextFrame();
@@ -362,6 +465,10 @@
   callback_->OnCaptureCompleted(new_frame);
 }
 
+void ScreenCapturerMac::SetExcludedWindow(WindowId window) {
+  excluded_window_ = window;
+}
+
 void ScreenCapturerMac::SetMouseShapeObserver(
       MouseShapeObserver* mouse_shape_observer) {
   assert(!mouse_shape_observer_);
@@ -631,6 +738,9 @@
     displays_to_capture = desktop_config_.displays;
   }
 
+  // Create the window list once for all displays.
+  CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_);
+
   for (size_t i = 0; i < displays_to_capture.size(); ++i) {
     const MacDisplayConfiguration& display_config = displays_to_capture[i];
 
@@ -655,6 +765,26 @@
     // Translate the region to be copied into display-relative coordinates.
     copy_region.Translate(-display_bounds.left(), -display_bounds.top());
 
+    DesktopRect excluded_window_bounds;
+    CGImageRef excluded_image = NULL;
+    CFDataRef excluded_window_region_data = NULL;
+    if (excluded_window_ && window_list) {
+      // Get the region of the excluded window relative the primary display.
+      excluded_window_bounds = GetExcludedWindowPixelBounds(
+          excluded_window_, display_config.dip_to_pixel_scale);
+      excluded_window_bounds.IntersectWith(display_config.pixel_bounds);
+
+      // Create the image under the excluded window first, because it's faster
+      // than captuing the whole display.
+      if (!excluded_window_bounds.is_empty()) {
+        excluded_image = CreateExcludedWindowRegionImage(
+            excluded_window_bounds,
+            display_config.dip_to_pixel_scale,
+            window_list,
+            &excluded_window_region_data);
+      }
+    }
+
     // Create an image containing a snapshot of the display.
     CGImageRef image = CGDisplayCreateImage(display_config.id);
     if (image == NULL)
@@ -684,9 +814,36 @@
                i.rect());
     }
 
+    // Copy the region of the excluded window to the frame.
+    if (excluded_image) {
+      assert(excluded_window_region_data);
+      display_base_address = CFDataGetBytePtr(excluded_window_region_data);
+      src_bytes_per_row = CGImageGetBytesPerRow(excluded_image);
+
+      // Translate the bounds relative to the desktop, because |frame| data
+      // starts from the desktop top-left corner.
+      DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds);
+      window_bounds_relative_to_desktop.Translate(
+          -screen_pixel_bounds_.left(), -screen_pixel_bounds_.top());
+      out_ptr = frame.data() +
+          (window_bounds_relative_to_desktop.left() * src_bytes_per_pixel) +
+          (window_bounds_relative_to_desktop.top() * frame.stride());
+
+      CopyRect(display_base_address,
+               src_bytes_per_row,
+               out_ptr,
+               frame.stride(),
+               src_bytes_per_pixel,
+               DesktopRect::MakeSize(excluded_window_bounds.size()));
+      CFRelease(excluded_window_region_data);
+      CFRelease(excluded_image);
+    }
+
     CFRelease(data);
     CFRelease(image);
   }
+  if (window_list)
+    CFRelease(window_list);
   return true;
 }