Home | History | Annotate | Download | only in mac
      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(&current_window_list_);
    240     last_udpate_time_ = TickTime::Now();
    241   }
    242 }
    243 
    244 }  // namespace webrtc
    245