注意:这个问题有意非常一般(例如,请求Objective-C和Swift代码示例),因为它旨在记录如何尽可能可访问地捕获macOS上的窗口截图。
我想在Objective-C / Swift代码中捕获macOS窗口的屏幕截图。我知道这是可能的,因为有很多方法可以在macOS上截取屏幕截图(⇧⌘4,Grab实用程序,screencapture
在命令行上,...),但我不知道如何在我自己的代码。理想情况下,我可以指定特定应用程序的窗口,然后在NSImage
或CGImage
中捕获它,然后我可以处理并显示给用户或存储在文件中。 / p>
答案 0 :(得分:13)
可以通过核心图形框架的工具Quartz Window Services在macOS上进行屏幕截图。我们的关键功能是CGWindowListCreateImage
,它“根据动态生成的窗口列表返回合成图像”,或者换句话说,根据指定的条件查找窗口,并创建包含每个窗口内容的图像。完善!其声明如下:
CGImageRef CGWindowListCreateImage(CGRect screenBounds,
CGWindowListOption listOption,
CGWindowID windowID,
CGWindowImageOption imageOption);
因此,为了捕获屏幕上的一个特定窗口,我们需要其窗口ID(CGWindowID
)。为了检索它,我们首先需要一个系统上所有可用窗口的列表。我们通过CGWindowListCopyWindowInfo
得到了这一点,CGWindowListOption
将CGWindowID
和相应的kCGWindowListOptionAll
一起选择要包含在结果列表中的窗口。要获取所有窗口,我们分别指定kCGNullWindowID
和NSArray<NSDictionary*> *windowInfoList = (__bridge_transfer id)
CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
。此外,如果您还没有弄明白,这是一个C API,所以我们将使用桥接转换来处理更友好的Objective-C容器而不是Core Foundation容器。
<强>目标-C:强>
let windowInfoList = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID)!
as NSArray
<强>夫特:强>
windowInfoList
从这里开始,我们需要将NSRunningApplication
过滤到我们想要的特定窗口。我们希望首先按应用程序过滤。为此,我们需要选择应用程序的进程ID。我们可以使用NSArray<NSRunningApplication*> *apps =
[NSRunningApplication runningApplicationsWithBundleIdentifier:
/* Bundle ID of the application, e.g.: */ @"com.apple.Safari"];
if (apps.count == 0) {
// Application is not currently running
puts("The application is not running");
return; // Or whatever
}
pid_t appPID = apps[0].processIdentifier;
来完成此任务:
<强>目标-C:强>
let apps = NSRunningApplication.runningApplications(withBundleIdentifier:
/* Bundle ID of the application, e.g.: */ "com.apple.Safari")
if apps.isEmpty {
// Application is not currently running
print("The application is not running")
return // Or whatever
}
let appPID = apps[0].processIdentifier
<强>夫特:强>
appPID
有了NSMutableArray<NSDictionary*> *appWindowsInfoList = [NSMutableArray new];
for (NSDictionary *info in windowInfoList) {
if ([info[(__bridge NSString *)kCGWindowOwnerPID] integerValue] == appPID) {
[appWindowsInfoList addObject:info];
}
}
,我们现在可以继续将我们的窗口信息列表过滤到只有匹配所有者PID的窗口:
<强>目标-C:强>
var appWindowsInfoList = [NSDictionary]()
for info_ in windowInfoList {
let info = info_ as! NSDictionary
if (info[kCGWindowOwnerPID as NSString] as! NSNumber).intValue == appPID {
appWindowsInfoList.append(info)
}
}
<强>夫特:强>
kCGWindowName
我们可以通过测试信息字典的其他键来完成上面的其他过滤 - 例如,通过名称(kCGWindowIsOnscreen
),或者窗口是否在屏幕上(NSDictionary *appWindowInfo = appWindowsInfoList[0];
CGWindowID windowID = [appWindowInfo[(__bridge NSString *)kCGWindowNumber] unsignedIntValue];
) - 但是现在,我们只需要列表中的第一个窗口:
<强>目标-C:强>
let appWindowInfo: NSDictionary = appWindowsInfoList[0];
let windowID: CGWindowID = (appWindowInfo[kCGWindowNumber as NSString] as! NSNumber).uint32Value
<强>夫特强>:
CGImageRef CGWindowListCreateImage(CGRect screenBounds,
CGWindowListOption listOption,
CGWindowID windowID,
CGWindowImageOption imageOption);
我们有窗口ID!现在,我们还有什么需要再次召开这个电话会议?
screenBounds
首先,我们需要CGRectNull
来捕获。根据{{3}},我们可以为此参数指定listOption
以尽可能紧密地包含所有指定的窗口。适合我。
其次,我们必须指定我们如何选择CGWindowListCopyWindowInfo
的窗口。在我们调用kCGWindowListOptionIncludingWindow
时,我们实际上使用了其中一个,但我们想要系统上的所有窗口;在这里,我们只想要一个,所以我们将指定CGWindowListCreateImage
,与the documentation相反, 对windowID
有意义,因为它指定CGWindowImageOption
我们传递的窗口,仅我们传递的窗口。
第三,我们将imageOption
作为我们想要捕获的窗口传递。
第四,最后,我们可以使用kCGWindowImageDefault
参数指定kCGWindowImageBoundsIgnoreFraming
s。这些会影响所得图像的外观;你可以通过按位OR组合它们。完整列表为its documentation page,但常见列表包括kCGWindowImageBestResolution
和kCGWindowImageNominalResolution
,kCGWindowImageBoundsIgnoreFraming
捕获窗口的内容及其帧和阴影,kCGWindowImageNominalResolution
仅捕获内容,{{1}以可用的最佳分辨率捕获窗口内容,无论实际大小(并且,取决于窗口,可能相当大),或CGImageRef windowImage =
CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow,
windowID, kCGWindowImageBoundsIgnoreFraming|
kCGWindowImageNominalResolution);
// NOTE: windowImage may be NULL if the capture failed
,它以屏幕上的当前大小捕获窗口。在这里,我选择了let windowImage: CGImage? =
CGWindowListCreateImage(.null, .optionIncludingWindow, windowID,
[.boundsIgnoreFraming, .nominalResolution])
和CREATE
(u:User {name: 'Alice'})-[:OWNS]->
(p1:Post {number: 1})-[:PREV]->
(p2:Post {number: 2})-[:PREV]->
(p3:Post {number: 3})-[:PREV]->
(p4:Post {number: 4})-[:PREV]->
(p5:Post)
来仅捕获与屏幕尺寸相同的内容。
Aaand,鼓声请:
<强>目标-C:强>
OPTIONAL MATCH
<强>夫特:强>
DELETE