import Foundation
|
|
struct PrivateSpaceFocusOutcome {
|
let targetSpaceID: UInt64
|
let displayIdentifier: String?
|
let previousCurrentSpaceID: UInt64?
|
let didRequestFocus: Bool
|
let error: String?
|
}
|
|
final class PrivateSpaceActivationBridge: @unchecked Sendable {
|
enum BridgeError: Error {
|
case frameworkUnavailable
|
case symbolUnavailable(String)
|
}
|
|
private typealias CGSMainConnectionIDFunction = @convention(c) () -> UInt32
|
private typealias CGSCopyManagedDisplaySpacesFunction = @convention(c) (UInt32) -> Unmanaged<CFArray>?
|
private typealias CGSManagedDisplaySetCurrentSpaceFunction = @convention(c) (UInt32, CFString, UInt64) -> Void
|
|
static let shared = try? PrivateSpaceActivationBridge()
|
|
private let handle: UnsafeMutableRawPointer?
|
private let mainConnectionID: CGSMainConnectionIDFunction
|
private let copyManagedDisplaySpaces: CGSCopyManagedDisplaySpacesFunction
|
private let setCurrentSpace: CGSManagedDisplaySetCurrentSpaceFunction
|
|
private init() throws {
|
guard let handle = dlopen("/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight", RTLD_LAZY) else {
|
throw BridgeError.frameworkUnavailable
|
}
|
|
self.handle = handle
|
self.mainConnectionID = try Self.symbol("CGSMainConnectionID", in: handle)
|
self.copyManagedDisplaySpaces = try Self.symbol("CGSCopyManagedDisplaySpaces", in: handle)
|
self.setCurrentSpace = try Self.symbol("CGSManagedDisplaySetCurrentSpace", in: handle)
|
}
|
|
deinit {
|
if let handle {
|
dlclose(handle)
|
}
|
}
|
|
func focus(spaceIDs: [UInt64]) -> PrivateSpaceFocusOutcome? {
|
guard let targetSpaceID = spaceIDs.first else { return nil }
|
|
let connectionID = mainConnectionID()
|
guard
|
let unmanagedDisplays = copyManagedDisplaySpaces(connectionID),
|
let displayRecords = unmanagedDisplays.takeRetainedValue() as? [[String: Any]]
|
else {
|
return PrivateSpaceFocusOutcome(
|
targetSpaceID: targetSpaceID,
|
displayIdentifier: nil,
|
previousCurrentSpaceID: nil,
|
didRequestFocus: false,
|
error: "managedDisplaySpacesUnavailable"
|
)
|
}
|
|
for displayRecord in displayRecords {
|
let displayIdentifier = stringValue(displayRecord["Display Identifier"])
|
let spaces = displayRecord["Spaces"] as? [[String: Any]] ?? []
|
guard spaces.contains(where: { spaceID(from: $0) == targetSpaceID }) else {
|
continue
|
}
|
|
let previousCurrentSpaceID = (displayRecord["Current Space"] as? [String: Any]).flatMap(spaceID)
|
guard !displayIdentifier.isEmpty else {
|
return PrivateSpaceFocusOutcome(
|
targetSpaceID: targetSpaceID,
|
displayIdentifier: nil,
|
previousCurrentSpaceID: previousCurrentSpaceID,
|
didRequestFocus: false,
|
error: "displayIdentifierMissing"
|
)
|
}
|
|
if previousCurrentSpaceID == targetSpaceID {
|
return PrivateSpaceFocusOutcome(
|
targetSpaceID: targetSpaceID,
|
displayIdentifier: displayIdentifier,
|
previousCurrentSpaceID: previousCurrentSpaceID,
|
didRequestFocus: false,
|
error: nil
|
)
|
}
|
|
setCurrentSpace(connectionID, displayIdentifier as CFString, targetSpaceID)
|
return PrivateSpaceFocusOutcome(
|
targetSpaceID: targetSpaceID,
|
displayIdentifier: displayIdentifier,
|
previousCurrentSpaceID: previousCurrentSpaceID,
|
didRequestFocus: true,
|
error: nil
|
)
|
}
|
|
return PrivateSpaceFocusOutcome(
|
targetSpaceID: targetSpaceID,
|
displayIdentifier: nil,
|
previousCurrentSpaceID: nil,
|
didRequestFocus: false,
|
error: "targetSpaceNotFound"
|
)
|
}
|
|
private static func symbol<T>(_ name: String, in handle: UnsafeMutableRawPointer) throws -> T {
|
guard let rawSymbol = dlsym(handle, name) else {
|
throw BridgeError.symbolUnavailable(name)
|
}
|
|
return unsafeBitCast(rawSymbol, to: T.self)
|
}
|
|
private func spaceID(from record: [String: Any]) -> UInt64? {
|
uint64Value(record["id64"] ?? record["id"])
|
}
|
|
private func stringValue(_ value: Any?) -> String {
|
if let string = value as? String { return string }
|
if let number = value as? NSNumber { return number.stringValue }
|
return ""
|
}
|
|
private func uint64Value(_ value: Any?) -> UInt64? {
|
if let number = value as? NSNumber { return number.uint64Value }
|
if let value = value as? UInt64 { return value }
|
if let value = value as? UInt32 { return UInt64(value) }
|
if let value = value as? Int { return UInt64(value) }
|
if let string = value as? String { return UInt64(string) }
|
return nil
|
}
|
}
|