19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[macOS][Swift4.1] すべてのウインドウをキャプチャして一覧表示する方法

Last updated at Posted at 2018-09-11

Macアプリで他アプリも含めたすべてのウインドウをキャプチャして、スクリーンショット一覧を表示する方法を記載します。

image.png

サンプルコードは以下にアップしました。
https://github.com/atsushijike/AllWindows

  • Xcode 9.4.1
  • Swift 4.1

概要

他のアプリの NSWindowCGWindowRef などのウインドウオブジェクトを取得することはできませんが、 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 クラスを定義します。

Window.swift
class Window {
    fileprivate(set) var id: CGWindowID = 0
    fileprivate(set) var name: String!
    fileprivate(set) var image: NSImage!
   ...
}

CGWindowListCopyWindowInfo で取得した情報を元に Window を生成して配列に追加します。

AppDelegate.swift
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 に指定します。

AppDelegate.swift
let windowInfoList = CGWindowListCopyWindowInfo([.optionAll], 0)

実際には表示されないウインドウの情報もすべて取得されてしまうため、以下の条件のウインドウを省くことにします。

  • アルファが0
  • ウインドウの矩形サイズが100以下
  • 取得したイメージサイズが1以下
  • アプリ名がDock
  • アプリ名がWindow Server
Window.swift
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 を元にウインドウのキャプチャイメージを取得します。

Window.swift
let windowId = windowInfo[Window.convert(CFString: kCGWindowNumber)]
let id = windowId != nil ? Window.convert(CFNumber: windowId as! CFNumber) : 0

キャプチャ部分は CGWindowListCreateImage(_:_:_:_:) を使用して CGImage を取得します。

Window.swift
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()
        }
    }
}
19
9
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?