在 macOS 下获取可屏幕共享的窗口和屏幕
在 macOS 下,我们可以通过使用 Core Graphics 和 Cocoa 框架来获取当前系统中可屏幕共享的窗口和屏幕信息。本文将详细介绍如何获取窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。
前提条件
在开始之前,请确保您的开发环境满足以下条件:
- macOS 系统
- 安装了 Xcode 或其他支持 Objective-C++ 开发的编译器
步骤
1. 包含必要的头文件
首先,包含必要的头文件:
objective-cpp
#include "base/devices/screen/enumerator.h"
#include "base/devices/screen/darwin/desktop_configuration.h"
#include "base/devices/screen/desktop_geometry.h"
#include "base/devices/screen/utils.h"
#include "base/folder/folder.h"
#include "base/log/logger.h"
#include <vector>
#include <functional>
#include <stdlib.h>
#include <string.h>
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
2. 获取窗口信息
我们可以使用 CGWindowListCopyWindowInfo
函数获取当前系统中的窗口信息。以下是获取窗口信息的函数:
objective-cpp
int enum_windows(const traa_size icon_size, const traa_size thumbnail_size,
const unsigned int external_flags, std::vector<traa_screen_source_info> &infos) {
CFArrayRef window_array = CGWindowListCopyWindowInfo(
kCGWindowListOptionAll | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
if (!window_array)
return false;
MacDesktopConfiguration desktop_config =
MacDesktopConfiguration::GetCurrent(MacDesktopConfiguration::TopLeftOrigin);
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;
}
traa_screen_source_info window_info;
window_info.is_window = true;
if (!CFNumberGetValue(window_id, kCFNumberSInt64Type, &window_info.id) || window_info.id <= 0) {
continue;
}
CFNumberRef window_layer =
reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowLayer));
if (!window_layer) {
continue;
}
int layer;
if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)
|| layer < kCGNormalWindowLevel || layer == kCGDockWindowLevel) {
continue;
}
window_info.is_minimized = !is_window_on_screen(window);
window_info.is_maximized = is_window_full_screen(desktop_config, window);
if ((external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_MINIMIZED) && window_info.is_minimized) {
continue;
}
CFNumberRef window_alpha =
reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowAlpha));
if (!window_alpha) {
continue;
} else {
int alpha;
if (!CFNumberGetValue(window_alpha, kCFNumberIntType, &alpha) || alpha <= 0) {
continue;
}
}
CFNumberRef window_sharing_state =
reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowSharingState));
if (!window_sharing_state) {
continue;
} else {
int sharing_state;
if (!CFNumberGetValue(window_sharing_state, kCFNumberIntType, &sharing_state) ||
sharing_state != 1) {
continue;
}
}
CFNumberRef window_pid =
reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowOwnerPID));
if (!window_pid) {
continue;
}
std::string str_title = get_window_title(window);
std::string str_owner_name = get_window_owner_name(window);
if (str_title.empty() && str_owner_name.empty() &&
!(external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_IGNORE_UNTITLED)) {
continue;
}
if (str_title.empty()) {
str_title = std::move(str_owner_name);
}
if (!str_title.empty()) {
strncpy(const_cast<char *>(window_info.title), str_title.c_str(),
std::min(sizeof(window_info.title) - 1, str_title.size()));
}
CGRect window_bounds = CGRectZero;
if (CFDictionaryContainsKey(window, kCGWindowBounds)) {
CFDictionaryRef rect = (CFDictionaryRef)CFDictionaryGetValue(window, kCGWindowBounds);
if (rect) {
CGRectMakeWithDictionaryRepresentation(rect, &window_bounds);
}
}
if (window_bounds.size.width <= 96 || window_bounds.size.height <= 96) {
continue;
}
window_info.rect = desktop_rect::make_xywh(window_bounds.origin.x, window_bounds.origin.y,
window_bounds.size.width, window_bounds.size.height)
.to_traa_rect();
NSRunningApplication *running_app = [NSRunningApplication
runningApplicationWithProcessIdentifier:((__bridge NSNumber *)window_pid).intValue];
if (running_app) {
std::string process_path = [[running_app bundleURL] fileSystemRepresentation];
if (!process_path.empty()) {
strncpy(const_cast<char *>(window_info.process_path), process_path.c_str(),
std::min(sizeof(window_info.process_path) - 1, process_path.size()));
} else if (external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_NOPROCESS_PATH) {
continue;
}
if (icon_size.width > 0 && icon_size.height > 0 && running_app.icon &&
running_app.icon.representations.count > 0) {
NSImage *icon = running_app.icon;
NSBitmapImageRep *icon_rep =
[[NSBitmapImageRep alloc] initWithData:[icon TIFFRepresentation]];
if (icon_rep) {
desktop_size icon_scaled_size =
calc_scaled_size(desktop_size(icon_rep.size.width, icon_rep.size.height), icon_size);
CGSize icon_scaled_ns_size =
CGSizeMake(icon_scaled_size.width(), icon_scaled_size.height());
NSBitmapImageRep *icon_scaled_image_rep = scale_image(icon_rep, icon_scaled_ns_size);
if (icon_scaled_image_rep) {
size_t icon_data_size = [icon_scaled_image_rep pixelsWide] *
[icon_scaled_image_rep pixelsHigh] *
[icon_scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
window_info.icon_data = new uint8_t[icon_data_size];
window_info.icon_size = icon_scaled_size.to_traa_size();
if (!window_info.icon_data) {
LOG_ERROR("alloc memory for icon data failed");
continue;
}
memcpy(const_cast<uint8_t *>(window_info.icon_data), [icon_scaled_image_rep bitmapData],
icon_data_size);
}
}
}
}
NSBitmapImageRep *origin_image_rep =
get_image_rep(true, static_cast<CGWindowID>(window_info.id));
if (!origin_image_rep) {
continue;
}
if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
NSImage *origin_image = [NSImage new];
[origin_image addRepresentation:origin_image_rep];
if (origin_image.size.width <= 1 || origin_image.size.height <= 1) {
continue;
}
desktop_size scaled_size = calc_scaled_size(
desktop_size(origin_image.size.width, origin_image.size.height), thumbnail_size);
CGSize scaled_ns_size = CGSizeMake(scaled_size.width(), scaled_size.height());
NSBitmapImageRep *scaled_image_rep = scale_image(origin_image_rep, scaled_ns_size);
if (!scaled_image_rep) {
continue;
}
size_t thumbnail_data_size = [scaled_image_rep pixelsWide] * [scaled_image_rep pixelsHigh] *
[scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
window_info.thumbnail_data = new uint8_t[thumbnail_data_size];
window_info.thumbnail_size = scaled_size.to_traa_size();
if (!window_info.thumbnail_data) {
LOG_ERROR("alloc memory for thumbnail data failed");
continue;
}
memcpy(const_cast<uint8_t *>(window_info.thumbnail_data), [scaled_image_rep bitmapData],
thumbnail_data_size);
}
infos.push_back(window_info);
}
CFRelease(window_array);
return TRAA_ERROR_NONE;
}
3. 获取屏幕信息
我们可以使用 NSScreen
类获取当前系统中的屏幕信息。以下是获取屏幕信息的函数:
objective-cpp
int enum_screens(const traa_size icon_size, const traa_size thumbnail_size,
const unsigned int external_flags, std::vector<traa_screen_source_info> &infos) {
NSArray *screen_array = NSScreen.screens;
if (!screen_array) {
return TRAA_ERROR_NONE;
}
for (size_t i = 0; i < screen_array.count; i++) {
NSScreen *screen = screen_array[i];
if (!screen) {
continue;
}
CGDirectDisplayID screen_id = static_cast<CGDirectDisplayID>(
[[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue]);
CGRect screen_bounds = CGDisplayBounds(screen_id);
NSBitmapImageRep *screen_image_rep = get_image_rep(false, screen_id);
if (!screen_image_rep) {
continue;
}
traa_screen_source_info screen_info;
screen_info.is_window = false;
screen_info.is_primary = CGDisplayIsMain(screen_id);
screen_info.id = static_cast<int64_t>(screen_id);
snprintf(const_cast<char *>(screen_info.title), sizeof(screen_info.title), "Display %d",
static_cast<int>(i));
screen_info.rect = desktop_rect::make_xywh(screen_bounds.origin.x, screen_bounds.origin.y,
screen_bounds.size.width, screen_bounds.size.height)
.to_traa_rect();
if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
NSImage *screen_image = [NSImage new];
[screen_image addRepresentation:screen_image_rep];
desktop_size scaled_size = calc_scaled_size(
desktop_size(screen_image.size.width, screen_image.size.height), thumbnail_size);
CGSize scaled_ns_size = CGSizeMake(scaled_size.width(), scaled_size.height());
NSBitmapImageRep *scaled_image_rep = scale_image(screen_image_rep, scaled_ns_size);
if (!scaled_image_rep) {
continue;
}
size_t thumbnail_data_size = [scaled_image_rep pixelsWide] * [scaled_image_rep pixelsHigh] *
[scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
screen_info.thumbnail_data = new uint8_t[thumbnail_data_size];
screen_info.thumbnail_size = scaled_size.to_traa_size();
if (!screen_info.thumbnail_data) {
LOG_ERROR("alloc memory for thumbnail data failed");
continue;
}
memcpy(const_cast<uint8_t *>(screen_info.thumbnail_data), [scaled_image_rep bitmapData],
thumbnail_data_size);
}
infos.push_back(screen_info);
}
return TRAA_ERROR_NONE;
}
4. 枚举屏幕和窗口信息
最后,我们可以通过调用 enum_windows
和 enum_screens
函数来枚举当前系统中的窗口和屏幕信息:
objective-cpp
int screen_source_info_enumerator::enum_screen_source_info(const traa_size icon_size,
const traa_size thumbnail_size,
const unsigned int external_flags,
traa_screen_source_info **infos,
int *count) {
if (__builtin_available(macOS 11, *)) {
if (!CGPreflightScreenCaptureAccess()) {
CGRequestScreenCaptureAccess();
return TRAA_ERROR_PERMISSION_DENIED;
}
}
std::vector<traa_screen_source_info> sources;
if (!(external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_WINDOW)) {
enum_windows(icon_size, thumbnail_size, external_flags, sources);
}
if (!(external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_SCREEN)) {
enum_screens(icon_size, thumbnail_size, external_flags, sources);
}
if (sources.size() == 0) {
return traa_error::TRAA_ERROR_NONE;
}
*count = static_cast<int>(sources.size());
*infos = reinterpret_cast<traa_screen_source_info *>(new traa_screen_source_info[sources.size()]);
if (*infos == nullptr) {
LOG_ERROR("alloca memroy for infos failed");
return traa_error::TRAA_ERROR_OUT_OF_MEMORY;
}
for (size_t i = 0; i < sources.size(); ++i) {
auto &source_info = sources[i];
auto &dest_info = (*infos)[i];
memcpy(&dest_info, &source_info, sizeof(traa_screen_source_info));
if (std::strlen(source_info.title) > 0) {
strncpy(const_cast<char *>(dest_info.title), source_info.title,
std::min(sizeof(dest_info.title) - 1, std::strlen(source_info.title)));
}
if (std::strlen(source_info.process_path) > 0) {
strncpy(const_cast<char *>(dest_info.process_path), source_info.process_path,
std::min(sizeof(dest_info.process_path) - 1, std::strlen(source_info.process_path)));
}
}
return TRAA_ERROR_NONE;
}
总结
通过上述步骤,我们可以在 macOS 下获取当前系统中可屏幕共享的窗口和屏幕信息,包括窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。希望这篇文章对您有所帮助。