Explorar el Código

utmctl: support auto-hiding main window

osy hace 2 años
padre
commit
4a08edbd41

+ 12 - 0
Platform/macOS/AppDelegate.swift

@@ -37,6 +37,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
         }
     }
     
+    @MainActor
+    @objc var isAutoTerminate: Bool {
+        get {
+            !isKeepRunningAfterLastWindowClosed
+        }
+        
+        set {
+            isKeepRunningAfterLastWindowClosed = !newValue
+        }
+    }
+    
     func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
         !isKeepRunningAfterLastWindowClosed && !hasRunningVirtualMachines
     }
@@ -104,6 +115,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
     func application(_ sender: NSApplication, delegateHandlesKey key: String) -> Bool {
         switch key {
         case "scriptingVirtualMachines": return true
+        case "isAutoTerminate": return true
         default: return false
         }
     }

+ 31 - 0
Platform/macOS/UTMPatches.swift

@@ -85,6 +85,18 @@ extension NSApplication {
         }
     }
     
+    /// Set KVO value in scripting delegate if implemented
+    /// - Parameters:
+    ///   - value: Value to set to
+    ///   - key: Key to look up
+    @objc dynamic func xxx_setValue(_ value: Any?, forKey key: String) {
+        if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
+            return (scriptingDelegate as! NSObject).setValue(value, forKey: key)
+        } else {
+            return xxx_setValue(value, forKey: key)
+        }
+    }
+    
     /// Get KVO value from scripting delegate if implemented
     /// - Parameters:
     ///   - index: Index of item
@@ -98,6 +110,19 @@ extension NSApplication {
         }
     }
     
+    /// Set KVO value in scripting delegate if implemented
+    /// - Parameters:
+    ///   - index: Index of item
+    ///   - key: Key to look up
+    ///   - value: Value to set item to
+    @objc dynamic func xxx_replaceValue(at index: Int, inPropertyWithKey key: String, withValue value: Any) {
+        if scriptingDelegate?.application?(self, delegateHandlesKey: key) ?? false {
+            return (scriptingDelegate as! NSObject).replaceValue(at: index, inPropertyWithKey: key, withValue: value)
+        } else {
+            return xxx_replaceValue(at: index, inPropertyWithKey: key, withValue: value)
+        }
+    }
+    
     fileprivate static func patchApplicationScripting() {
         patch(#selector(Self.value(forKey:)),
               with: #selector(Self.xxx_value(forKey:)),
@@ -105,5 +130,11 @@ extension NSApplication {
         patch(#selector(Self.value(at:inPropertyWithKey:)),
               with: #selector(Self.xxx_value(at:inPropertyWithKey:)),
               class: Self.self)
+        patch(#selector(Self.setValue(_:forKey:)),
+              with: #selector(Self.xxx_setValue(_:forKey:)),
+              class: Self.self)
+        patch(#selector(Self.replaceValue(at:inPropertyWithKey:withValue:)),
+              with: #selector(Self.xxx_replaceValue(at:inPropertyWithKey:withValue:)),
+              class: Self.self)
     }
 }

+ 10 - 0
Scripting/UTM.sdef

@@ -4,6 +4,7 @@
 <dictionary title="UTM Terminology">
 
     <suite name="Standard Suite" code="????" description="Common classes and commands for all applications.">
+        <access-group identifier="com.utmapp.UTM.vm-access" />
 
         <enumeration name="printing error handling" code="enum">
             <enumerator name="standard" code="lwst" description="Standard PostScript error handling">
@@ -13,6 +14,12 @@
                 <cocoa boolean-value="YES"/>
             </enumerator>
         </enumeration>
+        
+        <command name="close" code="coreclos" description="Close a document.">
+            <cocoa class="NSCloseCommand"/>
+            <access-group identifier="*"/>
+            <direct-parameter type="specifier" requires-access="r" description="the document(s) or window(s) to close."/>
+        </command>
 
         <command name="quit" code="aevtquit" description="Quit the application.">
             <cocoa class="NSQuitCommand"/>
@@ -99,6 +106,9 @@
           <element type="virtual machine" access="r">
             <cocoa key="scriptingVirtualMachines"/>
           </element>
+          <property name="auto terminate" code="kRlW" type="boolean" description="Auto terminate the application when all windows are closed?">
+              <cocoa key="isAutoTerminate"/>
+          </property>
         </class-extension>
         
         <enumeration name="backend" code="VmEb" description="Backend type.">

+ 10 - 4
Scripting/UTMScripting.swift

@@ -74,21 +74,27 @@ import ScriptingBridge
     case unavailable = 0x49556e41 /* 'IUnA' */
 }
 
