diff --git a/LICENSE b/LICENSE index 5d1758c..fbfea65 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2022 Jahn Bertsch +Copyright 2022-2023 Jahn Bertsch Copyright 2021 Khaos Tian Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/virtualOS.xcodeproj/project.pbxproj b/virtualOS.xcodeproj/project.pbxproj index fcaf751..9997254 100644 --- a/virtualOS.xcodeproj/project.pbxproj +++ b/virtualOS.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C9927E238930048776B /* VirtualMacConfiguration.swift */; }; 00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */; }; 0114C02629AA2416004159AF /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0114C02529AA2416004159AF /* MenuCommands.swift */; }; + 01FCAD8429AB707C00F12689 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -68,6 +69,7 @@ 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 00BA26AC2826DAF200E80B76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 0114C02529AA2416004159AF /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = ""; }; + 01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -169,6 +171,7 @@ 00989C9127E236A10048776B /* Model */ = { isa = PBXGroup; children = ( + 01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */, 0044A65427F601E60007988A /* MainViewModel.swift */, 007987B027E24A8400960D74 /* VirtualMac.swift */, 00989C9927E238930048776B /* VirtualMacConfiguration.swift */, @@ -323,6 +326,7 @@ 00989C9627E236A10048776B /* MainView.swift in Sources */, 0005A77A27E2809E0013BE83 /* VirtualMachineView.swift in Sources */, 00989C6427E2340C0048776B /* virtualOSApp.swift in Sources */, + 01FCAD8429AB707C00F12689 /* ApplicationDelegate.swift in Sources */, 007987B127E24A8400960D74 /* VirtualMac.swift in Sources */, 0044A65A27F76BD30007988A /* URL+Paths.swift in Sources */, 00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */, @@ -492,7 +496,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\""; DEVELOPMENT_TEAM = 2AD47BTDQ6; @@ -507,7 +511,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -525,7 +529,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\""; DEVELOPMENT_TEAM = 2AD47BTDQ6; @@ -540,7 +544,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/virtualOS/Model/ApplicationDelegate.swift b/virtualOS/Model/ApplicationDelegate.swift new file mode 100644 index 0000000..7c0c122 --- /dev/null +++ b/virtualOS/Model/ApplicationDelegate.swift @@ -0,0 +1,14 @@ +// +// ApplicationDelegate.swift +// virtualOS +// +// Created by Jahn Bertsch on 26.02.23. +// + +import AppKit + +class ApplicationDelegate: NSObject, NSApplicationDelegate { + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/virtualOS/Model/MainViewModel.swift b/virtualOS/Model/MainViewModel.swift index 81ba0ea..d838a88 100644 --- a/virtualOS/Model/MainViewModel.swift +++ b/virtualOS/Model/MainViewModel.swift @@ -28,6 +28,8 @@ final class MainViewModel: NSObject, ObservableObject { @Published var showLicenseInformationModal = false @Published var showConfirmationAlert = false @Published var showSettings = false + @Published var isFullScreen = false + @Published var useMainScreenSize = true @Published var licenseInformationTitleString = "" @Published var licenseInformationString = "" @Published var confirmationText = "" @@ -72,7 +74,7 @@ final class MainViewModel: NSObject, ObservableObject { moveFilesAfterUpdate() } - func buttonPressed() { + func statusButtonPressed() { switch state { case .Stopped: start() diff --git a/virtualOS/Model/VirtualMac.swift b/virtualOS/Model/VirtualMac.swift index ed3d3ae..07e2691 100644 --- a/virtualOS/Model/VirtualMac.swift +++ b/virtualOS/Model/VirtualMac.swift @@ -12,6 +12,23 @@ import Combine final class VirtualMac: ObservableObject { struct Parameters: Codable { + init() {} + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + cpuCount = try container.decode(Int.self, forKey: .cpuCount) + cpuCountMin = try container.decode(Int.self, forKey: .cpuCountMin) + cpuCountMax = try container.decode(Int.self, forKey: .cpuCountMax) + diskSizeInGB = try container.decode(UInt64.self, forKey: .diskSizeInGB) + memorySizeInGB = try container.decode(UInt64.self, forKey: .memorySizeInGB) + memorySizeInGBMin = try container.decode(UInt64.self, forKey: .memorySizeInGBMin) + memorySizeInGBMax = try container.decode(UInt64.self, forKey: .memorySizeInGBMax) + useMainScreenSize = try container.decodeIfPresent(Bool.self, forKey: .useMainScreenSize) ?? false // optional + screenWidth = try container.decode(Int.self, forKey: .screenWidth) + screenHeight = try container.decode(Int.self, forKey: .screenHeight) + pixelsPerInch = try container.decode(Int.self, forKey: .pixelsPerInch) + microphoneEnabled = try container.decode(Bool.self, forKey: .microphoneEnabled) + } + var cpuCount = 1 var cpuCountMin = 1 var cpuCountMax = 2 @@ -19,6 +36,7 @@ final class VirtualMac: ObservableObject { var memorySizeInGB: UInt64 = 1 var memorySizeInGBMin: UInt64 = 1 var memorySizeInGBMax: UInt64 = 2 + var useMainScreenSize = false var screenWidth = 1500 var screenHeight = 900 var pixelsPerInch = 250 diff --git a/virtualOS/Model/VirtualMacConfiguration.swift b/virtualOS/Model/VirtualMacConfiguration.swift index b54955a..54b28d3 100644 --- a/virtualOS/Model/VirtualMacConfiguration.swift +++ b/virtualOS/Model/VirtualMacConfiguration.swift @@ -118,11 +118,15 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { fileprivate func configureGraphicsDevice(parameters: VirtualMac.Parameters) { let graphicsDevice = VZMacGraphicsDeviceConfiguration() - graphicsDevice.displays = [VZMacGraphicsDisplayConfiguration( - widthInPixels: parameters.screenWidth, - heightInPixels: parameters.screenHeight, - pixelsPerInch: parameters.pixelsPerInch - )] + if parameters.useMainScreenSize, let mainScreen = NSScreen.main { + graphicsDevice.displays = [VZMacGraphicsDisplayConfiguration(for: mainScreen, sizeInPoints: NSSize(width: parameters.screenWidth, height: parameters.screenHeight))] + } else { + graphicsDevice.displays = [VZMacGraphicsDisplayConfiguration( + widthInPixels: parameters.screenWidth, + heightInPixels: parameters.screenHeight, + pixelsPerInch: parameters.pixelsPerInch + )] + } graphicsDevices = [graphicsDevice] } diff --git a/virtualOS/View/ConfigurationView.swift b/virtualOS/View/ConfigurationView.swift index 2639e8f..997bbe3 100644 --- a/virtualOS/View/ConfigurationView.swift +++ b/virtualOS/View/ConfigurationView.swift @@ -32,6 +32,8 @@ struct ConfigurationView: View { viewModel.virtualMac.parameters.screenHeight = Int(screenHeightValue) } } + @State fileprivate var useCustomScreenSize = true + @State fileprivate var useMainScreenSize = false var body: some View { VStack { @@ -57,6 +59,22 @@ struct ConfigurationView: View { Text("RAM: \(viewModel.virtualMac.parameters.memorySizeInGB) GB") .frame(minWidth: sliderTextWidth, alignment: .leading) } + + HStack() { + Text("Screen Size:") + Spacer() + Toggle("Custom", isOn: $useCustomScreenSize).onChange(of: useCustomScreenSize) { newValue in + useMainScreenSize = !newValue + } + Toggle("Main Screen", isOn: $useMainScreenSize).onChange(of: useMainScreenSize) { newValue in + useCustomScreenSize = !newValue + viewModel.virtualMac.parameters.useMainScreenSize = newValue + if let mainScreen = NSScreen.main { + screenWidthValue = Float(mainScreen.frame.width) + screenHeightValue = Float(mainScreen.frame.height) + } + } + } Slider(value: Binding(get: { screenWidthValue @@ -65,7 +83,7 @@ struct ConfigurationView: View { }), in: 800 ... Float(NSScreen.main?.frame.width ?? CGFloat(parameters.screenWidth)), step: 100) { Text("Screen Width: \(viewModel.virtualMac.parameters.screenWidth) px") .frame(minWidth: sliderTextWidth, alignment: .leading) - } + }.disabled(useMainScreenSize) Slider(value: Binding(get: { screenHeightValue @@ -74,11 +92,11 @@ struct ConfigurationView: View { }), in: 600 ... Float(NSScreen.main?.frame.height ?? CGFloat(parameters.screenHeight)), step: 50) { Text("Screen Height: \(viewModel.virtualMac.parameters.screenHeight) px") .frame(minWidth: sliderTextWidth, alignment: .leading) - } + }.disabled(useMainScreenSize) } .padding() .overlay { - RoundedRectangle(cornerRadius: 10) + RoundedRectangle(cornerRadius: 5) .stroke(.tertiary, lineWidth: 1) } @@ -90,6 +108,7 @@ struct ConfigurationView: View { let parameters = viewModel.virtualMac.parameters cpuCountSliderValue = Float(parameters.cpuCount) memorySliderValue = Float(parameters.memorySizeInGB) + useMainScreenSize = parameters.useMainScreenSize screenWidthValue = Float(parameters.screenWidth) screenHeightValue = Float(parameters.screenHeight) } diff --git a/virtualOS/View/MainView.swift b/virtualOS/View/MainView.swift index ffe3f72..92f7cab 100644 --- a/virtualOS/View/MainView.swift +++ b/virtualOS/View/MainView.swift @@ -14,13 +14,13 @@ struct MainView: View { var body: some View { VStack { - Spacer() if viewModel.showStatusBar { + Spacer() HStack { Text(viewModel.statusLabel) Spacer() Button { - viewModel.buttonPressed() + viewModel.statusButtonPressed() } label: { Text(viewModel.statusButtonLabel) }.disabled(viewModel.statusButtonDisabled) diff --git a/virtualOS/View/MenuCommands.swift b/virtualOS/View/MenuCommands.swift index cca09e3..8fd34f1 100644 --- a/virtualOS/View/MenuCommands.swift +++ b/virtualOS/View/MenuCommands.swift @@ -7,11 +7,11 @@ import SwiftUI +#if arch(arm64) + struct MenuCommands: Commands { @ObservedObject var viewModel: MainViewModel - #if arch(arm64) - var body: some Commands { CommandGroup(replacing: .appInfo) { Button("About virtualOS") { @@ -23,8 +23,11 @@ struct MenuCommands: Commands { viewModel.showSettings = !viewModel.showSettings }.keyboardShortcut(",") } - CommandGroup(replacing: .newItem) {} - CommandGroup(after: .newItem) { + CommandGroup(replacing: .newItem) { + Button(viewModel.statusButtonLabel) { + viewModel.statusButtonPressed() + }.keyboardShortcut("R") + Divider() Button("Delete Restore Image") { viewModel.deleteRestoreImage() }.disabled(!MainViewModel.restoreImageExists) @@ -38,14 +41,15 @@ struct MenuCommands: Commands { viewModel.showStatusBar = !viewModel.showStatusBar }.keyboardShortcut("B") Divider() - Button("Enter Full Screen") { + Button(String(format: "%@ Full Screen", viewModel.isFullScreen ? "Exit" : "Enter")) { if let window = NSApplication.shared.windows.first { + viewModel.isFullScreen = !viewModel.isFullScreen + viewModel.showStatusBar = !viewModel.isFullScreen window.toggleFullScreen(nil) } - }.keyboardShortcut("F", modifiers: [.command, .option]) + }.keyboardShortcut("F") } - } - - #endif // #if arch(arm64) + } } +#endif // #if arch(arm64) diff --git a/virtualOS/virtualOSApp.swift b/virtualOS/virtualOSApp.swift index fc4250e..181e874 100644 --- a/virtualOS/virtualOSApp.swift +++ b/virtualOS/virtualOSApp.swift @@ -18,11 +18,13 @@ struct virtualOSApp: App { #if arch(arm64) @StateObject var viewModel = MainViewModel() - @AppStorage("NSFullScreenMenuItemEverywhere") var fullScreenItemEverywhere = false #endif + @AppStorage("NSFullScreenMenuItemEverywhere") var fullScreenMenuItemEverywhere = false + @NSApplicationDelegateAdaptor(ApplicationDelegate.self) var applicationDelegate + init() { - fullScreenItemEverywhere = false + fullScreenMenuItemEverywhere = false NSWindow.allowsAutomaticWindowTabbing = false } @@ -57,7 +59,9 @@ struct virtualOSApp: App { #endif // #if arch(arm64) } .commands { + #if arch(arm64) MenuCommands(viewModel: viewModel) + #endif } }