FFMPEG录屏(20)--- 枚举macOS下的窗口和屏幕列表,并获取名称缩略图等信息
在 macOS 下获取可屏幕共享的窗口和屏幕
在 macOS 下,我们可以通过使用 Core Graphics 和 Cocoa 框架来获取当前系统中可屏幕共享的窗口和屏幕信息。本文将详细介绍如何获取窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。
前提条件
在开始之前,请确保您的开发环境满足以下条件:
- macOS 系统
- 安装了 Xcode 或其他支持 Objective-C++ 开发的编译器
步骤
1. 包含必要的头文件
首先,包含必要的头文件:
#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 函数获取当前系统中的窗口信息。以下是获取窗口信息的函数:
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 = [NSRunningApplicationrunningApplicationWithProcessIdentifier:((__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 类获取当前系统中的屏幕信息。以下是获取屏幕信息的函数:
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 函数来枚举当前系统中的窗口和屏幕信息:
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、标题、坐标、进程图标和缩略图等信息。希望这篇文章对您有所帮助。
源码传送
traa
相关文章:
FFMPEG录屏(20)--- 枚举macOS下的窗口和屏幕列表,并获取名称缩略图等信息
在 macOS 下获取可屏幕共享的窗口和屏幕 在 macOS 下,我们可以通过使用 Core Graphics 和 Cocoa 框架来获取当前系统中可屏幕共享的窗口和屏幕信息。本文将详细介绍如何获取窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。 前提条件 在开始之前ÿ…...
Redis 命令集 (超级详细)
目录 Redis 常用命令集 string类型 hash类型 list类型 set类型 zset类型 bitmap 类型 geo 类型 GEOADD (添加地理位置的坐标) GEOPOS (获取地理位置的坐标) GEODIST (计算两个位置之间的距离) GEOHASH (返回一个或多个位置对象的 geohash 值) GEORADIUS (根据用户…...
Spring Cloud --- GateWay和Sentinel集成实现服务限流
pom添加依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency><groupId>com.alibaba.csp</groupId><artifactId>s…...
python excel如何转成json,并且如何解决excel转成json时中文汉字乱码的问题
1.解决excel转成json时中文汉字乱码的问题 真的好久没有打开这个博客也好久没有想起来记录一下问题了,今天将表格测试集转成json格式的时候遇到了汉字都变成了乱码的问题,虽然这不是个大问题,但是编码问题挺烦人的,乱码之后像下图…...
【MySQL】实战篇—数据库设计与实现:根据需求设计数据库架构
在设计数据库架构时,开发者需要遵循一系列步骤,以确保数据库能够高效、可靠地满足系统需求。以下是设计数据库架构的理论知识和步骤说明。 1. 需求分析 需求分析是数据库设计的第一步,旨在理解系统的功能需求和数据需求。通过与利益相关者&…...
[Python学习日记-53] Python 中的正则表达式模块 —— re
[Python学习日记-53] Python 中的正则表达式模块 —— re 简介 re 模块 练习 简介 我们在编程的时候经常会遇到想在一段文字当中找出电话号码、身份证号、身高、年龄之类的信息,就像下面的数据一样 # 文件名:美丽学姐联系方式.txt 姓名 地区 …...
Unity编辑器 连接不到SteamVR问题记录
问题表现:之前正常的工程,某天打开后运行,在SteamVR未打开时,Unity工程运行后无法调用起来Steam VR,无任何反应,但用其他软件则可以调用起来SteamVR,并且运行正常,在重装了XR的一些插…...
nginx 日志配置笔记
Nginx 的日志配置非常重要,它可以帮助你记录服务器的访问情况、错误信息等,便于后续的分析和故障排查。Nginx 的日志配置主要包括访问日志(access log)和错误日志(error log)。 1、访问日志(Ac…...
Java中的接口是什么?如何定义接口?
1、Java中的接口是什么?如何定义接口? 在Java中,接口是一种引用类型,它定义了一组方法的契约,但不包含实现。接口定义了方法签名,但不提供方法的实现细节。Java中的接口用于实现多态性和代码的抽象化。 在…...
8.13TB高清卫星影像更新(WGS84坐标投影)
最近对WGS84版的高清卫星影像数据进行了一次更新,并基于更新区域生成了相应的接图表。 8.13TB高清卫星影像更新 本次数据更新了14820个离线包,共8.13TB大小,主要更新目标区域为中国东南区域。 更新范围接图表一 更新范围接图表二 更新范围…...
【力扣】[Java版] 刷题笔记-21. 合并两个有序链表
题目: 21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 解题思路 从题目和示例可以看出,应该是要循环遍历链表进行比较,然后组成新的链表。 第一种:递归…...
【Bug】RuntimeError: Engine loop has died
目录 报错前置条件报错内容解决方案 报错前置条件 使用vllm启动qwen2.5-32b-instruct模型后发生的报错 GPU是GeForce RTX 4090 Laptop GPU 系统是Windows 11 运行系统是WSL2-Ubuntu22.04 报错内容 INFO 10-22 22:29:31 engine.py:290] Added request chat-993cbe95e73d4a1db…...
Labview写CIP协议
参考资料 读(INT)变量 发送: 6f00 1C00 6d010f00 00000000 0300000000000000 00000000 00000000 0000 0200 0000 0000 B200 0C00 4d 02 91 02 5353 C300 0100 7856 返回: 6f00 1400 6d010f00 00000000 0300000000000000 00000000 00000000 0000 020…...
Redis批量获取缓存的方法
使用multiGet方法 优点:简单易用,适用于获取少量键的场景。 缺点:当获取的键数量较多时,可能会因为网络延迟导致性能下降。此外,如果某个键不存在,对应的返回值会是null,需要额外处理。 其他…...
MySQL配置文件中server-id的作用是什么
作用一: 通过 server-id 可以用来唯一标识主从复制环境中的一个服务器, 作用二: 再进行主从复制的过程中,会传递二进制日志文件,server-id 帮助MySQL确定哪些日志属于哪个服务器,从而确保日志正确地路由到相…...
Docker入门之构建
Docker构建概述 Docker Build 实现了客户端-服务器架构,其中: 客户端:Buildx 是用于运行和管理构建的客户端和用户界面。服务器:BuildKit 是处理构建执行的服务器或构建器。 当您调用构建时,Buildx 客户端会向 Bui…...
StarRocks数据库在SQL语句中解析JSON字符串
StarRocks数据库在SQL语句中解析JSON字符串 -- 使用数据库 use sr_test; -- 删除表 drop table ts_usr; -- 创建表 CREATE TABLE ts_usr ( uid bigint NOT NULL COMMENT "用户id", uname varchar(64) NULL COMMENT "用户名", ujson varchar(1024) NULL CO…...
RabbitMq-队列交换机绑定关系优化为枚举注册
📚目录 📚简介:🚀比较💨通常注册🌈优化后注册 ✍️代码💫自动注册的关键代码 📚简介: 该项目介绍,rabbitMq消息中间件,对队列的注册,交换机的注册,…...
施磊C++ | 项目实战 | 手写移植SGI STL二级空间配置器内存池 项目源码
手写移植SGI STL二级空间配置器内存池 项目源码 笔者建议配合这两篇博客进行学习 侯捷 | C | 内存管理 | 学习笔记(二):第二章节 std::allocator-CSDN博客 施磊C | 项目实战 | SGI STL二级空间配置器源码剖析-CSDN博客 文章目录 手写移植SGI STL二级空…...
C++ | Leetcode C++题解之第507题完美数
题目: 题解: class Solution { public:bool checkPerfectNumber(int num) {if (num 1) {return false;}int sum 1;for (int d 2; d * d < num; d) {if (num % d 0) {sum d;if (d * d < num) {sum num / d;}}}return sum num;} };...
LynxPrompt Action:GitHub Actions 实现 AI 配置中心化与自动化管理
1. 项目概述:为什么我们需要一个AI配置的“中央仓库”? 如果你和我一样,日常开发中同时用着Cursor、Claude Code、GitHub Copilot,甚至还在尝试Windsurf和Aider,那你一定遇到过这个头疼的问题:每个工具的配…...
Windows动态光标优化:LuumaCursorHelper工具包详解与实战指南
1. 项目概述与核心价值最近在折腾一个挺有意思的小工具,起因是发现很多朋友在用LuumaCursor这款动态光标主题时,总会遇到一些“小麻烦”。比如,安装后光标在某些应用里不显示、动画卡顿,或者想自定义一下效果却无从下手。我自己也…...
从Awesome List到实战:构建你的AI编程工作流与Vibe Coding环境
1. 从“Awesome List”到“Vibe Coding”实战指南:如何构建你的AI编程工作流如果你最近在GitHub上逛过,或者关注AI编程工具的圈子,大概率会刷到一个叫“Awesome Vibe Coding”的仓库。乍一看,它像是一个又一个AI工具和项目的简单罗…...
Zotero茉莉花插件:3大功能轻松管理中文文献,科研效率翻倍提升
Zotero茉莉花插件:3大功能轻松管理中文文献,科研效率翻倍提升 【免费下载链接】jasminum A Zotero add-on to retrive CNKI meta data. 一个简单的Zotero 插件,用于识别中文元数据 项目地址: https://gitcode.com/gh_mirrors/ja/jasminum …...
暖心指南|心理干预案例分享关键点!
行业痛点分析重庆市作为西部人口密集城市,心理健康问题呈现年轻化、复杂化趋势。调研数据显示,2022年全市心理健康机构年接诊量突破18万人次,但供需矛盾持续加剧。当前主要存在三方面挑战:临床诊断精度不足:传统评估依…...
Hack The Box注册失败?别慌,可能是你的‘上网姿势’不对(附最新可用方案)
Hack The Box注册问题排查与解决方案全指南 注册Hack The Box时遇到各种报错提示是许多技术爱好者共同的困扰。作为全球知名的网络安全实战平台,其注册流程确实存在一些技术门槛需要跨越。本文将系统性地分析注册失败的深层原因,并提供多种经过验证的解决…...
New-API数据导出功能:轻松管理AI模型使用记录与账单数据
New-API数据导出功能:轻松管理AI模型使用记录与账单数据 【免费下载链接】new-api A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible for…...
MODLR Studio光标操作插件开发:提升数据建模效率的交互优化实践
1. 项目概述与核心价值 最近在数据建模和可视化领域,一个名为 MODLR-Studio/modlr_cursor_ops 的项目引起了我的注意。乍一看这个标题,可能有些朋友会感到困惑:“MODLR”是什么?“Cursor Ops”又是指什么操作?这其实…...
Vivado 伪双口RAM IP核的配置精髓与实战避坑指南
1. 伪双口RAM的本质与真双口RAM的差异 第一次接触伪双口RAM(Simple Dual Port RAM)时,很多人会疑惑它和真双口RAM(True Dual Port RAM)到底有什么区别。这个问题困扰了我很久,直到在实际项目中踩了几个坑才…...
MATLAB图像处理实战:用imfindcircles函数搞定工业零件瑕疵检测(附完整代码)
MATLAB图像处理实战:工业零件瑕疵检测的精准圆识别技术 在工业自动化质检领域,圆形特征的精准检测直接关系到产品质量控制的可靠性。轴承、垫片、齿轮等标准件上的孔洞缺失或尺寸偏差,往往预示着潜在的产品缺陷。传统人工检测不仅效率低下&am…...
