Home | History | Annotate | Download | only in base
      1 /*
      2  *  Copyright 2010 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 #include "webrtc/base/macwindowpicker.h"
     11 
     12 #include <ApplicationServices/ApplicationServices.h>
     13 #include <CoreFoundation/CoreFoundation.h>
     14 #include <dlfcn.h>
     15 
     16 #include "webrtc/base/logging.h"
     17 #include "webrtc/base/macutils.h"
     18 
     19 namespace rtc {
     20 
     21 static const char* kCoreGraphicsName =
     22     "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/"
     23     "CoreGraphics.framework/CoreGraphics";
     24 
     25 static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo";
     26 static const char* kWindowListCreateDescriptionFromArray =
     27     "CGWindowListCreateDescriptionFromArray";
     28 
     29 // Function pointer for holding the CGWindowListCopyWindowInfo function.
     30 typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption,
     31                                                     CGWindowID);
     32 
     33 // Function pointer for holding the CGWindowListCreateDescriptionFromArray
     34 // function.
     35 typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef);
     36 
     37 MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
     38                                      get_window_list_desc_(NULL) {
     39 }
     40 
     41 MacWindowPicker::~MacWindowPicker() {
     42   if (lib_handle_ != NULL) {
     43     dlclose(lib_handle_);
     44   }
     45 }
     46 
     47 bool MacWindowPicker::Init() {
     48   // TODO: If this class grows to use more dynamically functions
     49   // from the CoreGraphics framework, consider using
     50   // webrtc/base/latebindingsymboltable.h.
     51   lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW);
     52   if (lib_handle_ == NULL) {
     53     LOG(LS_ERROR) << "Could not load CoreGraphics";
     54     return false;
     55   }
     56 
     57   get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo);
     58   get_window_list_desc_ =
     59       dlsym(lib_handle_, kWindowListCreateDescriptionFromArray);
     60   if (get_window_list_ == NULL || get_window_list_desc_ == NULL) {
     61     // The CGWindowListCopyWindowInfo and the
     62     // CGWindowListCreateDescriptionFromArray functions was introduced
     63     // in Leopard(10.5) so this is a normal failure on Tiger.
     64     LOG(LS_INFO) << "Failed to load Core Graphics symbols";
     65     dlclose(lib_handle_);
     66     lib_handle_ = NULL;
     67     return false;
     68   }
     69 
     70   return true;
     71 }
     72 
     73 bool MacWindowPicker::IsVisible(const WindowId& id) {
     74   // Init if we're not already inited.
     75   if (get_window_list_desc_ == NULL && !Init()) {
     76     return false;
     77   }
     78   CGWindowID ids[1];
     79   ids[0] = id.id();
     80   CFArrayRef window_id_array =
     81       CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
     82 
     83   CFArrayRef window_array =
     84       reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
     85           get_window_list_desc_)(window_id_array);
     86   if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
     87     // Could not find the window. It might have been closed.
     88     LOG(LS_INFO) << "Window not found";
     89     CFRelease(window_id_array);
     90     return false;
     91   }
     92 
     93   CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
     94       CFArrayGetValueAtIndex(window_array, 0));
     95   CFBooleanRef is_visible = reinterpret_cast<CFBooleanRef>(
     96       CFDictionaryGetValue(window, kCGWindowIsOnscreen));
     97 
     98   // Check that the window is visible. If not we might crash.
     99   bool visible = false;
    100   if (is_visible != NULL) {
    101     visible = CFBooleanGetValue(is_visible);
    102   }
    103   CFRelease(window_id_array);
    104   CFRelease(window_array);
    105   return visible;
    106 }
    107 
    108 bool MacWindowPicker::MoveToFront(const WindowId& id) {
    109   // Init if we're not already initialized.
    110   if (get_window_list_desc_ == NULL && !Init()) {
    111     return false;
    112   }
    113   CGWindowID ids[1];
    114   ids[0] = id.id();
    115   CFArrayRef window_id_array =
    116       CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
    117 
    118   CFArrayRef window_array =
    119       reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
    120           get_window_list_desc_)(window_id_array);
    121   if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
    122     // Could not find the window. It might have been closed.
    123     LOG(LS_INFO) << "Window not found";
    124     CFRelease(window_id_array);
    125     return false;
    126   }
    127 
    128   CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
    129       CFArrayGetValueAtIndex(window_array, 0));
    130   CFStringRef window_name_ref = reinterpret_cast<CFStringRef>(
    131       CFDictionaryGetValue(window, kCGWindowName));
    132   CFNumberRef application_pid = reinterpret_cast<CFNumberRef>(
    133       CFDictionaryGetValue(window, kCGWindowOwnerPID));
    134 
    135   int pid_val;
    136   CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val);
    137   std::string window_name;
    138   ToUtf8(window_name_ref, &window_name);
    139 
    140   // Build an applescript that sets the selected window to front
    141   // within the application. Then set the application to front.
    142   bool result = true;
    143   std::stringstream ss;
    144   ss << "tell application \"System Events\"\n"
    145      << "set proc to the first item of (every process whose unix id is "
    146      << pid_val
    147      << ")\n"
    148      << "tell proc to perform action \"AXRaise\" of window \""
    149      << window_name
    150      << "\"\n"
    151      << "set the frontmost of proc to true\n"
    152      << "end tell";
    153   if (!RunAppleScript(ss.str())) {
    154     // This might happen to for example X applications where the X
    155     // server spawns of processes with their own PID but the X server
    156     // is still registered as owner to the application windows. As a
    157     // workaround, we put the X server process to front, meaning that
    158     // all X applications will show up. The drawback with this
    159     // workaround is that the application that we really wanted to set
    160     // to front might be behind another X application.
    161     ProcessSerialNumber psn;
    162     pid_t pid = pid_val;
    163     int res = GetProcessForPID(pid, &psn);
    164     if (res != 0) {
    165       LOG(LS_ERROR) << "Failed getting process for pid";
    166       result = false;
    167     }
    168     res = SetFrontProcess(&psn);
    169     if (res != 0) {
    170       LOG(LS_ERROR) << "Failed setting process to front";
    171       result = false;
    172     }
    173   }
    174   CFRelease(window_id_array);
    175   CFRelease(window_array);
    176   return result;
    177 }
    178 
    179 bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
    180   const uint32_t kMaxDisplays = 128;
    181   CGDirectDisplayID active_displays[kMaxDisplays];
    182   uint32_t display_count = 0;
    183 
    184   CGError err = CGGetActiveDisplayList(kMaxDisplays,
    185                                        active_displays,
    186                                        &display_count);
    187   if (err != kCGErrorSuccess) {
    188     LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays.";
    189     return false;
    190   }
    191   for (uint32_t i = 0; i < display_count; ++i) {
    192     DesktopId id(active_displays[i], static_cast<int>(i));
    193     // TODO: Figure out an appropriate desktop title.
    194     DesktopDescription desc(id, "");
    195     desc.set_primary(CGDisplayIsMain(id.id()));
    196     descriptions->push_back(desc);
    197   }
    198   return display_count > 0;
    199 }
    200 
    201 bool MacWindowPicker::GetDesktopDimensions(const DesktopId& id,
    202                                            int* width,
    203                                            int* height) {
    204   *width = CGDisplayPixelsWide(id.id());
    205   *height = CGDisplayPixelsHigh(id.id());
    206   return true;
    207 }
    208 
    209 bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
    210   // Init if we're not already inited.
    211   if (get_window_list_ == NULL && !Init()) {
    212     return false;
    213   }
    214 
    215   // Only get onscreen, non-desktop windows.
    216   CFArrayRef window_array =
    217       reinterpret_cast<CGWindowListCopyWindowInfoProc>(get_window_list_)(
    218           kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
    219           kCGNullWindowID);
    220   if (window_array == NULL) {
    221     return false;
    222   }
    223 
    224   // Check windows to make sure they have an id, title, and use window layer 0.
    225   CFIndex i;
    226   CFIndex count = CFArrayGetCount(window_array);
    227   for (i = 0; i < count; ++i) {
    228     CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
    229         CFArrayGetValueAtIndex(window_array, i));
    230     CFStringRef window_title = reinterpret_cast<CFStringRef>(
    231         CFDictionaryGetValue(window, kCGWindowName));
    232     CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
    233         CFDictionaryGetValue(window, kCGWindowNumber));
    234     CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
    235         CFDictionaryGetValue(window, kCGWindowLayer));
    236     if (window_title != NULL && window_id != NULL && window_layer != NULL) {
    237       std::string title_str;
    238       int id_val, layer_val;
    239       ToUtf8(window_title, &title_str);
    240       CFNumberGetValue(window_id, kCFNumberIntType, &id_val);
    241       CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val);
    242 
    243       // Discard windows without a title.
    244       if (layer_val == 0 && title_str.length() > 0) {
    245         WindowId id(static_cast<CGWindowID>(id_val));
    246         WindowDescription desc(id, title_str);
    247         descriptions->push_back(desc);
    248       }
    249     }
    250   }
    251 
    252   CFRelease(window_array);
    253   return true;
    254 }
    255 
    256 }  // namespace rtc
    257