Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
osy authored May 24, 2024
2 parents 5b84f4b + 9dc2e94 commit e63b53a
Show file tree
Hide file tree
Showing 85 changed files with 3,882 additions and 1,313 deletions.
10 changes: 4 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ on:
default: 'false'

env:
BUILD_XCODE_PATH: /Applications/Xcode_15.2.app
RUNNER_IMAGE: macos-13
BUILD_XCODE_PATH: /Applications/Xcode_15.3.app
RUNNER_IMAGE: macos-14

jobs:
configuration:
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
if: steps.cache-sysroot.outputs.cache-hit != 'true' || github.event.inputs.rebuild_sysroot == 'true'
run: ./scripts/build_dependencies.sh -p ${{ matrix.platform }} -a ${{ matrix.arch }}
env:
NCPU: ${{ endsWith(matrix.platform, '-tci') && '2' || '0' }} # limit 2 CPU for TCI build due to memory issues, 0 = unlimited for other builds
NCPU: ${{ endsWith(matrix.platform, '-tci') && '1' || '0' }} # limit 1 CPU for TCI build due to memory issues, 0 = unlimited for other builds
- name: Compress Sysroot
if: steps.cache-sysroot.outputs.cache-hit != 'true' || github.event_name == 'release' || github.event.inputs.test_release == 'true'
run: tar -acf sysroot.tgz sysroot*
Expand Down Expand Up @@ -330,9 +330,7 @@ jobs:
LAUNCHER_PROFILE_DATA: ${{ vars.LAUNCHER_PROFILE_DATA }}
LAUNCHER_PROFILE_UUID: ${{ vars.LAUNCHER_PROFILE_UUID }}
- name: Install appdmg
run: |
python3 -m pip install setuptools
npm install -g appdmg
run: npm install -g appdmg
- name: Download Artifact
uses: actions/download-artifact@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions Build.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974

MARKETING_VERSION = 4.5.0
CURRENT_PROJECT_VERSION = 95
MARKETING_VERSION = 4.5.2
CURRENT_PROJECT_VERSION = 97

// Codesigning settings defined optionally, see Documentation/iOSDevelopment.md
#include? "CodeSigning.xcconfig"
Expand Down
31 changes: 30 additions & 1 deletion Configuration/QEMUConstant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import Metal
protocol QEMUConstant: Codable, RawRepresentable, CaseIterable where RawValue == String, AllCases == [Self] {
static var allRawValues: [String] { get }
static var allPrettyValues: [String] { get }
static var shownPrettyValues: [String] { get }
var prettyValue: String { get }
var rawValue: String { get }

var isHidden: Bool { get }

init?(rawValue: String)
}

Expand All @@ -37,6 +39,14 @@ extension QEMUConstant where Self: CaseIterable, AllCases == [Self] {
static var allPrettyValues: [String] {
allCases.map { value in value.prettyValue }
}

static var shownPrettyValues: [String] {
allCases.compactMap { value in value.isHidden ? nil : value.prettyValue }
}

var isHidden: Bool {
false
}
}

extension QEMUConstant where Self: RawRepresentable, RawValue == String {
Expand Down Expand Up @@ -478,3 +488,22 @@ extension QEMUTarget {
}
}
}

