Reputation: 491
I'm developing a macOS app that would work with app windows. The goal which I couldn't now afford is to retrieve a list of all opened app windows, which would look something like that (JSON example):
[
{
"appName": "Google Chrome",
"appWindows": [
{
"size": {"x": 100, "y": 100},
"position": {"height": 100, "width": 100},
"desktopNumber": 2
},
{
"size": {"x": 100, "y": 100},
"position": {"height": 100, "width": 100},
"desktopNumber": 3
}
]
},
{
"appName": "Telegram",
"appWindows": [
{
// full screen app, so has a separate desktop number
"size": {"x": 1080, "y": 1920},
"position": {"height": 0, "width": 0},
"desktopNumber": 4
}
]
}
]
Each app has an array of opened windows alongside a desktop number where it's been opened. A full-size app took a separate desktop space, so it should have a separate desktop number.
I know how to get an opened window list with CGWindowListCopyWindowInfo
, but I couldn't find a way to find a desktop number or determine which window is visible or not.
If it's not possible to do with Swift, then perhaps there are apple script solutions?
Upvotes: 2
Views: 1924
Reputation: 21137
You can retrieve app names, bounds & displays using following code:
import Cocoa
import CoreGraphics
let windowsInfo = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, CGWindowID(0))
let maxDisplays: UInt32 = 10
var displays = [CGDirectDisplayID](repeating: 0, count: Int(maxDisplays))
var displayCount: UInt32 = 0
let error = CGGetOnlineDisplayList(maxDisplays, &displays, &displayCount)
for windowInfo in Array<NSDictionary>.fromCFArray(records: windowsInfo) ?? [] {
if let appName = windowInfo["kCGWindowOwnerName"] as? String {
print("appName: \(appName)")
}
if let bounds = windowInfo["kCGWindowBounds"] as? NSDictionary {
print ("X: \(bounds["X"] ?? "<unknown>")")
print ("Y: \(bounds["Y"] ?? "<unknown>")")
print ("Width: \(bounds["Width"] ?? "<unknown>")")
print ("Height: \(bounds["Height"] ?? "<unknown>")")
if
let x = bounds["X"] as? Double,
let y = bounds["Y"] as? Double,
let width = bounds["Width"] as? Double,
let height = bounds["Height"] as? Double
{
for index in 0..<Int(maxDisplays) {
let display = displays[index]
let displayRect = CGDisplayBounds(display)
if displayRect.contains(CGRect(x: x, y: y, width: width, height: height)) {
print("Display: \(index)")
break
}
}
}
print("---")
}
}
extension Array {
static func fromCFArray(records: CFArray?) -> Array<Element>? {
var result: [Element]?
if let records = records {
for i in 0..<CFArrayGetCount(records) {
let unmanagedObject: UnsafeRawPointer = CFArrayGetValueAtIndex(records, i)
let rec: Element = unsafeBitCast(unmanagedObject, to: Element.self)
if (result == nil){
result = [Element]()
}
result!.append(rec)
}
}
return result
}
}
Example output:
appName: Xcode.app
X: 1284
Y: -1074
Width: 1029
Height: 557
Display: 1
---
appName: Xcode.app
X: 1284
Y: -954
Width: 1029
Height: 261
Display: 1
---
appName: Control Centre
X: 0
Y: 900
Width: 32
Height: 24
---
appName: Control Centre
X: 1294
Y: 0
Width: 146
Height: 24
Display: 0
---
appName: Notes
X: 0
Y: 0
Width: 3200
Height: 24
---
appName: Notes
X: 0
Y: 0
Width: 3200
Height: 24
---
appName: Notes
X: 0
Y: 0
Width: 3200
Height: 24
---
appName: Notes
X: -887
Y: -1333
Width: 3200
Height: 24
Display: 1
---
appName: Notes
X: 0
Y: 0
Width: 1440
Height: 24
Display: 0
---
appName: Notes
X: 0
Y: 0
Width: 1440
Height: 24
Display: 0
---
appName: Notes
X: 0
Y: 0
Width: 1440
Height: 24
Display: 0
---
appName: Notes
X: 0
Y: 0
Width: 1440
Height: 24
Display: 0
---
appName: Notes
X: 0
Y: 0
Width: 1440
Height: 24
Display: 0
---
...
Creating the JSON
object with this code is trivial. Btw, you may want to change:
"size": {"x": 1080, "y": 1920},
"position": {"height": 0, "width": 0},
to
"position": {"x": 1080, "y": 1920},
"size": {"height": 0, "width": 0},
Also, note that apps without a UI may not have a Display
value.
Upvotes: 10