+// MARK: UTMScriptingGenericMethods
+@objc public protocol UTMScriptingGenericMethods {
+    @objc optional func close() // Close a document.
+}
+
 // MARK: UTMScriptingApplication
 @objc public protocol UTMScriptingApplication: SBApplicationProtocol {
     @objc optional func windows() -> SBElementArray
     @objc optional var name: String { get } // The name of the application.
     @objc optional var frontmost: Bool { get } // Is this the active application?
     @objc optional var version: String { get } // The version number of the application.
-    @objc optional func `open`(_ x: Any!) -> Any // Open a document.
     @objc optional func quit() // Quit the application.
     @objc optional func exists(_ x: Any!) -> Bool // Verify that an object exists.
     @objc optional func virtualMachines() -> SBElementArray
+    @objc optional var autoTerminate: Bool { get } // Auto terminate the application when all windows are closed?
+    @objc optional func setAutoTerminate(_ autoTerminate: Bool) // Auto terminate the application when all windows are closed?
 }
 extension SBApplication: UTMScriptingApplication {}
 
 // MARK: UTMScriptingWindow
-@objc public protocol UTMScriptingWindow: SBObjectProtocol {
+@objc public protocol UTMScriptingWindow: SBObjectProtocol, UTMScriptingGenericMethods {
     @objc optional var name: String { get } // The title of the window.
     @objc optional func id() -> Int // The unique identifier of the window.
     @objc optional var index: Int { get } // The index of the window, ordered front to back.
@@ -109,7 +115,7 @@ extension SBApplication: UTMScriptingApplication {}
 extension SBObject: UTMScriptingWindow {}
 
 // MARK: UTMScriptingVirtualMachine
-@objc public protocol UTMScriptingVirtualMachine: SBObjectProtocol {
+@objc public protocol UTMScriptingVirtualMachine: SBObjectProtocol, UTMScriptingGenericMethods {
     @objc optional func serialPorts() -> SBElementArray
     @objc optional func id() -> String // The unique identifier of the VM.
     @objc optional var name: String { get } // The name of the VM.
@@ -126,7 +132,7 @@ extension SBObject: UTMScriptingWindow {}
 extension SBObject: UTMScriptingVirtualMachine {}
 
 // MARK: UTMScriptingSerialPort
-@objc public protocol UTMScriptingSerialPort: SBObjectProtocol {
+@objc public protocol UTMScriptingSerialPort: SBObjectProtocol, UTMScriptingGenericMethods {
     @objc optional func id() -> Int // The unique identifier of the tag.
     @objc optional var interface: UTMScriptingSerialInterface { get } // The type of serial interface on the host.
     @objc optional var address: String { get } // Host address of the serial port (determined by the interface type).

+ 19 - 3
utmctl/UTMCtl.swift

@@ -38,11 +38,23 @@ protocol UTMAPICommand: ParsableCommand {
 extension UTMAPICommand {
     /// Entry point for all subcommands
     func run() throws {
-        guard let utmApp = SBApplication(url: utmAppUrl) else {
+        guard let app = SBApplication(url: utmAppUrl) else {
             throw UTMCtl.APIError.applicationNotFound
         }
-        utmApp.launchFlags = [.defaults, .andHide]
-        utmApp.delegate = UTMCtl.EventErrorHandler.shared
+        app.launchFlags = [.defaults, .andHide]
+        app.delegate = UTMCtl.EventErrorHandler.shared
+        let utmApp = app as UTMScriptingApplication
+        if environment.hide {
+            utmApp.setAutoTerminate!(false)
+            if let windows = utmApp.windows!() as? [UTMScriptingWindow] {
+                for window in windows {
+                    if window.name == "UTM" {
+                        window.close!()
+                        break
+                    }
+                }
+            }
+        }
         try run(with: utmApp)
     }
     
@@ -114,6 +126,7 @@ fileprivate extension UTMScriptingStatus {
         case .paused: return "paused"
         case .resuming: return "resuming"
         case .stopping: return "stopping"
+        @unknown default: return "unknown"
         }
     }
 }
@@ -321,6 +334,9 @@ extension UTMCtl {
     struct EnvironmentOptions: ParsableArguments {
         @Flag(name: .shortAndLong, help: "Show debug logging.")
         var debug: Bool = false
+        
+        @Flag(help: "Hide the main UTM window.")
+        var hide: Bool = false
     }
 }