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