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