UTMPatches.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. //
  2. // Copyright © 2022 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. /// Handles Obj-C patches to fix AppKit issues
  18. final class UTMPatches {
  19. static private var isPatched: Bool = false
  20. /// Installs the patches
  21. /// TODO: Some thread safety/race issues etc
  22. static func patchAll() {
  23. NSKeyedUnarchiver.patchToolbarItem()
  24. NSApplication.patchApplicationScripting()
  25. // SwiftUI bug: works around crash due to "already had more Update Constraints in Window passes than there are views in the window" exception
  26. UserDefaults.standard.set(false, forKey: "NSWindowAssertWhenDisplayCycleLimitReached")
  27. }
  28. }
  29. fileprivate extension NSObject {
  30. static func patch(_ original: Selector, with swizzle: Selector, class cls: AnyClass?) {
  31. let originalMethod = class_getInstanceMethod(cls, original)!
  32. let swizzleMethod = class_getInstanceMethod(cls, swizzle)!
  33. method_exchangeImplementations(originalMethod, swizzleMethod)
  34. }
  35. }
  36. /// Patch unarchiving XIB objeccts
  37. extension NSKeyedUnarchiver {
  38. @objc dynamic func xxx_decodeObject(forKey key: String) -> Any? {
  39. switch key {
  40. case "NSMenuToolbarItemImage": return xxx_decodeObject(forKey: "NSToolbarItemImage")
  41. case "NSMenuToolbarItemTitle": return xxx_decodeObject(forKey: "NSToolbarItemTitle")
  42. case "NSMenuToolbarItemTarget": return xxx_decodeObject(forKey: "NSToolbarItemTarget")
  43. case "NSMenuToolbarItemAction": return xxx_decodeObject(forKey: "NSToolbarItemAction")
  44. default: return xxx_decodeObject(forKey: key)
  45. }
  46. }
  47. /// Workaround for exception when creating NSMenuToolbarItem from XIB
  48. fileprivate static func patchToolbarItem() {
  49. patch(#selector(Self.decodeObject(forKey:)),
  50. with: #selector(Self.xxx_decodeObject(forKey:)),
  51. class: Self.self)
  52. }
  53. }
  54. private var ScriptingDelegateHandle: Int = 0
  55. /// Patch NSApplication to use a new delegate for scripting tasks
  56. /// We cannot use NSApplicationDelegate because SwiftUI wraps it with its own implementation
  57. extension NSApplication {
  58. /// Set this, at startup, to the delegate used for scripting
  59. weak var scriptingDelegate: NSApplicationDelegate? {
  60. set {
  61. objc_setAssociatedObject(self, &ScriptingDelegateHandle, newValue, .OBJC_ASSOCIATION_ASSIGN)
  62. }
  63. get {
  64. objc_getAssociatedObject(self, &ScriptingDelegateHandle) as? NSApplicationDelegate
  65. }
  66. }
  67. /// Get KVO value from scripting delegate if implemented
  68. /// - Parameter key: Key to look up
  69. /// - Returns: Value from either scripting delegate or the default implementation
  70. @objc dynamic func xxx_value(forKey key: String) -> Any? {
  71. if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
  72. return (scriptingDelegate as! NSObject).value(forKey: key)
  73. } else {
  74. return xxx_value(forKey: key)
  75. }
  76. }
  77. /// Set KVO value in scripting delegate if implemented
  78. /// - Parameters:
  79. /// - value: Value to set to
  80. /// - key: Key to look up
  81. @objc dynamic func xxx_setValue(_ value: Any?, forKey key: String) {
  82. if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
  83. return (scriptingDelegate as! NSObject).setValue(value, forKey: key)
  84. } else {
  85. return xxx_setValue(value, forKey: key)
  86. }
  87. }
  88. /// Get KVO value from scripting delegate if implemented
  89. /// - Parameters:
  90. /// - index: Index of item
  91. /// - key: Key to look up
  92. /// - Returns: Value from either scripting delegate or the default implementation
  93. @objc dynamic func xxx_value(at index: Int, inPropertyWithKey key: String) -> Any? {
  94. if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
  95. return (scriptingDelegate as! NSObject).value(at: index, inPropertyWithKey: key)
  96. } else {
  97. return xxx_value(at: index, inPropertyWithKey: key)
  98. }
  99. }
  100. /// Set KVO value in scripting delegate if implemented
  101. /// - Parameters:
  102. /// - index: Index of item
  103. /// - key: Key to look up
  104. /// - value: Value to set item to
  105. @objc dynamic func xxx_replaceValue(at index: Int, inPropertyWithKey key: String, withValue value: Any) {
  106. if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
  107. return (scriptingDelegate as! NSObject).replaceValue(at: index, inPropertyWithKey: key, withValue: value)
  108. } else {
  109. return xxx_replaceValue(at: index, inPropertyWithKey: key, withValue: value)
  110. }
  111. }
  112. @objc func handleCreateCommand(_ command: NSCreateCommand) {
  113. (scriptingDelegate as? AppDelegate)?.handleCreateCommand(command)
  114. }
  115. fileprivate static func patchApplicationScripting() {
  116. patch(#selector(Self.value(forKey:)),
  117. with: #selector(Self.xxx_value(forKey:)),
  118. class: Self.self)
  119. patch(#selector(Self.value(at:inPropertyWithKey:)),
  120. with: #selector(Self.xxx_value(at:inPropertyWithKey:)),
  121. class: Self.self)
  122. patch(#selector(Self.setValue(_:forKey:)),
  123. with: #selector(Self.xxx_setValue(_:forKey:)),
  124. class: Self.self)
  125. patch(#selector(Self.replaceValue(at:inPropertyWithKey:withValue:)),
  126. with: #selector(Self.xxx_replaceValue(at:inPropertyWithKey:withValue:)),
  127. class: Self.self)
  128. }
  129. }