blob: 6e1fc3f933ad20cc8524f8339b0bcb174fcb3b46 [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 "modules/desktop_capture/mac/window_list_utils.h"
#include <ApplicationServices/ApplicationServices.h>
#include <algorithm>
#include <cmath>
#include <iterator>
#include "rtc_base/checks.h"
#include "rtc_base/mac_utils.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).
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;
}
// 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;
}
float GetScaleFactorAtPosition(const MacDesktopConfiguration& desktop_config,
DesktopVector position) {
// Find the dpi to physical pixel scale for the screen where the mouse cursor
// is.
for (auto it = desktop_config.displays.begin();
it != desktop_config.displays.end(); ++it) {
if (it->bounds.Contains(position)) {
return it->dip_to_pixel_scale;
}
}
return 1;
}
float GetWindowScaleFactor(CGWindowID id, DesktopSize size) {
DesktopRect window_bounds = GetWindowBounds(id);
float scale = 1.0f;
if (!window_bounds.is_empty() && !size.is_empty()) {
float scale_x = size.width() / window_bounds.width();
float scale_y = size.height() / window_bounds.height();
// Currently the scale in X and Y directions must be same.
if ((std::fabs(scale_x - scale_y) <=
std::numeric_limits<float>::epsilon() * std::max(scale_x, scale_y)) &&
scale_x > 0.0f) {
scale = scale_x;
}
}
return scale;
}
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