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
19namespace rtc {
20
21static const char* kCoreGraphicsName =
22    "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/"
23    "CoreGraphics.framework/CoreGraphics";
24
25static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo";
26static const char* kWindowListCreateDescriptionFromArray =
27    "CGWindowListCreateDescriptionFromArray";
28
29// Function pointer for holding the CGWindowListCopyWindowInfo function.
30typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption,
31                                                    CGWindowID);
32
33// Function pointer for holding the CGWindowListCreateDescriptionFromArray
34// function.
35typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef);
36
37MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
38                                     get_window_list_desc_(NULL) {
39}
40
41MacWindowPicker::~MacWindowPicker() {
42  if (lib_handle_ != NULL) {
43    dlclose(lib_handle_);
44  }
45}
46
47bool 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
73bool 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
108bool 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
179bool 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
201bool 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
209bool 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