Improve application window picker on Mac that was not listing all apps.

All application with empty title were not listed, for example Photos.
Fallback to owner name in that case while making sure to keep ignoring
the ghost window.
Most of the ghost windows can be filtered with IsWindowOnScreen
or IsWindowFullScreen except a few. For the remaining ghost we check
if there is no other window with the same pid.

Bug: chromium:516230
Test: Hangouts or Rumpus
Change-Id: Ibb9f98887e5aedf822fc0611836b1938b5056d43
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/130360
Commit-Queue: Julien Isorce <julien.isorce@chromium.org>
Reviewed-by: Brave Yao <braveyao@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27401}
diff --git a/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc b/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc
index 3a26c77..a85c275 100644
--- a/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc
+++ b/modules/desktop_capture/mac/full_screen_chrome_window_detector.cc
@@ -24,55 +24,13 @@
 
 const int64_t kUpdateIntervalMs = 500;
 
-std::string GetWindowTitle(CGWindowID id) {
-  CFArrayRef window_id_array =
-      CFArrayCreate(NULL, reinterpret_cast<const void**>(&id), 1, NULL);
-  CFArrayRef window_array =
-      CGWindowListCreateDescriptionFromArray(window_id_array);
-  std::string title;
-
-  if (window_array && CFArrayGetCount(window_array)) {
-    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
-        CFArrayGetValueAtIndex(window_array, 0));
-    CFStringRef title_ref = reinterpret_cast<CFStringRef>(
-        CFDictionaryGetValue(window, kCGWindowName));
-
-    if (title_ref)
-      rtc::ToUtf8(title_ref, &title);
-  }
-  CFRelease(window_id_array);
-  CFRelease(window_array);
-
-  return title;
-}
-
-int GetWindowOwnerPid(CGWindowID id) {
-  CFArrayRef window_id_array =
-      CFArrayCreate(NULL, reinterpret_cast<const void**>(&id), 1, NULL);
-  CFArrayRef window_array =
-      CGWindowListCreateDescriptionFromArray(window_id_array);
-  int pid = 0;
-
-  if (window_array && CFArrayGetCount(window_array)) {
-    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
-        CFArrayGetValueAtIndex(window_array, 0));
-    CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>(
-        CFDictionaryGetValue(window, kCGWindowOwnerPID));
-
-    if (pid_ref)
-      CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
-  }
-  CFRelease(window_id_array);
-  CFRelease(window_array);
-
-  return pid;
-}
-
 // Returns the window that is full-screen and has the same title and owner pid
 // as the input window.
 CGWindowID FindFullScreenWindowWithSamePidAndTitle(CGWindowID id) {
-  int pid = GetWindowOwnerPid(id);
+  const int pid = GetWindowOwnerPid(id);
   std::string title = GetWindowTitle(id);
+  if (title.empty())
+    return kCGNullWindowID;
 
   // Only get on screen, non-desktop windows.
   CFArrayRef window_array = CGWindowListCopyWindowInfo(
@@ -92,29 +50,18 @@
   for (CFIndex i = 0; i < count; ++i) {
     CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
         CFArrayGetValueAtIndex(window_array, i));
-    CFStringRef window_title_ref = reinterpret_cast<CFStringRef>(
-        CFDictionaryGetValue(window, kCGWindowName));
-    CFNumberRef window_id_ref = reinterpret_cast<CFNumberRef>(
-        CFDictionaryGetValue(window, kCGWindowNumber));
-    CFNumberRef window_pid_ref = reinterpret_cast<CFNumberRef>(
-        CFDictionaryGetValue(window, kCGWindowOwnerPID));
 
-    if (!window_title_ref || !window_id_ref || !window_pid_ref)
+    CGWindowID window_id = GetWindowId(window);
+    if (window_id == kNullWindowId)
       continue;
 
-    int window_pid = 0;
-    CFNumberGetValue(window_pid_ref, kCFNumberIntType, &window_pid);
-    if (window_pid != pid)
+    if (GetWindowOwnerPid(window) != pid)
       continue;
 
-    std::string window_title;
-    if (!rtc::ToUtf8(window_title_ref, &window_title) ||
-        window_title != title) {
+    std::string window_title = GetWindowTitle(window);
+    if (window_title != title)
       continue;
-    }
 
-    CGWindowID window_id;
-    CFNumberGetValue(window_id_ref, kCFNumberIntType, &window_id);
     if (IsWindowFullScreen(desktop_config, window)) {
       full_screen_window = window_id;
       break;
diff --git a/modules/desktop_capture/mac/screen_capturer_mac.mm b/modules/desktop_capture/mac/screen_capturer_mac.mm
index 8f9a9d3..41d19cd 100644
--- a/modules/desktop_capture/mac/screen_capturer_mac.mm
+++ b/modules/desktop_capture/mac/screen_capturer_mac.mm
@@ -13,6 +13,7 @@
 #include "modules/desktop_capture/mac/screen_capturer_mac.h"
 
 #include "modules/desktop_capture/mac/desktop_frame_provider.h"
+#include "modules/desktop_capture/mac/window_list_utils.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/constructor_magic.h"
 #include "rtc_base/logging.h"
@@ -75,11 +76,7 @@
     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);
+    CGWindowID id = GetWindowId(window);
     if (id == window_to_exclude) {
       found = true;
       continue;
diff --git a/modules/desktop_capture/mac/window_list_utils.cc b/modules/desktop_capture/mac/window_list_utils.cc
index 6e1fc3f..5899530 100644
--- a/modules/desktop_capture/mac/window_list_utils.cc
+++ b/modules/desktop_capture/mac/window_list_utils.cc
@@ -15,6 +15,8 @@
 #include <algorithm>
 #include <cmath>
 #include <iterator>
+#include <list>
+#include <map>
 
 #include "rtc_base/checks.h"
 #include "rtc_base/mac_utils.h"
@@ -78,11 +80,8 @@
   if (!window_array)
     return false;
 
-  MacDesktopConfiguration desktop_config;
-  if (ignore_minimized) {
-    desktop_config = MacDesktopConfiguration::GetCurrent(
-        MacDesktopConfiguration::TopLeftOrigin);
-  }
+  MacDesktopConfiguration desktop_config = MacDesktopConfiguration::GetCurrent(
+      MacDesktopConfiguration::TopLeftOrigin);
 
   // Check windows to make sure they have an id, title, and use window layer
   // other than 0.
@@ -94,12 +93,6 @@
       continue;
     }
 
-    CFStringRef window_title = reinterpret_cast<CFStringRef>(
-        CFDictionaryGetValue(window, kCGWindowName));
-    if (!window_title) {
-      continue;
-    }
-
     CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
         CFDictionaryGetValue(window, kCGWindowNumber));
     if (!window_id) {
@@ -127,6 +120,15 @@
       continue;
     }
 
+    // If window title is empty, only consider it if it is either on screen or
+    // fullscreen.
+    CFStringRef window_title = reinterpret_cast<CFStringRef>(
+        CFDictionaryGetValue(window, kCGWindowName));
+    if (!window_title && !IsWindowOnScreen(window) &&
+        !IsWindowFullScreen(desktop_config, window)) {
+      continue;
+    }
+
     if (!on_window(window)) {
       break;
     }
@@ -138,16 +140,66 @@
 
 bool GetWindowList(DesktopCapturer::SourceList* windows,
                    bool ignore_minimized) {
-  return GetWindowList(
-      [windows](CFDictionaryRef window) {
-        WindowId id = GetWindowId(window);
-        std::string title = GetWindowTitle(window);
-        if (id != kNullWindowId && !title.empty()) {
-          windows->push_back(DesktopCapturer::Source{id, title});
+  // Use a std::list so that iterators are preversed upon insertion and
+  // deletion.
+  std::list<DesktopCapturer::Source> sources;
+  std::map<int, std::list<DesktopCapturer::Source>::const_iterator> pid_itr_map;
+  const bool ret = GetWindowList(
+      [&sources, &pid_itr_map](CFDictionaryRef window) {
+        WindowId window_id = GetWindowId(window);
+        if (window_id != kNullWindowId) {
+          const std::string title = GetWindowTitle(window);
+          const int pid = GetWindowOwnerPid(window);
+          // Check if window for the same pid have been already inserted.
+          std::map<int,
+                   std::list<DesktopCapturer::Source>::const_iterator>::iterator
+              itr = pid_itr_map.find(pid);
+
+          // Only consider empty titles if the app has no other window with a
+          // proper title.
+          if (title.empty()) {
+            std::string owner_name = GetWindowOwnerName(window);
+
+            // At this time we do not know if there will be other windows
+            // for the same pid unless they have been already inserted, hence
+            // the check in the map. Also skip the window if owner name is
+            // empty too.
+            if (!owner_name.empty() && (itr == pid_itr_map.end())) {
+              sources.push_back(DesktopCapturer::Source{window_id, owner_name});
+              RTC_DCHECK(!sources.empty());
+              // Get an iterator on the last valid element in the source list.
+              std::list<DesktopCapturer::Source>::const_iterator last_source =
+                  --sources.end();
+              pid_itr_map.insert(
+                  std::pair<int,
+                            std::list<DesktopCapturer::Source>::const_iterator>(
+                      pid, last_source));
+            }
+          } else {
+            sources.push_back(DesktopCapturer::Source{window_id, title});
+            // Once the window with empty title has been removed no other empty
+            // windows are allowed for the same pid.
+            if (itr != pid_itr_map.end() && (itr->second != sources.end())) {
+              sources.erase(itr->second);
+              // sdt::list::end() never changes during the lifetime of that
+              // list.
+              itr->second = sources.end();
+            }
+          }
         }
         return true;
       },
       ignore_minimized);
+
+  if (!ret)
+    return false;
+
+  RTC_DCHECK(windows);
+  windows->reserve(windows->size() + sources.size());
+  std::copy(std::begin(sources), std::end(sources),
+            std::back_inserter(*windows));
+
+  return true;
 }
 
 // Returns true if the window is occupying a full screen.
@@ -198,6 +250,37 @@
   if (title && rtc::ToUtf8(title, &result)) {
     return result;
   }
+
+  return std::string();
+}
+
+std::string GetWindowTitle(CGWindowID id) {
+  std::string title;
+  if (GetWindowRef(id, [&title](CFDictionaryRef window) {
+        title = GetWindowTitle(window);
+      })) {
+    return title;
+  }
+  return std::string();
+}
+
+std::string GetWindowOwnerName(CFDictionaryRef window) {
+  CFStringRef owner_name = reinterpret_cast<CFStringRef>(
+      CFDictionaryGetValue(window, kCGWindowOwnerName));
+  std::string result;
+  if (owner_name && rtc::ToUtf8(owner_name, &result)) {
+    return result;
+  }
+  return std::string();
+}
+
+std::string GetWindowOwnerName(CGWindowID id) {
+  std::string owner_name;
+  if (GetWindowRef(id, [&owner_name](CFDictionaryRef window) {
+        owner_name = GetWindowOwnerPid(window);
+      })) {
+    return owner_name;
+  }
   return std::string();
 }
 
@@ -219,6 +302,31 @@
   return id;
 }
 
+int GetWindowOwnerPid(CFDictionaryRef window) {
+  CFNumberRef window_pid = reinterpret_cast<CFNumberRef>(
+      CFDictionaryGetValue(window, kCGWindowOwnerPID));
+  if (!window_pid) {
+    return 0;
+  }
+
+  int pid;
+  if (!CFNumberGetValue(window_pid, kCFNumberIntType, &pid)) {
+    return 0;
+  }
+
+  return pid;
+}
+
+int GetWindowOwnerPid(CGWindowID id) {
+  int pid;
+  if (GetWindowRef(id, [&pid](CFDictionaryRef window) {
+        pid = GetWindowOwnerPid(window);
+      })) {
+    return pid;
+  }
+  return 0;
+}
+
 float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config,
                                DesktopVector position) {
   // Find the dpi to physical pixel scale for the screen where the mouse cursor
diff --git a/modules/desktop_capture/mac/window_list_utils.h b/modules/desktop_capture/mac/window_list_utils.h
index ea622c4..ff9ad14 100644
--- a/modules/desktop_capture/mac/window_list_utils.h
+++ b/modules/desktop_capture/mac/window_list_utils.h
@@ -48,10 +48,30 @@
 // valid title can be retrieved, this function returns an empty string.
 std::string GetWindowTitle(CFDictionaryRef window);
 
+// Returns utf-8 encoded title of window |id|. If |id| cannot be found or no
+// valid title can be retrieved, this function returns an empty string.
+std::string GetWindowTitle(CGWindowID id);
+
+// Returns utf-8 encoded owner name of |window|. If |window| is not a window or
+// if no valid owner name can be retrieved, returns an empty string.
+std::string GetWindowOwnerName(CFDictionaryRef window);
+
+// Returns utf-8 encoded owner name of the given window |id|. If |id| cannot be
+// found or if no valid owner name can be retrieved, returns an empty string.
+std::string GetWindowOwnerName(CGWindowID id);
+
 // Returns id of |window|. If |window| is not a window or the window id cannot
 // be retrieved, this function returns kNullWindowId.
 WindowId GetWindowId(CFDictionaryRef window);
 
+// Returns the pid of the process owning |window|. Return 0 if |window| is not
+// a window or no valid owner can be retrieved.
+int GetWindowOwnerPid(CFDictionaryRef window);
+
+// Returns the pid of the process owning the window |id|. Return 0 if |id|
+// cannot be found or no valid owner can be retrieved.
+int GetWindowOwnerPid(CGWindowID id);
+
 // 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