macOSで起動中のウィンドウの一覧を列挙する

macOSアプリ開発のお話し。

起動中のウィンドウを一覧するには、CGWindowListCopyWindowInfoを使う。

struct Window: CustomStringConvertible {
    let rawData: [String: AnyObject]
    
    var name: String? {
        get {
            return rawData[kCGWindowName as String] as? String
        }
    }
    var number: Int? {
        get {
            return rawData[kCGWindowNumber as String] as? Int
        }
    }
    var ownerName: String? {
        get {
            return rawData[kCGWindowOwnerName as String] as? String
        }
    }
    var ownerPID: Int? {
        get {
            return rawData[kCGWindowOwnerPID as String] as? Int
        }
    }
    var isOnScreen: Bool {
        get {
            return rawData[kCGWindowIsOnscreen as String] as? Bool ?? false
        }
    }
    var windowLayer: Int {
        get {
            return rawData[kCGWindowLayer as String] as? Int ?? 0
        }
    }
    var description: String {
        get {
            return "Window: \"\(name ?? "nil")\" (\(number ?? -1)) Owner: \"\(ownerName ?? "nil")\" (\(ownerPID ?? -1))"
        }
    }
}

func enumerateWindows() -> [Window] {
    let options = CGWindowListOption(arrayLiteral: CGWindowListOption.optionAll)
    guard let results = CGWindowListCopyWindowInfo(options, CGWindowID(0)),
          let windowList = results as NSArray? as? [[String: AnyObject]]
    else { return [] }
    
    return windowList.map { Window(rawData: $0) }
}

let windows = enumerateWindows()

for window in windows {
    // isOnScreen かつ windowLayer == 0 のものが、通常のアプリのウィンドウであると判断する
    if window.isOnScreen && window.windowLayer == 0 {
        print(window)
    }
}

ただし、このままだとウィンドウのタイトルがほとんど取れない。これはプライバシーの観点で取れないようになっている。

取得するためには画面収録のパーミッションが必要で、以下のコードを実行することでパーミッションを得られる。

CGRequestScreenCaptureAccess()

参考