blob: 0bd6d5adceeae0be4762bcd32e2137f406f90df9 [file] [log] [blame]
/*
* 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 "webrtc/modules/desktop_capture/mac/window_list_utils.h"
#include <ApplicationServices/ApplicationServices.h>
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/macutils.h"
static_assert(
static_cast<webrtc::WindowId>(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<void(CFDictionaryRef)> 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<void*>(id) }
// };
CFArrayRef window_id_array =
CFArrayCreate(NULL, reinterpret_cast<const void**>(&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<CFDictionaryRef>(
CFArrayGetValueAtIndex(window_array, 0)));
result = true;
}
if (window_array) {
CFRelease(window_array);
}
CFRelease(window_id_array);
return result;
}
} // namespace
bool GetWindowList(rtc::FunctionView<bool(CFDictionaryRef)> 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<CFDictionaryRef>(
CFArrayGetValueAtIndex(window_array, i));
if (!window) {
continue;
}
CFStringRef window_title = reinterpret_cast<CFStringRef>(
CFDictionaryGetValue(window, kCGWindowName));
if (!window_title) {
continue;
}
CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
CFDictionaryGetValue(window, kCGWindowNumber));
if (!window_id) {
continue;
}
CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
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<CFDictionaryRef>(
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<CFBooleanRef>(
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<CFStringRef>(
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<CFNumberRef>(
CFDictionaryGetValue(window, kCGWindowNumber));
if (!window_id) {
return kNullWindowId;
}
WindowId id;
if (!CFNumberGetValue(window_id, kCFNumberIntType, &id)) {
return kNullWindowId;
}
return id;
}
DesktopRect GetWindowBounds(CFDictionaryRef window) {
CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>(
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