scripting: patch NSApplication to support KVO delegate

SwiftUI takes over the NSApplicationDelegate and does not pass KVO events
down to the NSApplicationDelegateAdaptor.
This commit is contained in:
osy
2022-12-14 18:43:49 -08:00
parent d2b488b6a5
commit 93e897e393
2 changed files with 52 additions and 0 deletions
+1
View File
@@ -25,6 +25,7 @@ struct UTMApp: App {
ContentView().environmentObject(data)
.onAppear {
appDelegate.data = data
NSApp.scriptingDelegate = appDelegate
Task {
do {
try await data.apiServer.start()
+51
View File
@@ -24,6 +24,7 @@ final class UTMPatches {
/// TODO: Some thread safety/race issues etc
static func patchAll() {
NSKeyedUnarchiver.patchToolbarItem()
NSApplication.patchApplicationScripting()
// SwiftUI bug: works around crash due to "already had more Update Constraints in Window passes than there are views in the window" exception
UserDefaults.standard.set(false, forKey: "NSWindowAssertWhenDisplayCycleLimitReached")
}
@@ -56,3 +57,53 @@ extension NSKeyedUnarchiver {
class: Self.self)
}
}
private var ScriptingDelegateHandle: Int = 0
/// Patch NSApplication to use a new delegate for scripting tasks
/// We cannot use NSApplicationDelegate because SwiftUI wraps it with its own implementation
extension NSApplication {
/// Set this, at startup, to the delegate used for scripting
weak var scriptingDelegate: NSApplicationDelegate? {
set {
objc_setAssociatedObject(self, &ScriptingDelegateHandle, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
get {
objc_getAssociatedObject(self, &ScriptingDelegateHandle) as? NSApplicationDelegate
}
}
/// Get KVO value from scripting delegate if implemented
/// - Parameter key: Key to look up
/// - Returns: Value from either scripting delegate or the default implementation
@objc dynamic func xxx_value(forKey key: String) -> Any? {
if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
return (scriptingDelegate as! NSObject).value(forKey: key)
} else {
return xxx_value(forKey: key)
}
}
/// Get KVO value from scripting delegate if implemented
/// - Parameters:
/// - index: Index of item
/// - key: Key to look up
/// - Returns: Value from either scripting delegate or the default implementation
@objc dynamic func xxx_value(at index: Int, inPropertyWithKey key: String) -> Any? {
if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
return (scriptingDelegate as! NSObject).value(at: index, inPropertyWithKey: key)
} else {
return xxx_value(at: index, inPropertyWithKey: key)
}
}
fileprivate static func patchApplicationScripting() {
patch(#selector(Self.value(forKey:)),
with: #selector(Self.xxx_value(forKey:)),
class: Self.self)
patch(#selector(Self.value(at:inPropertyWithKey:)),
with: #selector(Self.xxx_value(at:inPropertyWithKey:)),
class: Self.self)
}
}