• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MacWindowPicker()37 MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
38                                      get_window_list_desc_(NULL) {
39 }
40 
~MacWindowPicker()41 MacWindowPicker::~MacWindowPicker() {
42   if (lib_handle_ != NULL) {
43     dlclose(lib_handle_);
44   }
45 }
46 
Init()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 
IsVisible(const WindowId & id)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 
MoveToFront(const WindowId & id)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 
GetDesktopList(DesktopDescriptionList * descriptions)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 
GetDesktopDimensions(const DesktopId & id,int * width,int * height)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 
GetWindowList(WindowDescriptionList * descriptions)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