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
13namespace talk_base {
14
15static const char* kCoreGraphicsName =
16    "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/"
17    "CoreGraphics.framework/CoreGraphics";
18
19static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo";
20static const char* kWindowListCreateDescriptionFromArray =
21    "CGWindowListCreateDescriptionFromArray";
22
23// Function pointer for holding the CGWindowListCopyWindowInfo function.
24typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption,
25                                                    CGWindowID);
26
27// Function pointer for holding the CGWindowListCreateDescriptionFromArray
28// function.
29typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef);
30
31MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
32                                     get_window_list_desc_(NULL) {
33}
34
35MacWindowPicker::~MacWindowPicker() {
36  if (lib_handle_ != NULL) {
37    dlclose(lib_handle_);
38  }
39}
40
41bool 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
67bool 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
102bool 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
173bool 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
195bool 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
203bool 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