Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Scripts/check_fork_identity.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ readonly app_id="dev.jacobcx.Xcodes"
readonly tests_id="dev.jacobcx.Xcodes.Tests"
readonly helper_id="dev.jacobcx.Xcodes.Helper"
readonly team_id="K2648T24P4"
readonly marketing_version="4.1.1"
readonly build_number="49"
readonly marketing_version="4.1.2"
readonly build_number="50"
readonly app_copyright="Fork contributions © 2026 JacobCXDev. Upstream contributors retain their copyrights."
# shellcheck disable=SC2016 # Xcode expands this build-setting literal, not the shell.
readonly app_requirement='identifier "dev.jacobcx.Xcodes" and info [CFBundleShortVersionString] >= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)"'
Expand Down
24 changes: 14 additions & 10 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; };
CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; };
D10000010000000000000001 /* PinCodeInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D10000020000000000000001 /* PinCodeInputTests.swift */; };
D20000010000000000000001 /* SDKsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20000020000000000000001 /* SDKsViewTests.swift */; };
D30000010000000000000001 /* ProgressButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30000020000000000000001 /* ProgressButtonTests.swift */; };
CA2518EE25A7FF2B00F08414 /* SignInCredentialsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518ED25A7FF2B00F08414 /* SignInCredentialsViewTests.swift */; };
CA378F992466567600A58CE0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA378F982466567600A58CE0 /* AppState.swift */; };
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; };
CA42DD7325AEB04300BC0B0C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA42DD7225AEB04300BC0B0C /* Logger.swift */; };
CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; };
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */; };
Expand Down Expand Up @@ -219,9 +220,10 @@
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = "<group>"; };
CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateUpdateTests.swift; sourceTree = "<group>"; };
D10000020000000000000001 /* PinCodeInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinCodeInputTests.swift; sourceTree = "<group>"; };
D20000020000000000000001 /* SDKsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKsViewTests.swift; sourceTree = "<group>"; };
D30000020000000000000001 /* ProgressButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButtonTests.swift; sourceTree = "<group>"; };
CA2518ED25A7FF2B00F08414 /* SignInCredentialsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInCredentialsViewTests.swift; sourceTree = "<group>"; };
CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = "<group>"; };
CA42DD7225AEB04300BC0B0C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -469,7 +471,6 @@
CABFAA142592F73000380FEE /* XcodeList */ = {
isa = PBXGroup;
children = (
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */,
CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */,
CAFBDC4D2599B33D003DCC5A /* MainToolbar.swift */,
CA44901E2463AD34003D8213 /* Tag.swift */,
Expand Down Expand Up @@ -610,6 +611,8 @@
CAD2E7B72449575100113D76 /* AppStateTests.swift */,
CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */,
D10000020000000000000001 /* PinCodeInputTests.swift */,
D20000020000000000000001 /* SDKsViewTests.swift */,
D30000020000000000000001 /* ProgressButtonTests.swift */,
E10000020000000000000001 /* FileOperationsTests.swift */,
CA2518ED25A7FF2B00F08414 /* SignInCredentialsViewTests.swift */,
884f01aed2f43048ab4d3323 /* NewVersionNotificationTests.swift */,
Expand Down Expand Up @@ -981,7 +984,6 @@
536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */,
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */,
CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */,
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -993,6 +995,8 @@
CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */,
CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */,
D10000010000000000000001 /* PinCodeInputTests.swift in Sources */,
D20000010000000000000001 /* SDKsViewTests.swift in Sources */,
D30000010000000000000001 /* ProgressButtonTests.swift in Sources */,
E10000010000000000000001 /* FileOperationsTests.swift in Sources */,
14d2f5a1273f6c350cad4406 /* NewVersionNotificationTests.swift in Sources */,
CAB3AB0E25BCA6C200BF1B04 /* AppStateTests.swift in Sources */,
Expand Down Expand Up @@ -1090,7 +1094,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 50;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = NO;
Expand All @@ -1102,7 +1106,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 4.1.1;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes;
PRODUCT_NAME = Xcodes;
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -1343,7 +1347,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 50;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1355,7 +1359,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 4.1.1;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes;
PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 6.0;
Expand All @@ -1372,7 +1376,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 50;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = K2648T24P4;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1384,7 +1388,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 4.1.1;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = dev.jacobcx.Xcodes;
PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 6.0;
Expand Down
37 changes: 25 additions & 12 deletions Xcodes/Backend/AppState+Runtimes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,31 @@ extension AppState {
}

func refreshInstalledRuntimes() async throws {
try Task.checkCancellation()
let refreshID = UUID()
installedRuntimesRefreshID = refreshID
isRefreshingInstalledRuntimes = true
installedRuntimesError = nil
defer {
if installedRuntimesRefreshID == refreshID {
installedRuntimesRefreshID = nil
isRefreshingInstalledRuntimes = false
}
}

let runtimes = try await runtimeService.installedRuntimes()
try Task.checkCancellation()
guard installedRuntimesRefreshID == refreshID else { return }
installedRuntimes = runtimes.map(CoreSimulatorImage.init)
do {
let runtimes = try await runtimeService.installedRuntimes()
try Task.checkCancellation()
guard installedRuntimesRefreshID == refreshID else { return }
installedRuntimes = runtimes.map(CoreSimulatorImage.init)
} catch {
if installedRuntimesRefreshID == refreshID,
!Task.isCancelled,
!(error is CancellationError) {
installedRuntimesError = error
}
throw error
}
}

func downloadRuntime(runtime: DownloadableRuntime) {
Expand All @@ -98,14 +111,14 @@ extension AppState {
if let error = error as? RuntimeInstallPolicyError {
switch error {
case .noSelectedXcode:
presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "No selected Xcode. Please make an Xcode active")
presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: localizeString("RuntimeNeedsActiveXcode"))
case .xcode16_1OrGreaterRequired:
presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode16.1"))
presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: localizeString("Alert.Install.Error.Need.Xcode16.1"))
case .xcode26OrGreaterRequired:
presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode26"))
presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: localizeString("Alert.Install.Error.Need.Xcode26"))
}
} else {
presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: error.legibleLocalizedDescription)
}
}

