diff --git a/Sources/tart/Commands/Get.swift b/Sources/tart/Commands/Get.swift index 7ffada6..51bca0c 100644 --- a/Sources/tart/Commands/Get.swift +++ b/Sources/tart/Commands/Get.swift @@ -9,6 +9,7 @@ fileprivate struct VMInfo: Encodable { let DiskFormat: String let Size: String let Display: String + let HideTitleBar: Bool let Running: Bool let State: String } @@ -27,7 +28,7 @@ struct Get: AsyncParsableCommand { let vmConfig = try VMConfig(fromURL: vmDir.configURL) let memorySizeInMb = vmConfig.memorySize / 1024 / 1024 - let info = VMInfo(OS: vmConfig.os, CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: try vmDir.sizeGB(), DiskFormat: vmConfig.diskFormat.rawValue, Size: String(format: "%.3f", Float(try vmDir.allocatedSizeBytes()) / 1000 / 1000 / 1000), Display: vmConfig.display.description, Running: try vmDir.running(), State: try vmDir.state().rawValue) + let info = VMInfo(OS: vmConfig.os, CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: try vmDir.sizeGB(), DiskFormat: vmConfig.diskFormat.rawValue, Size: String(format: "%.3f", Float(try vmDir.allocatedSizeBytes()) / 1000 / 1000 / 1000), Display: vmConfig.display.description, HideTitleBar: vmConfig.hideTitleBar, Running: try vmDir.running(), State: try vmDir.state().rawValue) print(format.renderSingle(info)) } } diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index fd7a851..52437f4 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -12,6 +12,21 @@ var vm: VM? struct IPNotFound: Error { } +extension View { + /// Apply `transform` only if `condition` is true, otherwise leave self unchanged. + @ViewBuilder + func conditional( + _ condition: Bool, + _ transform: (Self) -> Content + ) -> some View { + if condition { + transform(self) + } else { + self + } + } +} + @available(macOS 14, *) extension VZDiskSynchronizationMode { public init(_ description: String) throws { @@ -718,16 +733,12 @@ struct Run: AsyncParsableCommand { private func runUI(_ suspendable: Bool, _ captureSystemKeys: Bool) { MainApp.suspendable = suspendable MainApp.capturesSystemKeys = captureSystemKeys + MainApp.hideTitleBar = vm!.config.hideTitleBar MainApp.main() } } -struct MainApp: App { - static var suspendable: Bool = false - static var capturesSystemKeys: Bool = false - - @NSApplicationDelegateAdaptor private var appDelegate: AppDelegate - +struct CommonScene: Scene { var body: some Scene { WindowGroup(vm!.name) { Group { @@ -750,6 +761,7 @@ struct MainApp: App { idealHeight: CGFloat(vm!.config.display.height), maxHeight: .infinity ) + .conditional(MainApp.hideTitleBar) { $0.ignoresSafeArea() } }.commands { // Remove some standard menu options CommandGroup(replacing: .help, addition: {}) @@ -782,6 +794,38 @@ struct MainApp: App { } } +struct HideTitleBarApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + + var body: some Scene { + CommonScene() + .windowStyle(.hiddenTitleBar) + } +} + +struct ShowTitleBarApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + + var body: some Scene { + CommonScene() + } +} + + +struct MainApp { + static var suspendable: Bool = false + static var capturesSystemKeys: Bool = false + static var hideTitleBar: Bool = false + + static func main() { + if hideTitleBar { + HideTitleBarApp.main() + } else { + ShowTitleBarApp.main() + } + } +} + class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { if (kill(getpid(), MainApp.suspendable ? SIGUSR1 : SIGINT) == 0) { diff --git a/Sources/tart/Commands/Set.swift b/Sources/tart/Commands/Set.swift index fbb55d6..a0e7c4b 100644 --- a/Sources/tart/Commands/Set.swift +++ b/Sources/tart/Commands/Set.swift @@ -37,6 +37,9 @@ struct Set: AsyncParsableCommand { """)) var diskSize: UInt16? + @Flag(inversion: .prefixedNo, help: ArgumentHelp("Whether to hide the title bar and ignore safe area for fullscreen style")) + var hideTitleBar: Bool? = nil + func run() async throws { let vmDir = try VMStorageLocal().open(name) var vmConfig = try VMConfig(fromURL: vmDir.configURL) @@ -60,6 +63,10 @@ struct Set: AsyncParsableCommand { vmConfig.displayRefit = displayRefit + if let hideTitleBar = hideTitleBar { + vmConfig.hideTitleBar = hideTitleBar + } + if randomMAC { vmConfig.macAddress = VZMACAddress.randomLocallyAdministered() } diff --git a/Sources/tart/VMConfig.swift b/Sources/tart/VMConfig.swift index 198cb24..f3460ee 100644 --- a/Sources/tart/VMConfig.swift +++ b/Sources/tart/VMConfig.swift @@ -26,6 +26,7 @@ enum CodingKeys: String, CodingKey { case display case displayRefit case diskFormat + case hideTitleBar // macOS-specific keys case ecid @@ -56,6 +57,7 @@ struct VMConfig: Codable { var display: VMDisplayConfig = VMDisplayConfig() var displayRefit: Bool? var diskFormat: DiskImageFormat = .raw + var hideTitleBar: Bool = false init( platform: Platform, @@ -130,6 +132,7 @@ struct VMConfig: Codable { displayRefit = try container.decodeIfPresent(Bool.self, forKey: .displayRefit) let diskFormatString = try container.decodeIfPresent(String.self, forKey: .diskFormat) ?? "raw" diskFormat = DiskImageFormat(rawValue: diskFormatString) ?? .raw + hideTitleBar = try container.decodeIfPresent(Bool.self, forKey: .hideTitleBar) ?? false } func encode(to encoder: Encoder) throws { @@ -149,6 +152,7 @@ struct VMConfig: Codable { try container.encode(displayRefit, forKey: .displayRefit) } try container.encode(diskFormat.rawValue, forKey: .diskFormat) + try container.encode(hideTitleBar, forKey: .hideTitleBar) } mutating func setCPU(cpuCount: Int) throws {