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