捕获macOS窗口的截图

时间:2017-12-30 01:47:58

标签: objective-c swift macos screenshot

注意:这个问题有意非常一般(例如,请求Objective-C和Swift代码示例),因为它旨在记录如何尽可能可访问地捕获macOS上的窗口截图。

我想在Objective-C / Swift代码中捕获macOS窗口的屏幕截图。我知道这是可能的,因为有很多方法可以在macOS上截取屏幕截图(⇧⌘4,Grab实用程序,screencapture在命令行上,...),但我不知道如何在我自己的代码。理想情况下,我可以指定特定应用程序的窗口,然后在NSImageCGImage中捕获它,然后我可以处理并显示给用户或存储在文件中。 / p>

1 个答案:

答案 0 :(得分:13)

可以通过核心图形框架的工具Quartz Window Services在macOS上进行屏幕截图。我们的关键功能是CGWindowListCreateImage,它“根据动态生成的窗口列表返回合成图像”,或者换句话说,根据指定的条件查找窗口,并创建包含每个窗口内容的图像。完善!其声明如下:

CGImageRef CGWindowListCreateImage(CGRect screenBounds, 
                                   CGWindowListOption listOption, 
                                   CGWindowID windowID, 
                                   CGWindowImageOption imageOption);

因此,为了捕获屏幕上的一个特定窗口,我们需要其窗口ID(CGWindowID)。为了检索它,我们首先需要一个系统上所有可用窗口的列表。我们通过CGWindowListCopyWindowInfo得到了这一点,CGWindowListOptionCGWindowID和相应的kCGWindowListOptionAll一起选择要包含在结果列表中的窗口。要获取所有窗口,我们分别指定kCGNullWindowIDNSArray<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,但常见列表包括kCGWindowImageBestResolutionkCGWindowImageNominalResolutionkCGWindowImageBoundsIgnoreFraming捕获窗口的内容及其帧和阴影,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