All application with empty title were not listed, for example Photos. Fallback to owner name in that case while making sure to keep ignoring the ghost window. Most of the ghost windows can be filtered with IsWindowOnScreen or IsWindowFullScreen except a few. For the remaining ghost we check if there is no other window with the same pid. Bug: chromium:516230 Test: Hangouts or Rumpus Change-Id: Ibb9f98887e5aedf822fc0611836b1938b5056d43 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/130360 Commit-Queue: Julien Isorce <julien.isorce@chromium.org> Reviewed-by: Brave Yao <braveyao@webrtc.org> Cr-Commit-Position: refs/heads/master@{#27401}
389 lines
12 KiB
C++
389 lines
12 KiB
C++
/*
|
|
* 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 <list>
|
|
#include <map>
|
|
|
|
#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 = 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;
|
|
}
|
|
|
|
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 window title is empty, only consider it if it is either on screen or
|
|
// fullscreen.
|
|
CFStringRef window_title = reinterpret_cast<CFStringRef>(
|
|
CFDictionaryGetValue(window, kCGWindowName));
|
|
if (!window_title && !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) {
|
|
// Use a std::list so that iterators are preversed upon insertion and
|
|
// deletion.
|
|
std::list<DesktopCapturer::Source> sources;
|
|
std::map<int, std::list<DesktopCapturer::Source>::const_iterator> pid_itr_map;
|
|
const bool ret = GetWindowList(
|
|
[&sources, &pid_itr_map](CFDictionaryRef window) {
|
|
WindowId window_id = GetWindowId(window);
|
|
if (window_id != kNullWindowId) {
|
|
const std::string title = GetWindowTitle(window);
|
|
const int pid = GetWindowOwnerPid(window);
|
|
// Check if window for the same pid have been already inserted.
|
|
std::map<int,
|
|
std::list<DesktopCapturer::Source>::const_iterator>::iterator
|
|
itr = pid_itr_map.find(pid);
|
|
|
|
// Only consider empty titles if the app has no other window with a
|
|
// proper title.
|
|
if (title.empty()) {
|
|
std::string owner_name = GetWindowOwnerName(window);
|
|
|
|
// At this time we do not know if there will be other windows
|
|
// for the same pid unless they have been already inserted, hence
|
|
// the check in the map. Also skip the window if owner name is
|
|
// empty too.
|
|
if (!owner_name.empty() && (itr == pid_itr_map.end())) {
|
|
sources.push_back(DesktopCapturer::Source{window_id, owner_name});
|
|
RTC_DCHECK(!sources.empty());
|
|
// Get an iterator on the last valid element in the source list.
|
|
std::list<DesktopCapturer::Source>::const_iterator last_source =
|
|
--sources.end();
|
|
pid_itr_map.insert(
|
|
std::pair<int,
|
|
std::list<DesktopCapturer::Source>::const_iterator>(
|
|
pid, last_source));
|
|
}
|
|
} else {
|
|
sources.push_back(DesktopCapturer::Source{window_id, title});
|
|
// Once the window with empty title has been removed no other empty
|
|
// windows are allowed for the same pid.
|
|
if (itr != pid_itr_map.end() && (itr->second != sources.end())) {
|
|
sources.erase(itr->second);
|
|
// sdt::list::end() never changes during the lifetime of that
|
|
// list.
|
|
itr->second = sources.end();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
ignore_minimized);
|
|
|
|
if (!ret)
|
|
return false;
|
|
|
|
RTC_DCHECK(windows);
|
|
windows->reserve(windows->size() + sources.size());
|
|
std::copy(std::begin(sources), std::end(sources),
|
|
std::back_inserter(*windows));
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
std::string GetWindowTitle(CGWindowID id) {
|
|
std::string title;
|
|
if (GetWindowRef(id, [&title](CFDictionaryRef window) {
|
|
title = GetWindowTitle(window);
|
|
})) {
|
|
return title;
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
std::string GetWindowOwnerName(CFDictionaryRef window) {
|
|
CFStringRef owner_name = reinterpret_cast<CFStringRef>(
|
|
CFDictionaryGetValue(window, kCGWindowOwnerName));
|
|
std::string result;
|
|
if (owner_name && rtc::ToUtf8(owner_name, &result)) {
|
|
return result;
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
std::string GetWindowOwnerName(CGWindowID id) {
|
|
std::string owner_name;
|
|
if (GetWindowRef(id, [&owner_name](CFDictionaryRef window) {
|
|
owner_name = GetWindowOwnerPid(window);
|
|
})) {
|
|
return owner_name;
|
|
}
|
|
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;
|
|
}
|
|
|
|
int GetWindowOwnerPid(CFDictionaryRef window) {
|
|
CFNumberRef window_pid = reinterpret_cast<CFNumberRef>(
|
|
CFDictionaryGetValue(window, kCGWindowOwnerPID));
|
|
if (!window_pid) {
|
|
return 0;
|
|
}
|
|
|
|
int pid;
|
|
if (!CFNumberGetValue(window_pid, kCFNumberIntType, &pid)) {
|
|
return 0;
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
int GetWindowOwnerPid(CGWindowID id) {
|
|
int pid;
|
|
if (GetWindowRef(id, [&pid](CFDictionaryRef window) {
|
|
pid = GetWindowOwnerPid(window);
|
|
})) {
|
|
return pid;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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
|