#if WITH_QEMU_TCI
/// TCI build has a reduced set of supported architectures due to size of binaries.
extension QEMUArchitecture {
var isHidden: Bool {
switch self {
case .arm: return false
case .aarch64: return false
case .i386: return false
case .ppc: return false
case .ppc64: return false
case .riscv32: return false
case .riscv64: return false
case .x86_64: return false
default: return true
}
}
}
#endif
7 changes: 6 additions & 1 deletion Configuration/UTMAppleConfigurationDisplay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ struct UTMAppleConfigurationDisplay: Codable, Identifiable {
var heightInPixels: Int = 1200

var pixelsPerInch: Int = 80


var isDynamicResolution: Bool = true

let id = UUID()

enum CodingKeys: String, CodingKey {
case widthInPixels = "WidthPixels"
case heightInPixels = "HeightPixels"
case pixelsPerInch = "PixelsPerInch"
case isDynamicResolution = "DynamicResolution"
}

init() {
Expand All @@ -49,13 +52,15 @@ struct UTMAppleConfigurationDisplay: Codable, Identifiable {
widthInPixels = try values.decode(Int.self, forKey: .widthInPixels)
heightInPixels = try values.decode(Int.self, forKey: .heightInPixels)
pixelsPerInch = try values.decode(Int.self, forKey: .pixelsPerInch)
isDynamicResolution = try values.decodeIfPresent(Bool.self, forKey: .isDynamicResolution) ?? true
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(widthInPixels, forKey: .widthInPixels)
try container.encode(heightInPixels, forKey: .heightInPixels)
try container.encode(pixelsPerInch, forKey: .pixelsPerInch)
try container.encode(isDynamicResolution, forKey: .isDynamicResolution)
}

#if arch(arm64)
Expand Down
14 changes: 13 additions & 1 deletion Configuration/UTMQemuConfiguration+Arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ import Virtualization // for getting network interfaces
socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("crt")
}

/// Used for placeholder images
var placeholderUrl: URL {
#if os(macOS)
URL(fileURLWithPath: "/dev/null")
#else
let empty = FileManager.default.temporaryDirectory.appendingPathComponent("empty")
FileManager.default.createFile(atPath: empty.path, contents: nil)
return empty
#endif
}

/// Combined generated and user specified arguments.
@QEMUArgumentBuilder var allArguments: [QEMUArgument] {
generatedArguments
Expand Down Expand Up @@ -725,7 +736,8 @@ import Virtualization // for getting network interfaces
"file="
imageURL
} else if !isCd {
"file=/dev/null"
"file="
placeholderUrl
}
if drive.isReadOnly || isCd {
"readonly=on"
Expand Down
27 changes: 26 additions & 1 deletion Platform/Shared/BusyIndicator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,38 @@
import SwiftUI

struct BusyIndicator: View {
@Binding var progress: Float?

init(progress: Binding<Float?> = .constant(nil)) {
_progress = progress
}

var body: some View {
Spinner(size: .large)
progressView
.frame(width: 100, height: 100, alignment: .center)
.foregroundColor(.white)
.background(Color.gray.opacity(0.5))
.clipShape(RoundedRectangle(cornerRadius: 25.0, style: .continuous))
}

#if os(macOS)
@ViewBuilder
private var progressView: some View {
if let progress = progress {
ProgressView(value: progress)
.progressViewStyle(.circular)
.controlSize(.large)
} else {
Spinner(size: .large)
}
}
#else
// TODO: implement progress spinner for iOS
@ViewBuilder
private var progressView: some View {
Spinner(size: .large)
}
#endif
}

struct BusyIndicator_Previews: PreviewProvider {
Expand Down
2 changes: 1 addition & 1 deletion Platform/Shared/BusyOverlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct BusyOverlay: View {
var body: some View {
Group {
if data.busy {
BusyIndicator()
BusyIndicator(progress: $data.busyProgress)
} else {
EmptyView()
}
Expand Down
102 changes: 32 additions & 70 deletions Platform/Shared/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ContentView: View {
@StateObject private var releaseHelper = UTMReleaseHelper()
@State private var newPopupPresented = false
@State private var openSheetPresented = false
@State private var alertItem: AlertItem?
@Environment(\.openURL) var openURL
@AppStorage("ServerAutostart") private var isServerAutostart: Bool = false

Expand All @@ -50,6 +51,14 @@ struct ContentView: View {
}, content: {
VMReleaseNotesView(helper: releaseHelper).padding()
})
.alert(item: $alertItem) { item in
switch item {
case .downloadUrl(let url):
return Alert(title: Text("Download VM"), message: Text("Do you want to download '\(url)'?"), primaryButton: .cancel(), secondaryButton: .default(Text("Download")) {
data.downloadUTMZip(from: url)
})
}
}
.onReceive(NSNotification.ShowReleaseNotes) { _ in
Task {
await releaseHelper.fetchReleaseNotes(force: true)
Expand Down Expand Up @@ -115,13 +124,17 @@ struct ContentView: View {
}

private func handleURL(url: URL) {
data.busyWorkAsync {
if url.isFileURL {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
components.scheme?.lowercased() == "utm",
components.host == "downloadVM",
let urlParameter = components.queryItems?.first(where: { $0.name == "url" })?.value,
let url = URL(string: urlParameter) {
if alertItem == nil {
alertItem = .downloadUrl(url)
}
} else if url.isFileURL {
data.busyWorkAsync {
try await importUTM(url: url)
} else if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let scheme = components.scheme,
scheme.lowercased() == "utm" {
try await handleUTMURL(with: components)
}
}
}
Expand All @@ -141,70 +154,6 @@ struct ContentView: View {
}
}
}

@MainActor private func handleUTMURL(with components: URLComponents) async throws {
func findVM() -> VMData? {
if let vmName = components.queryItems?.first(where: { $0.name == "name" })?.value {
return data.virtualMachines.first(where: { $0.detailsTitleLabel == vmName })
} else {
return nil
}
}

if let action = components.host {
switch action {
case "start":
if let vm = findVM(), vm.state == .stopped {
data.run(vm: vm)
}
break
case "stop":
if let vm = findVM(), vm.state == .started {
try await vm.wrapped!.stop(usingMethod: .force)
data.stop(vm: vm)
}
break
case "restart":
if let vm = findVM(), vm.state == .started {
try await vm.wrapped!.restart()
}
break
case "pause":
if let vm = findVM(), vm.state == .started {
let shouldSaveOnPause: Bool
if let vm = vm.wrapped as? (any UTMSpiceVirtualMachine) {
shouldSaveOnPause = !vm.isRunningAsDisposible
} else {
shouldSaveOnPause = true
}
try await vm.wrapped!.pause()
if shouldSaveOnPause {
try? await vm.wrapped!.saveSnapshot(name: nil)
}
}
case "resume":
if let vm = findVM(), vm.state == .paused {
try await vm.wrapped!.resume()
}
break
case "sendText":
if let vm = findVM(), vm.state == .started {
data.automationSendText(to: vm, urlComponents: components)
}
break
case "click":
if let vm = findVM(), vm.state == .started {
data.automationSendMouse(to: vm, urlComponents: components)
}
break
case "downloadVM":
await data.downloadUTMZip(from: components)
break
default:
return
}
}
}
}

extension ContentView: DropDelegate {
Expand Down Expand Up @@ -246,6 +195,19 @@ extension ContentView: DropDelegate {
}
}

extension ContentView {
private enum AlertItem: Identifiable {
case downloadUrl(URL)

var id: Int {
switch self {
case .downloadUrl(let url):
return url.hashValue
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
Expand Down
2 changes: 1 addition & 1 deletion Platform/Shared/VMConfigConstantPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct VMConfigConstantPicker: View {

var body: some View {
Picker(titleKey ?? "", selection: $selection) {
ForEach(type.allPrettyValues) { displayValue in
ForEach(type.shownPrettyValues) { displayValue in
Text(displayValue).tag(identifier(for: displayValue))
}
}
Expand Down
Loading

0 comments on commit e63b53a

Please sign in to comment.