From b4a4f8e3298e25ed27eb5aa1c2e7424dc024fe93 Mon Sep 17 00:00:00 2001 From: Matt Kiazyk Date: Mon, 5 Dec 2022 23:08:44 -0600 Subject: [PATCH] Adds open in Rosetta option for Apple Silicon machines --- Xcodes.xcodeproj/project.pbxproj | 4 +++ Xcodes/Backend/AppState.swift | 25 ++++++++++++----- Xcodes/Backend/Hardware.swift | 28 +++++++++++++++++++ Xcodes/Backend/XcodeCommands.swift | 26 ++++++++++++++--- .../Preferences/AdvancedPreferencePane.swift | 10 +++++++ Xcodes/Resources/en.lproj/Localizable.strings | 3 ++ 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 Xcodes/Backend/Hardware.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 3e5a3d8..c8ca65c 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; }; E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; }; E872EE4E2808D4F100D3DD8B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E872EE502808D4F100D3DD8B /* Localizable.strings */; }; + E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; }; E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; }; E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; }; E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; }; @@ -297,6 +298,7 @@ E2AFDCCA28F024D000864ADD /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = ""; }; E872EE4F2808D4F100D3DD8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = ""; }; E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; @@ -489,6 +491,7 @@ E87DD6EA25D053FA00D86808 /* Progress+.swift */, E81D7E9F2805250100A205FC /* Collection+.swift */, E8D655BF288DD04700A139C2 /* SelectedActionType.swift */, + E87AB3C42939B65E00D72F43 /* Hardware.swift */, ); path = Backend; sourceTree = ""; @@ -877,6 +880,7 @@ CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */, CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */, CAFBDC68259A308B003DCC5A /* InfoPane.swift in Sources */, + E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */, CAA1CB4D255A5CFD003FD669 /* SignInPhoneListView.swift in Sources */, CAFBDC6C259A3098003DCC5A /* View+Conditional.swift in Sources */, CABFA9CF2592EEEA00380FEE /* Process.swift in Sources */, diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 39c57a8..ebd44b5 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -93,7 +93,12 @@ class AppState: ObservableObject { } } } - + + @Published var showOpenInRosettaOption = false { + didSet { + Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption") + } + } // MARK: - Publisher Cancellables var cancellables = Set() @@ -142,6 +147,7 @@ class AppState: ObservableObject { createSymLinkOnSelect = Current.defaults.bool(forKey: "createSymLinkOnSelect") ?? false onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none installPath = Current.defaults.string(forKey: "installPath") ?? Path.defaultInstallDirectory.string + showOpenInRosettaOption = Current.defaults.bool(forKey: "showOpenInRosettaOption") ?? false } // MARK: Timer @@ -585,13 +591,18 @@ class AppState: ObservableObject { ) } - func open(xcode: Xcode) { + func open(xcode: Xcode, openInRosetta: Bool? = false) { switch xcode.installState { - case let .installed(path): - NSWorkspace.shared.openApplication(at: path.url, configuration: .init()) - default: - Logger.appState.error("\(xcode.id) is not installed") - return + case let .installed(path): + let config = NSWorkspace.OpenConfiguration.init() + if (openInRosetta ?? false) { + config.architecture = CPU_TYPE_X86_64 + } + config.allowsRunningApplicationSubstitution = false + NSWorkspace.shared.openApplication(at: path.url, configuration: config) + default: + Logger.appState.error("\(xcode.id) is not installed") + return } } diff --git a/Xcodes/Backend/Hardware.swift b/Xcodes/Backend/Hardware.swift new file mode 100644 index 0000000..0f7601b --- /dev/null +++ b/Xcodes/Backend/Hardware.swift @@ -0,0 +1,28 @@ +import Foundation + + +struct Hardware { + + /// + /// Determines the architecture of the Mac on which we're running. Returns `arm64` for Apple Silicon + /// and `x86_64` for Intel-based Macs or `nil` if the system call fails. + static func getMachineHardwareName() -> String? + { + var sysInfo = utsname() + let retVal = uname(&sysInfo) + var finalString: String? = nil + + if retVal == EXIT_SUCCESS + { + let bytes = Data(bytes: &sysInfo.machine, count: Int(_SYS_NAMELEN)) + finalString = String(data: bytes, encoding: .utf8) + } + + // _SYS_NAMELEN will include a billion null-terminators. Clear those out so string comparisons work as you expect. + return finalString?.trimmingCharacters(in: CharacterSet(charactersIn: "\0")) + } + + static func isAppleSilicon() -> Bool { + return Hardware.getMachineHardwareName() == "arm64" + } +} diff --git a/Xcodes/Backend/XcodeCommands.swift b/Xcodes/Backend/XcodeCommands.swift index b02cf60..76e9924 100644 --- a/Xcodes/Backend/XcodeCommands.swift +++ b/Xcodes/Backend/XcodeCommands.swift @@ -92,16 +92,34 @@ struct OpenButton: View { @EnvironmentObject var appState: AppState let xcode: Xcode? + var openInRosetta: Bool { + appState.showOpenInRosettaOption && Hardware.isAppleSilicon() + } + var body: some View { - Button(action: open) { - Text("Open") + if openInRosetta { + Menu("Open") { + Button(action: open) { + Text("Open") + } + .help("Open") + Button(action: open) { + Text("Open In Rosetta") + } + .help("Open In Rosetta") + } + } else { + Button(action: open) { + Text("Open") + } + .help("Open") } - .help("Open") + } private func open() { guard let xcode = xcode else { return } - appState.open(xcode: xcode) + appState.open(xcode: xcode, openInRosetta: openInRosetta) } } diff --git a/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift b/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift index 439f55a..21d6b8c 100644 --- a/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift +++ b/Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift @@ -106,6 +106,16 @@ struct AdvancedPreferencePane: View { } .groupBoxStyle(PreferencesGroupBoxStyle()) + if Hardware.isAppleSilicon() { + GroupBox(label: Text("Apple Silicon")) { + Toggle("ShowOpenInRosetta", isOn: $appState.showOpenInRosettaOption) + .disabled(appState.createSymLinkOnSelectDisabled) + Text("ShowOpenInRosettaDescription") + .font(.footnote) + .fixedSize(horizontal: false, vertical: true) + } + .groupBoxStyle(PreferencesGroupBoxStyle()) + } GroupBox(label: Text("PrivilegedHelper")) { VStack(alignment: .leading, spacing: 8) { diff --git a/Xcodes/Resources/en.lproj/Localizable.strings b/Xcodes/Resources/en.lproj/Localizable.strings index a4a685d..59a8676 100644 --- a/Xcodes/Resources/en.lproj/Localizable.strings +++ b/Xcodes/Resources/en.lproj/Localizable.strings @@ -102,6 +102,9 @@ "HelperNotInstalled" = "Helper is not installed"; "InstallHelper" = "Install helper"; +"ShowOpenInRosetta" = "Show Open In Rosetta option"; +"ShowOpenInRosettaDescription" = "Open in Rosetta option will show where other \"Open\" functions are available. Note: This will only show for Apple Silicon machines."; + // Experiment Preference Pane "Experiments" = "Experiments"; "FasterUnxip" = "Faster Unxip";