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/interface/logging.h"
21
22
23namespace webrtc {
24
25namespace {
26
27const int64_t kUpdateIntervalMs = 500;
28
29// Returns true if the window is minimized.
30bool 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.
53bool 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
79std::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
101int 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.
125CGWindowID 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
180bool 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
194FullScreenChromeWindowDetector::FullScreenChromeWindowDetector()
195    : ref_count_(0) {}
196
197FullScreenChromeWindowDetector::~FullScreenChromeWindowDetector() {}
198
199CGWindowID 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
225void 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(&current_window_list_);
240    last_udpate_time_ = TickTime::Now();
241  }
242}
243
244}  // namespace webrtc
245