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 "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" 12 13 #include <assert.h> 14 #include <libproc.h> 15 #include <string> 16 17 #include "webrtc/base/macutils.h" 18 #include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" 19 #include "webrtc/modules/desktop_capture/mac/window_list_utils.h" 20 #include "webrtc/system_wrappers/include/logging.h" 21 22 23 namespace webrtc { 24 25 namespace { 26 27 const int64_t kUpdateIntervalMs = 500; 28 29 // Returns true if the window is minimized. 30 bool IsWindowMinimized(CGWindowID id) { 31 CFArrayRef window_id_array = 32 CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); 33 CFArrayRef window_array = 34 CGWindowListCreateDescriptionFromArray(window_id_array); 35 bool minimized = false; 36 37 if (window_array && CFArrayGetCount(window_array)) { 38 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 39 CFArrayGetValueAtIndex(window_array, 0)); 40 CFBooleanRef on_screen = reinterpret_cast<CFBooleanRef>( 41 CFDictionaryGetValue(window, kCGWindowIsOnscreen)); 42 43 minimized = !on_screen; 44 } 45 46 CFRelease(window_id_array); 47 CFRelease(window_array); 48 49 return minimized; 50 } 51 52 // Returns true if the window is occupying a full screen. 53 bool IsWindowFullScreen(const MacDesktopConfiguration& desktop_config, 54 CFDictionaryRef window) { 55 bool fullscreen = false; 56 57 CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>( 58 CFDictionaryGetValue(window, kCGWindowBounds)); 59 60 CGRect bounds; 61 if (bounds_ref && 62 CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) { 63 for (MacDisplayConfigurations::const_iterator it = 64 desktop_config.displays.begin(); 65 it != desktop_config.displays.end(); ++it) { 66 if (it->bounds.equals(DesktopRect::MakeXYWH(bounds.origin.x, 67 bounds.origin.y, 68 bounds.size.width, 69 bounds.size.height))) { 70 fullscreen = true; 71 break; 72 } 73 } 74 } 75 76 return fullscreen; 77 } 78 79 std::string GetWindowTitle(CGWindowID id) { 80 CFArrayRef window_id_array = 81 CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); 82 CFArrayRef window_array = 83 CGWindowListCreateDescriptionFromArray(window_id_array); 84 std::string title; 85 86 if (window_array && CFArrayGetCount(window_array)) { 87 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 88 CFArrayGetValueAtIndex(window_array, 0)); 89 CFStringRef title_ref = reinterpret_cast<CFStringRef>( 90 CFDictionaryGetValue(window, kCGWindowName)); 91 92 if (title_ref) 93 rtc::ToUtf8(title_ref, &title); 94 } 95 CFRelease(window_id_array); 96 CFRelease(window_array); 97 98 return title; 99 } 100 101 int GetWindowOwnerPid(CGWindowID id) { 102 CFArrayRef window_id_array = 103 CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL); 104 CFArrayRef window_array = 105 CGWindowListCreateDescriptionFromArray(window_id_array); 106 int pid = 0; 107 108 if (window_array && CFArrayGetCount(window_array)) { 109 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 110 CFArrayGetValueAtIndex(window_array, 0)); 111 CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>( 112 CFDictionaryGetValue(window, kCGWindowOwnerPID)); 113 114 if (pid_ref) 115 CFNumberGetValue(pid_ref, kCFNumberIntType, &pid); 116 } 117 CFRelease(window_id_array); 118 CFRelease(window_array); 119 120 return pid; 121 } 122 123 // Returns the window that is full-screen and has the same title and owner pid 124 // as the input window. 125 CGWindowID FindFullScreenWindowWithSamePidAndTitle(CGWindowID id) { 126 int pid = GetWindowOwnerPid(id); 127 std::string title = GetWindowTitle(id); 128 129 // Only get on screen, non-desktop windows. 130 CFArrayRef window_array = CGWindowListCopyWindowInfo( 131 kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, 132 kCGNullWindowID); 133 if (!window_array) 134 return kCGNullWindowID; 135 136 CGWindowID full_screen_window = kCGNullWindowID; 137 138 MacDesktopConfiguration desktop_config = MacDesktopConfiguration::GetCurrent( 139 MacDesktopConfiguration::TopLeftOrigin); 140 141 // Check windows to make sure they have an id, title, and use window layer 142 // other than 0. 143 CFIndex count = CFArrayGetCount(window_array); 144 for (CFIndex i = 0; i < count; ++i) { 145 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 146 CFArrayGetValueAtIndex(window_array, i)); 147 CFStringRef window_title_ref = reinterpret_cast<CFStringRef>( 148 CFDictionaryGetValue(window, kCGWindowName)); 149 CFNumberRef window_id_ref = reinterpret_cast<CFNumberRef>( 150 CFDictionaryGetValue(window, kCGWindowNumber)); 151 CFNumberRef window_pid_ref = reinterpret_cast<CFNumberRef>( 152 CFDictionaryGetValue(window, kCGWindowOwnerPID)); 153 154 if (!window_title_ref || !window_id_ref || !window_pid_ref) 155 continue; 156 157 int window_pid = 0; 158 CFNumberGetValue(window_pid_ref, kCFNumberIntType, &window_pid); 159 if (window_pid != pid) 160 continue; 161 162 std::string window_title; 163 if (!rtc::ToUtf8(window_title_ref, &window_title) || 164 window_title != title) { 165 continue; 166 } 167 168 CGWindowID window_id; 169 CFNumberGetValue(window_id_ref, kCFNumberIntType, &window_id); 170 if (IsWindowFullScreen(desktop_config, window)) { 171 full_screen_window = window_id; 172 break; 173 } 174 } 175 176 CFRelease(window_array); 177 return full_screen_window; 178 } 179 180 bool IsChromeWindow(CGWindowID id) { 181 int pid = GetWindowOwnerPid(id); 182 char buffer[PROC_PIDPATHINFO_MAXSIZE]; 183 int path_length = proc_pidpath(pid, buffer, sizeof(buffer)); 184 if (path_length <= 0) 185 return false; 186 187 const char* last_slash = strrchr(buffer, '/'); 188 std::string name(last_slash ? last_slash + 1 : buffer); 189 return name.find("Google Chrome") == 0 || name == "Chromium"; 190 } 191 192 } // namespace 193 194 FullScreenChromeWindowDetector::FullScreenChromeWindowDetector() 195 : ref_count_(0) {} 196 197 FullScreenChromeWindowDetector::~FullScreenChromeWindowDetector() {} 198 199 CGWindowID FullScreenChromeWindowDetector::FindFullScreenWindow( 200 CGWindowID original_window) { 201 if (!IsChromeWindow(original_window) || !IsWindowMinimized(original_window)) 202 return kCGNullWindowID; 203 204 CGWindowID full_screen_window_id = 205 FindFullScreenWindowWithSamePidAndTitle(original_window); 206 207 if (full_screen_window_id == kCGNullWindowID) 208 return kCGNullWindowID; 209 210 for (WindowCapturer::WindowList::iterator it = previous_window_list_.begin(); 211 it != previous_window_list_.end(); ++it) { 212 if (static_cast<CGWindowID>(it->id) != full_screen_window_id) 213 continue; 214 215 int64_t time_interval = 216 (TickTime::Now() - last_udpate_time_).Milliseconds(); 217 LOG(LS_WARNING) << "The full-screen window exists in the list, " 218 << "which was updated " << time_interval << "ms ago."; 219 return kCGNullWindowID; 220 } 221 222 return full_screen_window_id; 223 } 224 225 void FullScreenChromeWindowDetector::UpdateWindowListIfNeeded( 226 CGWindowID original_window) { 227 if (IsChromeWindow(original_window) && 228 (TickTime::Now() - last_udpate_time_).Milliseconds() 229 > kUpdateIntervalMs) { 230 previous_window_list_.clear(); 231 previous_window_list_.swap(current_window_list_); 232 233 // No need to update the window list when the window is minimized. 234 if (IsWindowMinimized(original_window)) { 235 previous_window_list_.clear(); 236 return; 237 } 238 239 GetWindowList(¤t_window_list_); 240 last_udpate_time_ = TickTime::Now(); 241 } 242 } 243 244 } // namespace webrtc 245