// Copyright 2010 Google Inc. All Rights Reserved #include "talk/base/macwindowpicker.h" #include #include #include #include "talk/base/logging.h" #include "talk/base/macutils.h" namespace talk_base { static const char* kCoreGraphicsName = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" "CoreGraphics.framework/CoreGraphics"; static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo"; static const char* kWindowListCreateDescriptionFromArray = "CGWindowListCreateDescriptionFromArray"; // Function pointer for holding the CGWindowListCopyWindowInfo function. typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption, CGWindowID); // Function pointer for holding the CGWindowListCreateDescriptionFromArray // function. typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef); MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL), get_window_list_desc_(NULL) { } MacWindowPicker::~MacWindowPicker() { if (lib_handle_ != NULL) { dlclose(lib_handle_); } } bool MacWindowPicker::Init() { // TODO: If this class grows to use more dynamically functions // from the CoreGraphics framework, consider using // talk/base/latebindingsymboltable.h. lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW); if (lib_handle_ == NULL) { LOG(LS_ERROR) << "Could not load CoreGraphics"; return false; } get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo); get_window_list_desc_ = dlsym(lib_handle_, kWindowListCreateDescriptionFromArray); if (get_window_list_ == NULL || get_window_list_desc_ == NULL) { // The CGWindowListCopyWindowInfo and the // CGWindowListCreateDescriptionFromArray functions was introduced // in Leopard(10.5) so this is a normal failure on Tiger. LOG(LS_INFO) << "Failed to load Core Graphics symbols"; dlclose(lib_handle_); lib_handle_ = NULL; return false; } return true; } bool MacWindowPicker::IsVisible(const WindowId& id) { // Init if we're not already inited. if (get_window_list_desc_ == NULL && !Init()) { return false; } CGWindowID ids[1]; ids[0] = id.id(); CFArrayRef window_id_array = CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); CFArrayRef window_array = reinterpret_cast( get_window_list_desc_)(window_id_array); if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { // Could not find the window. It might have been closed. LOG(LS_INFO) << "Window not found"; CFRelease(window_id_array); return false; } CFDictionaryRef window = reinterpret_cast( CFArrayGetValueAtIndex(window_array, 0)); CFBooleanRef is_visible = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowIsOnscreen)); // Check that the window is visible. If not we might crash. bool visible = false; if (is_visible != NULL) { visible = CFBooleanGetValue(is_visible); } CFRelease(window_id_array); CFRelease(window_array); return visible; } bool MacWindowPicker::MoveToFront(const WindowId& id) { // Init if we're not already initialized. if (get_window_list_desc_ == NULL && !Init()) { return false; } CGWindowID ids[1]; ids[0] = id.id(); CFArrayRef window_id_array = CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); CFArrayRef window_array = reinterpret_cast( get_window_list_desc_)(window_id_array); if (window_array == NULL || 0 == CFArrayGetCount(window_array)) { // Could not find the window. It might have been closed. LOG(LS_INFO) << "Window not found"; CFRelease(window_id_array); return false; } CFDictionaryRef window = reinterpret_cast( CFArrayGetValueAtIndex(window_array, 0)); CFStringRef window_name_ref = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowName)); CFNumberRef application_pid = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowOwnerPID)); int pid_val; CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val); std::string window_name; ToUtf8(window_name_ref, &window_name); // Build an applescript that sets the selected window to front // within the application. Then set the application to front. bool result = true; std::stringstream ss; ss << "tell application \"System Events\"\n" << "set proc to the first item of (every process whose unix id is " << pid_val << ")\n" << "tell proc to perform action \"AXRaise\" of window \"" << window_name << "\"\n" << "set the frontmost of proc to true\n" << "end tell"; if (!RunAppleScript(ss.str())) { // This might happen to for example X applications where the X // server spawns of processes with their own PID but the X server // is still registered as owner to the application windows. As a // workaround, we put the X server process to front, meaning that // all X applications will show up. The drawback with this // workaround is that the application that we really wanted to set // to front might be behind another X application. ProcessSerialNumber psn; pid_t pid = pid_val; int res = GetProcessForPID(pid, &psn); if (res != 0) { LOG(LS_ERROR) << "Failed getting process for pid"; result = false; } res = SetFrontProcess(&psn); if (res != 0) { LOG(LS_ERROR) << "Failed setting process to front"; result = false; } } CFRelease(window_id_array); CFRelease(window_array); return result; } bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) { const uint32_t kMaxDisplays = 128; CGDirectDisplayID active_displays[kMaxDisplays]; uint32_t display_count = 0; CGError err = CGGetActiveDisplayList(kMaxDisplays, active_displays, &display_count); if (err != kCGErrorSuccess) { LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays."; return false; } for (uint32_t i = 0; i < display_count; ++i) { DesktopId id(active_displays[i], static_cast(i)); // TODO: Figure out an appropriate desktop title. DesktopDescription desc(id, ""); desc.set_primary(CGDisplayIsMain(id.id())); descriptions->push_back(desc); } return display_count > 0; } bool MacWindowPicker::GetDesktopDimensions(const DesktopId& id, int* width, int* height) { *width = CGDisplayPixelsWide(id.id()); *height = CGDisplayPixelsHigh(id.id()); return true; } bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) { // Init if we're not already inited. if (get_window_list_ == NULL && !Init()) { return false; } // Only get onscreen, non-desktop windows. CFArrayRef window_array = reinterpret_cast(get_window_list_)( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); if (window_array == NULL) { return false; } // Check windows to make sure they have an id, title, and use window layer 0. CFIndex i; CFIndex count = CFArrayGetCount(window_array); for (i = 0; i < count; ++i) { CFDictionaryRef window = reinterpret_cast( CFArrayGetValueAtIndex(window_array, i)); CFStringRef window_title = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowName)); CFNumberRef window_id = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowNumber)); CFNumberRef window_layer = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowLayer)); if (window_title != NULL && window_id != NULL && window_layer != NULL) { std::string title_str; int id_val, layer_val; ToUtf8(window_title, &title_str); CFNumberGetValue(window_id, kCFNumberIntType, &id_val); CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val); // Discard windows without a title. if (layer_val == 0 && title_str.length() > 0) { WindowId id(static_cast(id_val)); WindowDescription desc(id, title_str); descriptions->push_back(desc); } } } CFRelease(window_array); return true; } } // namespace talk_base