Expand Down Expand Up @@ -151,9 +164,9 @@ extension AppState {
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
self.error = error
if let error = error as? XcodesKitError {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.message)
self.presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: error.message)
} else {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
self.presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: error.legibleLocalizedDescription)
}
}
}
Expand Down Expand Up @@ -191,9 +204,9 @@ extension AppState {
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
self.error = error
if let error = error as? XcodesKitError {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.message)
self.presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: error.message)
} else {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
self.presentedAlert = .generic(title: localizeString("RuntimeInstallError"), message: error.legibleLocalizedDescription)
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ class AppState: ObservableObject {
}
}

var showOpenInRosettaOptionDisabled: Bool { PreferenceKey.showOpenInRosettaOption.isManaged() }

@Published var terminateAfterLastWindowClosed = false {
didSet {
Current.defaults.set(terminateAfterLastWindowClosed, forKey: "terminateAfterLastWindowClosed")
Expand All @@ -378,6 +380,8 @@ class AppState: ObservableObject {

@Published var downloadableRuntimes: [DownloadableRuntime] = []
@Published var installedRuntimes: [CoreSimulatorImage] = []
@Published var isRefreshingInstalledRuntimes = false
@Published var installedRuntimesError: Error?

// MARK: - Operation State

Expand Down Expand Up @@ -483,6 +487,9 @@ class AppState: ObservableObject {
// MARK: Timer
/// Runs a timer every 6 hours when app is open to check if it needs to auto install any xcodes
func setupAutoInstallTimer() {
autoInstallTimer?.invalidate()
autoInstallTimer = nil

guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }

if autoInstallType == .none { return }
Expand Down
6 changes: 4 additions & 2 deletions Xcodes/Backend/XcodeCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ struct CancelRuntimeInstallButton: View {

var body: some View {
Button(action: cancelInstall) {
Image(systemName: "xmark.circle.fill")
}.help(localizeString("StopInstallation"))
Label("StopInstallation", systemImage: "xmark.circle.fill")
}
.labelStyle(.iconOnly)
.help(localizeString("StopInstallation"))
.buttonStyle(.plain)
}

Expand Down
1 change: 1 addition & 0 deletions Xcodes/Frontend/About/ScrollingTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct ScrollingTextView: NSViewRepresentable {
let view = NSTextView.scrollableTextView()
let textView = view.documentView as? NSTextView
textView?.isEditable = false
textView?.textContainerInset = NSSize(width: 16, height: 16)
return view
}

Expand Down
4 changes: 4 additions & 0 deletions Xcodes/Frontend/Common/ProgressButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ struct ProgressButton<Label: View>: View {
)
}
.disabled(isInProgress)
.accessibilityRepresentation {
Button(action: action, label: label)
.disabled(isInProgress)
}
}
}
13 changes: 9 additions & 4 deletions Xcodes/Frontend/Common/View+ErrorHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ protocol ErrorHandler: Sendable {
}

struct AlertErrorHandler: ErrorHandler {
private let title: String
private let id = UUID()

nonisolated init(title: String = "An error occurred") {
self.title = title
}

func handle<T: View>(
_ error: Binding<Error?>,
in view: T,
Expand Down Expand Up @@ -96,8 +101,8 @@ private extension AlertErrorHandler {
switch error.resolveCategory() {
case let .recoverable(recoveryOption):
return Alert(
title: Text("An error occurred"),
message: Text(error.localizedDescription),
title: Text(LocalizedStringKey(title)),
message: Text(error.legibleLocalizedDescription),
primaryButton: .default(Text("Dismiss")),
secondaryButton: .default(
Text(recoveryOption.description),
Expand All @@ -106,8 +111,8 @@ private extension AlertErrorHandler {
)
case .nonRecoverable:
return Alert(
title: Text("An error occurred"),
message: Text(error.localizedDescription),
title: Text(LocalizedStringKey(title)),
message: Text(error.legibleLocalizedDescription),
dismissButton: .default(Text("Dismiss"))
)
case .requiresSignout:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct InstallationStepDetailView: View {

case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
ProgressView()
.scaleEffect(0.5)
.progressViewStyle(.linear)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Xcodes/Frontend/InfoPane/InstalledStateButtons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ struct InstalledStateButtons: View {
HStack {
Text(xcode.installedPath?.string ?? "")
Button(action: { appState.reveal(xcode.installedPath) }) {
Image(systemName: "arrow.right.circle.fill")
Label("RevealInFinder", systemImage: "arrow.right.circle.fill")
}
.labelStyle(.iconOnly)
.buttonStyle(PlainButtonStyle())
.help("RevealInFinder")
}
Expand Down
13 changes: 12 additions & 1 deletion Xcodes/Frontend/InfoPane/PlatformsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ struct PlatformsView: View {
}
}

if runtimes.isEmpty {
Text("NoRuntimesToShow")
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
if appState.downloadableRuntimes.isEmpty {
Button("Refresh") { appState.updateDownloadableRuntimes() }
.frame(maxWidth: .infinity, alignment: .leading)
}
}

ForEach(runtimes, id: \.identifier) { runtime in
runtimeView(runtime: runtime)
.frame(minWidth: 200)
Expand Down Expand Up @@ -118,8 +128,9 @@ struct PlatformsView: View {
func pathIfAvailable(xcode: Xcode, runtime: DownloadableRuntime) -> some View {
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) {
Button(action: { appState.reveal(path: path.string) }) {
Image(systemName: "arrow.right.circle.fill")
Label("RevealInFinder", systemImage: "arrow.right.circle.fill")
}
.labelStyle(.iconOnly)
.buttonStyle(PlainButtonStyle())
.help("RevealInFinder")
} else {
Expand Down
3 changes: 2 additions & 1 deletion Xcodes/Frontend/InfoPane/SDKsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ struct SDKsView: View {
("macOS", sdks.macOS),
("iOS", sdks.iOS),
("watchOS", sdks.watchOS),
("tvOS", sdks.tvOS)
("tvOS", sdks.tvOS),
("visionOS", sdks.visionOS)
].compactMap { // remove nil compiler
guard $0.1 != nil, // has version array
!$0.1!.isEmpty // has at least 1 version
Expand Down
17 changes: 2 additions & 15 deletions Xcodes/Frontend/MainWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,6 @@ struct MainWindow: View {
})
.help("ManageAppleAccount")
.disabled(appState.isRestoringAuthenticationState)
if #available(macOS 14, *) {
SettingsLink(label: {
Label("Preferences", systemImage: "gearshape")
})
.help("PreferencesDescription")
} else {
Button(action: {
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
}, label: {
Label("Preferences", systemImage: "gearshape")
})
.help("PreferencesDescription")
}
}
}
}
Expand Down Expand Up @@ -151,10 +138,10 @@ struct MainWindow: View {
} else if case let .waitingForFederatedAuthentication(federationResponse) = appState.authenticationState {
SignInFederatedView(federationResponse: federationResponse)
.environmentObject(appState)
.frame(width: 400)
.frame(width: 452)
} else {
SignInCredentialsView()
.frame(width: 400)
.frame(width: 452)
}
}

Expand Down
Loading