/* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/desktop_capture/mac/window_list_utils.h" #include #include "rtc_base/checks.h" #include "rtc_base/macutils.h" static_assert( static_cast(kCGNullWindowID) == webrtc::kNullWindowId, "kNullWindowId needs to equal to kCGNullWindowID."); namespace webrtc { namespace { // Get CFDictionaryRef from |id| and call |on_window| against it. This function // returns false if native APIs fail, typically it indicates that the |id| does // not represent a window. |on_window| will not be called if false is returned // from this function. bool GetWindowRef(CGWindowID id, rtc::FunctionView on_window) { RTC_DCHECK(on_window); // TODO(zijiehe): |id| is a 32-bit integer, casting it to an array seems not // safe enough. Maybe we should create a new // const void* arr[] = { // reinterpret_cast(id) } // }; CFArrayRef window_id_array = CFArrayCreate(NULL, reinterpret_cast(&id), 1, NULL); CFArrayRef window_array = CGWindowListCreateDescriptionFromArray(window_id_array); bool result = false; // TODO(zijiehe): CFArrayGetCount(window_array) should always return 1. // Otherwise, we should treat it as failure. if (window_array && CFArrayGetCount(window_array)) { on_window(reinterpret_cast( CFArrayGetValueAtIndex(window_array, 0))); result = true; } if (window_array) { CFRelease(window_array); } CFRelease(window_id_array); return result; } } // namespace bool GetWindowList(rtc::FunctionView on_window, bool ignore_minimized) { RTC_DCHECK(on_window); // Only get on screen, non-desktop windows. // According to // https://developer.apple.com/documentation/coregraphics/cgwindowlistoption/1454105-optiononscreenonly , // when kCGWindowListOptionOnScreenOnly is used, the order of windows are in // decreasing z-order. CFArrayRef window_array = CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); if (!window_array) return false; MacDesktopConfiguration desktop_config; if (ignore_minimized) { desktop_config = MacDesktopConfiguration::GetCurrent( MacDesktopConfiguration::TopLeftOrigin); } // Check windows to make sure they have an id, title, and use window layer // other than 0. CFIndex count = CFArrayGetCount(window_array); for (CFIndex i = 0; i < count; i++) { CFDictionaryRef window = reinterpret_cast( CFArrayGetValueAtIndex(window_array, i)); if (!window) { continue; } CFStringRef window_title = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowName)); if (!window_title) { continue; } // TODO(webrtc:8460): On 10.12, the name of the dock window is not "Dock" // anymore. The following check should be removed soon or later. if (CFStringCompare(window_title, CFSTR("Dock"), 0) == 0) { continue; } CFNumberRef window_id = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowNumber)); if (!window_id) { continue; } CFNumberRef window_layer = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowLayer)); if (!window_layer) { continue; } // Skip windows with layer=0 (menu, dock). // TODO(zijiehe): The windows with layer != 0 are skipped, is this a bug in // code (not likely) or a bug in comments? What's the meaning of window // layer number in the first place. int layer; if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)) { continue; } if (layer != 0) { continue; } // Skip windows that are minimized and not full screen. if (ignore_minimized && !IsWindowOnScreen(window) && !IsWindowFullScreen(desktop_config, window)) { continue; } if (!on_window(window)) { break; } } CFRelease(window_array); return true; } bool GetWindowList(DesktopCapturer::SourceList* windows, bool ignore_minimized) { return GetWindowList( [windows](CFDictionaryRef window) { WindowId id = GetWindowId(window); std::string title = GetWindowTitle(window); if (id != kNullWindowId && !title.empty()) { windows->push_back(DesktopCapturer::Source{ id, title }); } return true; }, ignore_minimized); } // Returns true if the window is occupying a full screen. bool IsWindowFullScreen( const MacDesktopConfiguration& desktop_config, CFDictionaryRef window) { bool fullscreen = false; CFDictionaryRef bounds_ref = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowBounds)); CGRect bounds; if (bounds_ref && CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) { for (MacDisplayConfigurations::const_iterator it = desktop_config.displays.begin(); it != desktop_config.displays.end(); it++) { if (it->bounds.equals(DesktopRect::MakeXYWH(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height))) { fullscreen = true; break; } } } return fullscreen; } bool IsWindowOnScreen(CFDictionaryRef window) { CFBooleanRef on_screen = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowIsOnscreen)); return on_screen != NULL && CFBooleanGetValue(on_screen); } bool IsWindowOnScreen(CGWindowID id) { bool on_screen; if (GetWindowRef(id, [&on_screen](CFDictionaryRef window) { on_screen = IsWindowOnScreen(window); })) { return on_screen; } return false; } std::string GetWindowTitle(CFDictionaryRef window) { CFStringRef title = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowName)); std::string result; if (title && rtc::ToUtf8(title, &result)) { return result; } return std::string(); } WindowId GetWindowId(CFDictionaryRef window) { CFNumberRef window_id = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowNumber)); if (!window_id) { return kNullWindowId; } // Note: WindowId is 64-bit on 64-bit system, but CGWindowID is always 32-bit. // CFNumberGetValue() fills only top 32 bits, so we should use CGWindowID to // receive the window id. CGWindowID id; if (!CFNumberGetValue(window_id, kCFNumberIntType, &id)) { return kNullWindowId; } return id; } DesktopRect GetWindowBounds(CFDictionaryRef window) { CFDictionaryRef window_bounds = reinterpret_cast( CFDictionaryGetValue(window, kCGWindowBounds)); if (!window_bounds) { return DesktopRect(); } CGRect gc_window_rect; if (!CGRectMakeWithDictionaryRepresentation(window_bounds, &gc_window_rect)) { return DesktopRect(); } return DesktopRect::MakeXYWH(gc_window_rect.origin.x, gc_window_rect.origin.y, gc_window_rect.size.width, gc_window_rect.size.height); } DesktopRect GetWindowBounds(CGWindowID id) { DesktopRect result; if (GetWindowRef(id, [&result](CFDictionaryRef window) { result = GetWindowBounds(window); })) { return result; } return DesktopRect(); } } // namespace webrtc