Macアプリで他アプリも含めたすべてのウインドウをキャプチャして、スクリーンショット一覧を表示する方法を記載します。
サンプルコードは以下にアップしました。
https://github.com/atsushijike/AllWindows
- Xcode 9.4.1
- Swift 4.1
概要
他のアプリの NSWindow
や CGWindowRef
などのウインドウオブジェクトを取得することはできませんが、 CGWindowListCopyWindowInfo(_:_:)
を使用することで以下のようなウインドウの情報を取得することができます。
{
kCGWindowAlpha = 1;
kCGWindowBounds = {
Height = 546;
Width = 1994;
X = 52;
Y = 478;
};
kCGWindowIsOnscreen = 1;
kCGWindowLayer = 0;
kCGWindowMemoryUsage = 1128;
kCGWindowName = Code;
kCGWindowNumber = 3190;
kCGWindowOwnerName = Finder;
kCGWindowOwnerPID = 567;
kCGWindowSharingState = 1;
kCGWindowStoreType = 1;
}
kCGWindowNumber
キーから CGWindowListCreateImage(_:_:_:_:)
を使用してウインドウキャプチャイメージを取得することができます。
ウインドウ一覧の取得
ウインドウIDとアプリ名、キャプチャイメージを持つ Window
クラスを定義します。
class Window {
fileprivate(set) var id: CGWindowID = 0
fileprivate(set) var name: String!
fileprivate(set) var image: NSImage!
...
}
CGWindowListCopyWindowInfo
で取得した情報を元に Window
を生成して配列に追加します。
class AppDelegate: NSObject, NSApplicationDelegate {
...
private var windows: [Window] = []
...
func reloadWindows() {
windows.removeAll()
if let windowInfoList = CGWindowListCopyWindowInfo([.optionAll], 0) {
for windowInfo in windowInfoList as NSArray {
if let info = windowInfo as? NSDictionary,
let window = Window(with: info) {
print("\(info)")
windows.append(window)
}
}
}
collectionView.reloadData()
}
...
}
フルスクリーン時に取得すると同じスクリーンにあるウインドウが取得できないため、
option
引数を CGWindowListOption.optionAll
に指定します。
let windowInfoList = CGWindowListCopyWindowInfo([.optionAll], 0)
実際には表示されないウインドウの情報もすべて取得されてしまうため、以下の条件のウインドウを省くことにします。
- アルファが0
- ウインドウの矩形サイズが100以下
- 取得したイメージサイズが1以下
- アプリ名がDock
- アプリ名がWindow Server
class Window {
...
init?(with windowInfo: NSDictionary) {
let windowAlpha = windowInfo[Window.convert(CFString: kCGWindowAlpha)]
let alpha = windowAlpha != nil ? (windowAlpha as! NSNumber).intValue : 0
let windowBounds = windowInfo[Window.convert(CFString: kCGWindowBounds)]
let bounds = windowBounds != nil ? CGRect(dictionaryRepresentation: windowBounds as! CFDictionary) ?? .zero : .zero
let ownerName = windowInfo[Window.convert(CFString: kCGWindowOwnerName)]
let name = ownerName != nil ? Window.convert(CFString: ownerName as! CFString) : ""
let windowId = windowInfo[Window.convert(CFString: kCGWindowNumber)]
let id = windowId != nil ? Window.convert(CFNumber: windowId as! CFNumber) : 0
let image = NSImage.windowImage(with: id)
guard
alpha > 0,
bounds.width > 100,
bounds.height > 100,
image.size.width > 1,
image.size.height > 1,
name != "Dock",
name != "Window Server" else {
return nil
}
self.id = id
self.name = name
self.image = image
}
ウインドウのスクリーンショット
kCGWindowNumber
キーで取得した CGWindowID
を元にウインドウのキャプチャイメージを取得します。
let windowId = windowInfo[Window.convert(CFString: kCGWindowNumber)]
let id = windowId != nil ? Window.convert(CFNumber: windowId as! CFNumber) : 0
キャプチャ部分は CGWindowListCreateImage(_:_:_:_:)
を使用して CGImage
を取得します。
private extension NSImage {
class func windowImage(with windowId: CGWindowID) -> NSImage {
if let screenShot = CGWindowListCreateImage(CGRect.null, .optionIncludingWindow, CGWindowID(windowId), CGWindowImageOption()) {
let bitmapRep = NSBitmapImageRep(cgImage: screenShot)
let image = NSImage()
image.addRepresentation(bitmapRep)
return image
} else {
return NSImage()
}
}
}