Repository: glinford/dns-easy-switcher Branch: main Commit: 899f42346182 Files: 86 Total size: 136.5 KB Directory structure: gitextract_u_joqz4e/ ├── .gitignore ├── DNS Easy Switcher/ │ ├── AboutView.swift │ ├── AddCustomDNSView.swift │ ├── Assets.xcassets/ │ │ ├── AccentColor.colorset/ │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── CustomDNSManagerView.swift │ ├── CustomSheet.swift │ ├── DNSManager.swift │ ├── DNSSettings.swift │ ├── DNSSpeedTester.swift │ ├── DNS_Easy_Switcher.entitlements │ ├── DNS_Easy_SwitcherApp.swift │ ├── EditCustomDNSView.swift │ ├── MenuBarController.swift │ ├── MenuBarView.swift │ └── Preview Content/ │ └── Preview Assets.xcassets/ │ └── Contents.json ├── DNS Easy Switcher.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ └── DNS Easy Switcher.xcscheme ├── LICENSE ├── README.md └── Releases/ ├── DNS Easy Switcher v1.0.0/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg ├── DNS Easy Switcher v1.0.1/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg ├── DNS Easy Switcher v1.0.2/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg ├── DNS Easy Switcher v1.0.3/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ ├── DNS Easy Switcher.dmg │ ├── DistributionSummary.plist │ ├── ExportOptions.plist │ └── Packaging.log ├── DNS Easy Switcher v1.0.4/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── CodeResources │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg ├── DNS Easy Switcher v1.0.5/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── CodeResources │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg ├── DNS Easy Switcher v1.0.6/ │ ├── DNS Easy Switcher.app/ │ │ └── Contents/ │ │ ├── CodeResources │ │ ├── Info.plist │ │ ├── MacOS/ │ │ │ └── DNS Easy Switcher │ │ ├── PkgInfo │ │ ├── Resources/ │ │ │ ├── AppIcon.icns │ │ │ └── Assets.car │ │ └── _CodeSignature/ │ │ └── CodeResources │ └── DNS Easy Switcher.dmg └── DNS Easy Switcher v1.0.7/ ├── DNS Easy Switcher.app/ │ └── Contents/ │ ├── CodeResources │ ├── Info.plist │ ├── MacOS/ │ │ └── DNS Easy Switcher │ ├── PkgInfo │ ├── Resources/ │ │ ├── AppIcon.icns │ │ └── Assets.car │ └── _CodeSignature/ │ └── CodeResources └── DNS Easy Switcher.dmg ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # fastlane # # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output .DS_Store ================================================ FILE: DNS Easy Switcher/AboutView.swift ================================================ // // AboutView.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 27/02/2025. // import SwiftUI struct AboutView: View { var onClose: () -> Void private var versionText: String { let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" return buildNumber.isEmpty ? "Version \(shortVersion)" : "Version \(shortVersion) (\(buildNumber))" } var body: some View { VStack(alignment: .leading, spacing: 12) { Text("DNS Easy Switcher") .font(.headline) Text(versionText) .foregroundColor(.secondary) Link("GitHub — glinford/dns-easy-switcher", destination: URL(string: "https://github.com/glinford/dns-easy-switcher")!) HStack { Spacer() Button("Close") { onClose() } .keyboardShortcut(.escape) } .padding(.top, 8) } .padding() .frame(width: 320) } } ================================================ FILE: DNS Easy Switcher/AddCustomDNSView.swift ================================================ // // AddCustomDNSView.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import SwiftUI import SwiftData struct AddCustomDNSView: View { @State private var name: String = "" @State private var primaryDNS: String = "" @State private var secondaryDNS: String = "" @State private var tertiaryDNS: String = "" @State private var quaternaryDNS: String = "" var onComplete: (CustomDNSServer?) -> Void var body: some View { VStack(alignment: .leading, spacing: 12) { TextField("Name (e.g. Work DNS)", text: $name) .textFieldStyle(.roundedBorder) TextField("Primary DNS (e.g. 8.8.8.8 or 127.0.0.1:5353)", text: $primaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple addresses. For custom ports on IPv4, add colon and port number (e.g., 127.0.0.1:5353)") TextField("Secondary DNS (optional)", text: $secondaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple addresses. For custom ports on IPv4, add colon and port number (e.g., 127.0.0.1:5353)") TextField("Third DNS (IPv6 or IPv4, optional)", text: $tertiaryDNS) .textFieldStyle(.roundedBorder) .help("Tip: bracket IPv6 if adding a port, e.g., [2001:4860:4860::8888]:5353") TextField("Fourth DNS (IPv6 or IPv4, optional)", text: $quaternaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple IPv6 entries if needed") HStack { Button("Cancel") { onComplete(nil) } .keyboardShortcut(.escape) Spacer() Button("Add") { guard !name.isEmpty && !primaryDNS.isEmpty else { return } let server = CustomDNSServer( name: name, primaryDNS: primaryDNS, secondaryDNS: secondaryDNS, tertiaryDNS: tertiaryDNS, quaternaryDNS: quaternaryDNS ) onComplete(server) } .keyboardShortcut(.return) .disabled(name.isEmpty || primaryDNS.isEmpty) } } .padding() .frame(width: 360) } } ================================================ FILE: DNS Easy Switcher/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DNS Easy Switcher/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ {"images":[{"size":"1024x1024","filename":"1024-mac.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024-mac.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} ================================================ FILE: DNS Easy Switcher/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DNS Easy Switcher/CustomDNSManagerView.swift ================================================ // // CustomDNSManagerView.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 25/02/2025. // import SwiftUI enum CustomDNSAction { case use, edit, delete } struct CustomDNSManagerView: View { let customServers: [CustomDNSServer] let onAction: (CustomDNSAction, CustomDNSServer) -> Void let onClose: () -> Void var body: some View { VStack(alignment: .leading, spacing: 8) { Text("Manage Custom DNS") .font(.headline) .padding(.bottom, 4) if customServers.isEmpty { Text("No custom DNS servers added") .foregroundColor(.secondary) .padding(.vertical, 12) } else { List { ForEach(customServers) { server in HStack { Text(server.name) .lineLimit(1) Spacer() Button(action: { onAction(.edit, server) }) { Image(systemName: "pencil") .frame(width: 16, height: 16) } .buttonStyle(.plain) .help("Edit this DNS") .padding(.trailing, 8) Button(action: { onAction(.delete, server) }) { Image(systemName: "trash") .frame(width: 16, height: 16) } .buttonStyle(.plain) .foregroundColor(.red) .help("Delete this DNS") } .padding(.vertical, 4) } } .frame(minHeight: 100, maxHeight: 200) .listStyle(.plain) } HStack { Spacer() Button("Close") { onClose() } .keyboardShortcut(.escape) } .padding(.top, 8) } .padding() .frame(width: 300) } } ================================================ FILE: DNS Easy Switcher/CustomSheet.swift ================================================ // // CustomSheet.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import SwiftUI import AppKit class CustomSheetWindowController: NSWindowController { convenience init(view: some View, title: String) { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 300, height: 200), styleMask: [.titled, .closable], backing: .buffered, defer: false ) window.title = title window.center() window.contentView = NSHostingView(rootView: view) window.isReleasedWhenClosed = false self.init(window: window) } } ================================================ FILE: DNS Easy Switcher/DNSManager.swift ================================================ // // DNSManager.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import Foundation import AppKit import LocalAuthentication class DNSManager { static let shared = DNSManager() let cloudflareServers = [ "1.1.1.1", // IPv4 Primary "1.0.0.1", // IPv4 Secondary "2606:4700:4700::1111", // IPv6 Primary "2606:4700:4700::1001" // IPv6 Secondary ] let quad9Servers = [ "9.9.9.9", // IPv4 Primary "149.112.112.112", // IPv4 Secondary "2620:fe::fe", // IPv6 Primary "2620:fe::9" // IPv6 Secondary ] let adguardServers = [ "94.140.14.14", // IPv4 Primary "94.140.15.15", // IPv4 Secondary "2a10:50c0::ad1:ff", // IPv6 Primary "2a10:50c0::ad2:ff" // IPv6 Secondary ] let getflixServers: [String: String] = [ "Australia — Melbourne": "118.127.62.178", "Australia — Perth": "45.248.78.99", "Australia — Sydney 1": "54.252.183.4", "Australia — Sydney 2": "54.252.183.5", "Brazil — São Paulo": "54.94.175.250", "Canada — Toronto": "169.53.182.124", "Denmark — Copenhagen": "82.103.129.240", "Germany — Frankfurt": "54.93.169.181", "Great Britain — London": "212.71.249.225", "Hong Kong": "119.9.73.44", "India — Mumbai": "103.13.112.251", "Ireland — Dublin": "54.72.70.84", "Italy — Milan": "95.141.39.238", "Japan — Tokyo": "172.104.90.123", "Netherlands — Amsterdam": "46.166.189.67", "New Zealand — Auckland 1": "120.138.27.84", "New Zealand — Auckland 2": "120.138.22.174", "Singapore": "54.251.190.247", "South Africa — Johannesburg": "102.130.116.140", "Spain — Madrid": "185.93.3.168", "Sweden — Stockholm": "46.246.29.68", "Turkey — Istanbul": "212.68.53.190", "United States — Dallas (Central)": "169.55.51.86", "United States — Oregon (West)": "54.187.61.200", "United States — Virginia (East)": "54.164.176.2" ] private func getNetworkServices() -> [String] { let task = Process() task.launchPath = "/usr/sbin/networksetup" task.arguments = ["-listallnetworkservices"] let pipe = Pipe() task.standardOutput = pipe do { try task.run() let data = pipe.fileHandleForReading.readDataToEndOfFile() if let services = String(data: data, encoding: .utf8) { return services.components(separatedBy: .newlines) .dropFirst() // Drop the header line .filter { !$0.isEmpty && !$0.hasPrefix("*") } // Remove empty lines and disabled services } } catch { print("Error getting network services: \(error)") } return [] } private func findActiveServices() -> [String] { let services = getNetworkServices() let activeServices = services.filter { $0.lowercased().contains("wi-fi") || $0.lowercased().contains("ethernet") } return activeServices.isEmpty ? [services.first].compactMap { $0 } : activeServices } private func executeWithAuthentication(command: String, completion: @escaping (Bool) -> Void) { let context = LAContext() context.localizedReason = "DNS Easy Switcher needs to modify network settings" var error: NSError? if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) { context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "DNS Easy Switcher needs to modify network settings") { success, error in if success { DispatchQueue.global(qos: .userInitiated).async { let task = Process() task.launchPath = "/bin/bash" task.arguments = ["-c", command] let pipe = Pipe() task.standardOutput = pipe do { try task.run() task.waitUntilExit() let success = task.terminationStatus == 0 DispatchQueue.main.async { completion(success) } } catch { print("Failed to execute command: \(error)") DispatchQueue.main.async { completion(false) } } } } else { print("Authentication failed: \(error?.localizedDescription ?? "Unknown error")") DispatchQueue.main.async { completion(false) } } } } else { // Fall back to AppleScript for admin privileges print("Local Authentication not available: \(error?.localizedDescription ?? "Unknown error")") DispatchQueue.global(qos: .userInitiated).async { let script = """ do shell script "\(command)" with administrator privileges """ var scriptError: NSDictionary? if let scriptObject = NSAppleScript(source: script) { if scriptObject.executeAndReturnError(&scriptError) != nil { DispatchQueue.main.async { completion(true) } } else { print("AppleScript error: \(scriptError ?? ["error": "Unknown error"] as NSDictionary)") DispatchQueue.main.async { completion(false) } } } else { DispatchQueue.main.async { completion(false) } } } } } func setPredefinedDNS(dnsServers: [String], completion: @escaping (Bool) -> Void) { let services = findActiveServices() guard !services.isEmpty else { completion(false) return } let dispatchGroup = DispatchGroup() var allSucceeded = true for service in services { dispatchGroup.enter() let dnsArgs = dnsServers.joined(separator: " ") let dnsCommand = "/usr/sbin/networksetup -setdnsservers '\(service)' \(dnsArgs)" let ipv6Command = "/usr/sbin/networksetup -setv6off '\(service)'; /usr/sbin/networksetup -setv6automatic '\(service)'" let fullCommand = "\(dnsCommand); \(ipv6Command)" executeWithAuthentication(command: fullCommand) { success in if !success { allSucceeded = false } dispatchGroup.leave() } } dispatchGroup.notify(queue: .main) { completion(allSucceeded) } } func setCustomDNS(servers rawServers: [String], completion: @escaping (Bool) -> Void) { let services = findActiveServices() // Allow comma-separated entries in any slot let flattenedServers = rawServers .flatMap { entry in entry .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } } .filter { !$0.isEmpty } let parsedServers = flattenedServers.compactMap(parseDNSServer) guard !services.isEmpty, !parsedServers.isEmpty else { completion(false) return } let hasCustomPorts = parsedServers.contains { $0.port != nil } // If no custom ports are specified, use the standard network setup method if !hasCustomPorts { let servers = parsedServers.map { $0.address } setStandardDNS(services: services, servers: servers, completion: completion) return } // For DNS servers with custom ports, we need to modify the resolver configuration let resolverContent = createResolverContent(parsedServers) // We'll use the existing executeWithAuthentication method which properly handles // authentication with Touch ID or admin password let createDirCmd = "sudo mkdir -p /etc/resolver" executeWithAuthentication(command: createDirCmd) { dirSuccess in if !dirSuccess { print("Failed to create resolver directory") completion(false) return } // Now write the resolver content let writeFileCmd = "echo '\(resolverContent)' | sudo tee /etc/resolver/custom > /dev/null" self.executeWithAuthentication(command: writeFileCmd) { fileSuccess in if !fileSuccess { print("Failed to write resolver configuration") completion(false) return } // Set permissions let permCmd = "sudo chmod 644 /etc/resolver/custom" self.executeWithAuthentication(command: permCmd) { permSuccess in if !permSuccess { print("Failed to set resolver file permissions") completion(false) return } // Also set standard DNS servers to ensure proper resolution let standardServers = parsedServers.map { $0.address } self.setStandardDNS(services: services, servers: standardServers, completion: completion) } } } } private func createResolverContent(_ servers: [(address: String, port: Int?)]) -> String { var resolverContent = "# Custom DNS configuration with port\n" for server in servers { resolverContent += "nameserver \(server.address)\n" if let port = server.port { resolverContent += "port \(port)\n" } } return resolverContent } private func parseDNSServer(_ input: String) -> (address: String, port: Int?)? { let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } // Support IPv6 with explicit port using bracket notation: [addr]:port if trimmed.hasPrefix("["), let closingBracket = trimmed.firstIndex(of: "]") { let address = String(trimmed[trimmed.index(after: trimmed.startIndex).. Void) { let services = findActiveServices() guard !services.isEmpty else { completion(false) return } // Remove any custom resolver configuration let removeResolverCmd = "sudo rm -f /etc/resolver/custom" executeWithAuthentication(command: removeResolverCmd) { _ in // Continue with normal DNS reset regardless of resolver removal success let dispatchGroup = DispatchGroup() var allSucceeded = true for service in services { dispatchGroup.enter() let command = "/usr/sbin/networksetup -setdnsservers '\(service)' empty" self.executeWithAuthentication(command: command) { success in if !success { allSucceeded = false } dispatchGroup.leave() } } dispatchGroup.notify(queue: .main) { completion(allSucceeded) } } } // Helper method to set standard DNS settings private func setStandardDNS(services: [String], servers: [String], completion: @escaping (Bool) -> Void) { let dispatchGroup = DispatchGroup() var allSucceeded = true for service in services { dispatchGroup.enter() let dnsArgs = servers.joined(separator: " ") let dnsCommand = "/usr/sbin/networksetup -setdnsservers '\(service)' \(dnsArgs)" let ipv6Command = "/usr/sbin/networksetup -setv6off '\(service)'; /usr/sbin/networksetup -setv6automatic '\(service)'" let fullCommand = "\(dnsCommand); \(ipv6Command)" executeWithAuthentication(command: fullCommand) { success in if !success { allSucceeded = false } dispatchGroup.leave() } } dispatchGroup.notify(queue: .main) { completion(allSucceeded) } } private func executePrivilegedCommand(arguments: [String]) -> Bool { let services = findActiveServices() guard !services.isEmpty else { return false } var success = true for service in services { // Properly escape the arguments for AppleScript let escapedArgs = arguments.map { arg in return "\\\"" + arg.replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\"", with: "\\\"") + "\\\"" }.joined(separator: " ") // Combine IPv4 and IPv6 commands in a single script if we're setting DNS let isSettingDNS = arguments[0] == "-setdnsservers" let commandScript: String if isSettingDNS { // Combine DNS and IPv6 commands with semicolons in a single admin privilege request let ipv6Script = "/usr/sbin/networksetup -setv6off '\(service)'; /usr/sbin/networksetup -setv6automatic '\(service)'" commandScript = """ do shell script "/usr/sbin/networksetup \(escapedArgs); \(ipv6Script)" with administrator privileges with prompt "DNS Easy Switcher needs to modify network settings" """ } else { // For other commands, keep as is commandScript = """ do shell script "/usr/sbin/networksetup \(escapedArgs)" with administrator privileges with prompt "DNS Easy Switcher needs to modify network settings" """ } var error: NSDictionary? if let scriptObject = NSAppleScript(source: commandScript) { if scriptObject.executeAndReturnError(&error) == nil { if let error = error { print("Error executing privileged command: \(error)") success = false } } } else { success = false } } return success } func clearDNSCache(completion: @escaping (Bool) -> Void) { let flushCommand = "dscacheutil -flushcache" executeWithAuthentication(command: flushCommand) { success in if success { let restartCommand = "killall -HUP mDNSResponder 2>/dev/null || killall -HUP mdnsresponder 2>/dev/null || true" self.executeWithAuthentication(command: restartCommand) { _ in completion(success) } } else { completion(false) } } } } ================================================ FILE: DNS Easy Switcher/DNSSettings.swift ================================================ // // DNSSettings.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import Foundation import SwiftData @Model final class CustomDNSServer: Identifiable { var id: String var name: String var primaryDNS: String var secondaryDNS: String var tertiaryDNS: String? var quaternaryDNS: String? var timestamp: Date init(id: String = UUID().uuidString, name: String, primaryDNS: String, secondaryDNS: String, tertiaryDNS: String? = nil, quaternaryDNS: String? = nil, timestamp: Date = Date()) { self.id = id self.name = name self.primaryDNS = primaryDNS self.secondaryDNS = secondaryDNS self.tertiaryDNS = tertiaryDNS self.quaternaryDNS = quaternaryDNS self.timestamp = timestamp } } @Model final class DNSSettings { @Attribute(.unique) var id: String var isCloudflareEnabled: Bool var isQuad9Enabled: Bool var activeCustomDNSID: String? var timestamp: Date var activeGetFlixLocation: String? var isAdGuardEnabled: Bool? init(id: String = UUID().uuidString, isCloudflareEnabled: Bool = false, isQuad9Enabled: Bool = false, activeCustomDNSID: String? = nil, timestamp: Date = Date(), isAdGuardEnabled: Bool? = false, activeGetFlixLocation: String? = nil) { self.id = id self.isCloudflareEnabled = isCloudflareEnabled self.isQuad9Enabled = isQuad9Enabled self.activeCustomDNSID = activeCustomDNSID self.timestamp = timestamp self.isAdGuardEnabled = isAdGuardEnabled } } extension CustomDNSServer { /// Returns all user-entered DNS entries, supporting comma-separated values per field. var dnsEntries: [String] { [primaryDNS, secondaryDNS, tertiaryDNS ?? "", quaternaryDNS ?? ""] .flatMap { entry in entry .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } } .filter { !$0.isEmpty } } } ================================================ FILE: DNS Easy Switcher/DNSSpeedTester.swift ================================================ // // DNSSpeedTester.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 25/02/2025. // import Foundation import SwiftData class DNSSpeedTester { static let shared = DNSSpeedTester() // Result struct to store ping results struct PingResult: Identifiable { let id = UUID() let dnsName: String let server: String let responseTime: Double // in milliseconds let isSuccess: Bool let isCustom: Bool let customID: String? init(dnsName: String, server: String, responseTime: Double, isSuccess: Bool, isCustom: Bool = false, customID: String? = nil) { self.dnsName = dnsName self.server = server self.responseTime = responseTime self.isSuccess = isSuccess self.isCustom = isCustom self.customID = customID } } // Track running tasks to ensure proper cleanup private var runningTasks: [Process] = [] private var isCurrentlyTesting = false // Perform ping test for all DNS servers including custom ones func testAllDNS(customServers: [CustomDNSServer], completion: @escaping ([PingResult]) -> Void) { // Safety check to prevent multiple simultaneous tests guard !isCurrentlyTesting else { completion([]) return } isCurrentlyTesting = true runningTasks = [] let dnsManager = DNSManager.shared var allDNSToTest: [(String, String, Bool, String?)] = [ ("Cloudflare", dnsManager.cloudflareServers[0], false, nil), ("Quad9", dnsManager.quad9Servers[0], false, nil), ("AdGuard", dnsManager.adguardServers[0], false, nil) ] // Add all Getflix servers let getflixServers = dnsManager.getflixServers.sorted(by: { $0.key < $1.key }) allDNSToTest.append(contentsOf: getflixServers.map { ("Getflix: \($0.key)", $0.value, false, nil) }) // Add custom DNS servers (first entry only to keep test time reasonable) for server in customServers { if let firstEntry = server.dnsEntries.first { allDNSToTest.append((server.name, firstEntry, true, server.id)) } } // Use serial queue to avoid overwhelming the system let queue = DispatchQueue(label: "com.glinford.DNSSpeedTest", qos: .userInitiated) let resultsQueue = DispatchQueue(label: "com.glinford.DNSSpeedTestResults", attributes: .concurrent) let resultsLock = NSLock() var results: [PingResult] = [] let group = DispatchGroup() // Create a semaphore to limit concurrent operations let semaphore = DispatchSemaphore(value: 5) // Allow 5 concurrent pings for (index, (name, server, isCustom, customID)) in allDNSToTest.enumerated() { group.enter() // Add a small delay between tests to avoid overwhelming the system queue.asyncAfter(deadline: .now() + Double(index) * 0.05) { [weak self] in guard let self = self else { semaphore.signal() group.leave() return } semaphore.wait() // Wait for a slot to become available self.pingServer(server: server) { responseTime, isSuccess in resultsQueue.async { resultsLock.lock() let result = PingResult( dnsName: name, server: server, responseTime: responseTime, isSuccess: isSuccess, isCustom: isCustom, customID: customID ) results.append(result) resultsLock.unlock() semaphore.signal() // Release the slot group.leave() } } } } group.notify(queue: .main) { [weak self] in guard let self = self else { return } // Clean up any remaining processes for task in self.runningTasks { if task.isRunning { task.terminate() } } self.runningTasks = [] self.isCurrentlyTesting = false // Sort results by response time let sortedResults = results.sorted { $0.responseTime < $1.responseTime } completion(sortedResults) } } // Cancel any ongoing tests func cancelTests() { for task in runningTasks { if task.isRunning { task.terminate() } } runningTasks = [] isCurrentlyTesting = false } // Clean up when app is terminating func cleanup() { cancelTests() } // Measure ping time to a DNS server with safer implementation private func pingServer(server: String, completion: @escaping (Double, Bool) -> Void) { let task = Process() task.launchPath = "/sbin/ping" task.arguments = ["-c", "2", "-t", "1", server] // 2 pings with 1-second timeout (reduced for speed) let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe // Keep track of task for cleanup runningTasks.append(task) // Set up termination handler before running task.terminationHandler = { [weak self] process in guard let self = self else { return } // Remove this task from our tracking list if let index = self.runningTasks.firstIndex(where: { $0 === process }) { self.runningTasks.remove(at: index) } let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) ?? "" // Parse ping results if process.terminationStatus == 0 && output.contains("min/avg/max") { // More robust parsing approach let lines = output.components(separatedBy: .newlines) for line in lines { if line.contains("min/avg/max") { let parts = line.components(separatedBy: "=") if parts.count >= 2 { let stats = parts[1].trimmingCharacters(in: .whitespaces) let values = stats.components(separatedBy: "/") if values.count >= 2 { if let avgTime = Double(values[1].trimmingCharacters(in: .whitespaces)) { completion(avgTime, true) return } } } } } // If we get here, parsing failed completion(999, false) } else { completion(999, false) // Ping failed } } do { try task.run() } catch { // Remove this task from our tracking list if it failed to start if let index = runningTasks.firstIndex(where: { $0 === task }) { runningTasks.remove(at: index) } completion(999, false) // Process failed to start } } // Deinitializer to clean up resources deinit { cleanup() } } ================================================ FILE: DNS Easy Switcher/DNS_Easy_Switcher.entitlements ================================================ ================================================ FILE: DNS Easy Switcher/DNS_Easy_SwitcherApp.swift ================================================ // // DNS_Easy_SwitcherApp.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import SwiftUI import SwiftData import AppKit @main struct DNS_Easy_SwitcherApp: App { @StateObject private var menuBarController = MenuBarController() let modelContainer: ModelContainer init() { do { let schema = Schema([ DNSSettings.self, CustomDNSServer.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) self.modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { // If the persistent store cannot be created (e.g., corrupted store or permission issue), // fall back to an in-memory container so the app can still launch. let schema = Schema([ DNSSettings.self, CustomDNSServer.self ]) self.modelContainer = try! ModelContainer( for: schema, configurations: [ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)] ) print("Warning: Using in-memory store due to ModelContainer error: \(error)") } } var body: some Scene { WindowGroup(id: "hidden") { Color.clear .frame(width: 0, height: 0) .hidden() } .windowStyle(.hiddenTitleBar) .defaultSize(width: 0, height: 0) .modelContainer(modelContainer) MenuBarExtra("DNS Switcher", systemImage: "network") { MenuBarView() .environment(\.modelContext, modelContainer.mainContext) .frame(width: 300) } } } ================================================ FILE: DNS Easy Switcher/EditCustomDNSView.swift ================================================ // // EditCustomDNSView.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 25/02/2025. // import SwiftUI struct EditCustomDNSView: View { let server: CustomDNSServer var onComplete: (CustomDNSServer?) -> Void @State private var name: String @State private var primaryDNS: String @State private var secondaryDNS: String @State private var tertiaryDNS: String @State private var quaternaryDNS: String init(server: CustomDNSServer, onComplete: @escaping (CustomDNSServer?) -> Void) { self.server = server self.onComplete = onComplete _name = State(initialValue: server.name) _primaryDNS = State(initialValue: server.primaryDNS) _secondaryDNS = State(initialValue: server.secondaryDNS) _tertiaryDNS = State(initialValue: server.tertiaryDNS ?? "") _quaternaryDNS = State(initialValue: server.quaternaryDNS ?? "") } var body: some View { VStack(alignment: .leading, spacing: 12) { TextField("Name (e.g. Work DNS)", text: $name) .textFieldStyle(.roundedBorder) TextField("Primary DNS (e.g. 8.8.8.8 or 127.0.0.1:5353)", text: $primaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple addresses. For custom ports on IPv4, add colon and port number (e.g., 127.0.0.1:5353)") TextField("Secondary DNS (optional)", text: $secondaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple addresses. For custom ports on IPv4, add colon and port number (e.g., 127.0.0.1:5353)") TextField("Third DNS (IPv6 or IPv4, optional)", text: $tertiaryDNS) .textFieldStyle(.roundedBorder) .help("Tip: bracket IPv6 if adding a port, e.g., [2001:4860:4860::8888]:5353") TextField("Fourth DNS (IPv6 or IPv4, optional)", text: $quaternaryDNS) .textFieldStyle(.roundedBorder) .help("Use comma to add multiple IPv6 entries if needed") HStack { Button("Cancel") { onComplete(nil) } .keyboardShortcut(.escape) Spacer() Button("Save") { guard !name.isEmpty && !primaryDNS.isEmpty else { return } let updatedServer = CustomDNSServer( id: server.id, name: name, primaryDNS: primaryDNS, secondaryDNS: secondaryDNS, tertiaryDNS: tertiaryDNS, quaternaryDNS: quaternaryDNS, timestamp: server.timestamp ) onComplete(updatedServer) } .keyboardShortcut(.return) .disabled(name.isEmpty || primaryDNS.isEmpty) } } .padding() .frame(width: 360) } } ================================================ FILE: DNS Easy Switcher/MenuBarController.swift ================================================ // // MenuBarController.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import Foundation import AppKit class MenuBarController: ObservableObject { init() { // Hide the dock icon NSApplication.shared.setActivationPolicy(.accessory) } } ================================================ FILE: DNS Easy Switcher/MenuBarView.swift ================================================ // // MenuBarView.swift // DNS Easy Switcher // // Created by Gregory LINFORD on 23/02/2025. // import SwiftUI import SwiftData struct MenuBarView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \DNSSettings.timestamp) private var dnsSettings: [DNSSettings] @Query(sort: \CustomDNSServer.name) private var customServers: [CustomDNSServer] @State private var isUpdating = false @State private var isSpeedTesting = false @State private var pingResults: [DNSSpeedTester.PingResult] = [] @State private var showingAddDNS = false @State private var showingManageDNS = false @State private var aboutWindowController: CustomSheetWindowController? @State private var selectedServer: CustomDNSServer? @State private var windowController: CustomSheetWindowController? var body: some View { Group { VStack { // Cloudflare DNS Toggle(getLabelWithPing("Cloudflare DNS", dnsType: .cloudflare), isOn: Binding( get: { dnsSettings.first?.isCloudflareEnabled ?? false }, set: { newValue in if newValue && !isUpdating { activateDNS(type: .cloudflare) } } )) .padding(.horizontal) .disabled(isUpdating || isSpeedTesting) .overlay(alignment: .trailing) { if isSpeedTesting { ProgressView() .scaleEffect(0.6) .frame(width: 12, height: 12) .padding(.trailing, 8) } } // Quad9 DNS Toggle(getLabelWithPing("Quad9 DNS", dnsType: .quad9), isOn: Binding( get: { dnsSettings.first?.isQuad9Enabled ?? false }, set: { newValue in if newValue && !isUpdating { activateDNS(type: .quad9) } } )) .padding(.horizontal) .disabled(isUpdating || isSpeedTesting) .overlay(alignment: .trailing) { if isSpeedTesting { ProgressView() .scaleEffect(0.6) .frame(width: 12, height: 12) .padding(.trailing, 8) } } // AdGuard DNS Toggle(getLabelWithPing("AdGuard DNS", dnsType: .adguard), isOn: Binding( get: { dnsSettings.first?.isAdGuardEnabled ?? false }, set: { newValue in if newValue && !isUpdating { activateDNS(type: .adguard) } } )) .padding(.horizontal) .disabled(isUpdating || isSpeedTesting) .overlay(alignment: .trailing) { if isSpeedTesting { ProgressView() .scaleEffect(0.6) .frame(width: 12, height: 12) .padding(.trailing, 8) } } // GetFlix DNS Menu Menu { ForEach(Array(DNSManager.shared.getflixServers.keys.sorted()), id: \.self) { location in Button(action: { activateDNS(type: .getflix(location)) }) { HStack { Text(getGetflixLabelWithPing(location)) Spacer() if dnsSettings.first?.activeGetFlixLocation == location { Image(systemName: "checkmark") } } } } } label: { HStack { Text("GetFlix DNS") Spacer() if let activeLocation = dnsSettings.first?.activeGetFlixLocation { Circle() .fill(Color.green) .frame(width: 8, height: 8) } if isSpeedTesting { ProgressView() .scaleEffect(0.6) .frame(width: 12, height: 12) .padding(.trailing, 4) } } } .padding(.horizontal) .disabled(isUpdating || isSpeedTesting) Divider() // Custom DNS section if !customServers.isEmpty { Menu { ForEach(customServers) { server in Button(action: { activateDNS(type: .custom(server)) }) { HStack { Text(getCustomDNSLabelWithPing(server)) Spacer() if dnsSettings.first?.activeCustomDNSID == server.id { Image(systemName: "checkmark") } } } } } label: { HStack { Text("Custom DNS") Spacer() if dnsSettings.first?.activeCustomDNSID != nil { Circle() .fill(Color.green) .frame(width: 8, height: 8) } if isSpeedTesting { ProgressView() .scaleEffect(0.6) .frame(width: 12, height: 12) .padding(.trailing, 4) } } } .padding(.horizontal) .disabled(isUpdating || isSpeedTesting) Button(action: { showManageCustomDNSSheet() }) { Text("Manage Custom DNS") .frame(maxWidth: .infinity) } .buttonStyle(.plain) .padding(.horizontal) .padding(.vertical, 5) .disabled(isSpeedTesting) } Button(action: { showAddCustomDNSSheet() }) { Text("Add Custom DNS") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .padding(.horizontal) .padding(.vertical, 5) .disabled(isSpeedTesting) Divider() Button("Disable DNS Override") { if !isUpdating && !isSpeedTesting { isUpdating = true DNSManager.shared.disableDNS { success in if success { Task { @MainActor in updateSettings(type: .none) } } isUpdating = false } } } .padding(.vertical, 5) .disabled(isUpdating || isSpeedTesting) // Speed Test Button Button(action: { runSpeedTest() }) { HStack { Text("Run Speed Test") if isSpeedTesting { Spacer() ProgressView() .scaleEffect(0.8) .frame(width: 16, height: 16) } } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .padding(.horizontal) .padding(.vertical, 5) .disabled(isUpdating || isSpeedTesting) Button(action: { clearDNSCache() }) { HStack { Text("Clear DNS Cache") if isUpdating { Spacer() ProgressView() .scaleEffect(0.8) .frame(width: 16, height: 16) } } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .padding(.horizontal) .padding(.vertical, 5) .disabled(isUpdating || isSpeedTesting) Divider() Button("About") { showAboutSheet() } .padding(.vertical, 5) Button("Quit") { NSApplication.shared.terminate(nil) } .padding(.vertical, 5) } .padding(.vertical, 5) } .onAppear { ensureSettingsExist() } } // Helper methods for getting ping results private func getLabelWithPing(_ baseLabel: String, dnsType: DNSType) -> String { guard !pingResults.isEmpty else { return baseLabel } switch dnsType { case .cloudflare: if let result = pingResults.first(where: { $0.dnsName == "Cloudflare" }) { return "\(baseLabel) (\(Int(result.responseTime))ms)" } case .quad9: if let result = pingResults.first(where: { $0.dnsName == "Quad9" }) { return "\(baseLabel) (\(Int(result.responseTime))ms)" } case .adguard: if let result = pingResults.first(where: { $0.dnsName == "AdGuard" }) { return "\(baseLabel) (\(Int(result.responseTime))ms)" } default: break } return baseLabel } private func getGetflixLabelWithPing(_ location: String) -> String { guard !pingResults.isEmpty else { return location } if let result = pingResults.first(where: { $0.dnsName == "Getflix: \(location)" }) { return "\(location) (\(Int(result.responseTime))ms)" } return location } private func getCustomDNSLabelWithPing(_ server: CustomDNSServer) -> String { guard !pingResults.isEmpty else { return server.name } if let result = pingResults.first(where: { $0.isCustom && $0.customID == server.id }) { return "\(server.name) (\(Int(result.responseTime))ms)" } return server.name } // Run DNS speed test private func runSpeedTest() { guard !isSpeedTesting else { return } isSpeedTesting = true pingResults = [] DNSSpeedTester.shared.testAllDNS(customServers: customServers) { results in self.pingResults = results self.isSpeedTesting = false } } private func showAddCustomDNSSheet() { let addView = AddCustomDNSView { newServer in if let newServer = newServer { modelContext.insert(newServer) try? modelContext.save() // Automatically activate the new DNS activateDNS(type: .custom(newServer)) } windowController?.close() windowController = nil } windowController = CustomSheetWindowController(view: addView, title: "Add Custom DNS") windowController?.window?.level = .floating windowController?.showWindow(nil) // Position the window relative to the menu bar if let window = windowController?.window, let screenFrame = NSScreen.main?.frame { let windowFrame = window.frame let newOrigin = NSPoint( x: screenFrame.width - windowFrame.width - 20, y: screenFrame.height - 40 - windowFrame.height ) window.setFrameTopLeftPoint(newOrigin) } } private func showManageCustomDNSSheet() { let manageView = CustomDNSManagerView(customServers: customServers, onAction: { action, server in switch action { case .edit: editCustomDNS(server) case .delete: modelContext.delete(server) try? modelContext.save() // If this was the active server, disable DNS if dnsSettings.first?.activeCustomDNSID == server.id { isUpdating = true DNSManager.shared.disableDNS { success in if success { Task { @MainActor in updateSettings(type: .none) } } isUpdating = false } } case .use: activateDNS(type: .custom(server)) } // Don't close the window for .use or .edit actions if action == .delete { windowController?.close() windowController = nil } }, onClose: { windowController?.close() windowController = nil }) windowController = CustomSheetWindowController(view: manageView, title: "Manage Custom DNS") windowController?.window?.level = .floating windowController?.showWindow(nil) // Position the window relative to the menu bar if let window = windowController?.window, let screenFrame = NSScreen.main?.frame { let windowFrame = window.frame let newOrigin = NSPoint( x: screenFrame.width - windowFrame.width - 20, y: screenFrame.height - 40 - windowFrame.height ) window.setFrameTopLeftPoint(newOrigin) } } private func showAboutSheet() { let aboutView = AboutView { aboutWindowController?.close() aboutWindowController = nil } aboutWindowController?.close() aboutWindowController = CustomSheetWindowController(view: aboutView, title: "About") aboutWindowController?.window?.level = .floating aboutWindowController?.showWindow(nil) aboutWindowController?.window?.center() } private func editCustomDNS(_ server: CustomDNSServer) { let editView = EditCustomDNSView(server: server) { updatedServer in if let updatedServer = updatedServer { // Update existing server properties server.name = updatedServer.name server.primaryDNS = updatedServer.primaryDNS server.secondaryDNS = updatedServer.secondaryDNS server.tertiaryDNS = updatedServer.tertiaryDNS server.quaternaryDNS = updatedServer.quaternaryDNS try? modelContext.save() // If this was the active server, update DNS settings if dnsSettings.first?.activeCustomDNSID == server.id { activateDNS(type: .custom(server)) } } windowController?.close() windowController = nil } windowController?.close() windowController = CustomSheetWindowController(view: editView, title: "Edit Custom DNS") windowController?.window?.level = .floating windowController?.showWindow(nil) // Position the window relative to the menu bar if let window = windowController?.window, let screenFrame = NSScreen.main?.frame { let windowFrame = window.frame let newOrigin = NSPoint( x: screenFrame.width - windowFrame.width - 20, y: screenFrame.height - 40 - windowFrame.height ) window.setFrameTopLeftPoint(newOrigin) } } enum DNSType: Equatable { case none case cloudflare case quad9 case adguard case custom(CustomDNSServer) case getflix(String) static func == (lhs: DNSType, rhs: DNSType) -> Bool { switch (lhs, rhs) { case (.none, .none): return true case (.cloudflare, .cloudflare): return true case (.quad9, .quad9): return true case (.adguard, .adguard): return true case (.custom(let lServer), .custom(let rServer)): return lServer.id == rServer.id case (.getflix(let lLocation), .getflix(let rLocation)): return lLocation == rLocation default: return false } } } private func activateDNS(type: DNSType) { isUpdating = true switch type { case .cloudflare: DNSManager.shared.setPredefinedDNS(dnsServers: DNSManager.shared.cloudflareServers) { success in if success { Task { @MainActor in updateSettings(type: type) } } isUpdating = false } case .quad9: DNSManager.shared.setPredefinedDNS(dnsServers: DNSManager.shared.quad9Servers) { success in if success { Task { @MainActor in updateSettings(type: type) } } isUpdating = false } case .adguard: DNSManager.shared.setPredefinedDNS(dnsServers: DNSManager.shared.adguardServers) { success in if success { Task { @MainActor in updateSettings(type: type) } } isUpdating = false } case .custom(let server): DNSManager.shared.setCustomDNS(servers: server.dnsEntries) { success in if success { Task { @MainActor in updateSettings(type: type) } } isUpdating = false } case .getflix(let location): if let dnsServer = DNSManager.shared.getflixServers[location] { DNSManager.shared.setCustomDNS(servers: [dnsServer]) { success in if success { Task { @MainActor in updateSettings(type: type) } } isUpdating = false } } case .none: updateSettings(type: type) isUpdating = false } } private func updateSettings(type: DNSType) { if let settings = dnsSettings.first { settings.isCloudflareEnabled = (type == .cloudflare) settings.isQuad9Enabled = (type == .quad9) settings.isAdGuardEnabled = type == .adguard ? true : nil if case .getflix(let location) = type { settings.activeGetFlixLocation = location } else { settings.activeGetFlixLocation = nil } if case .custom(let server) = type { settings.activeCustomDNSID = server.id } else { settings.activeCustomDNSID = nil } settings.timestamp = Date() } } private func ensureSettingsExist() { if dnsSettings.isEmpty { modelContext.insert(DNSSettings()) try? modelContext.save() } } private func clearDNSCache() { if !isUpdating && !isSpeedTesting { isUpdating = true DNSManager.shared.clearDNSCache { success in DispatchQueue.main.async { self.isUpdating = false } } } } } ================================================ FILE: DNS Easy Switcher/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: DNS Easy Switcher.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 9165B6682D6B616D00DD9643 /* DNS_Easy_SwitcherApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B6672D6B616D00DD9643 /* DNS_Easy_SwitcherApp.swift */; }; 9165B66A2D6B616D00DD9643 /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B6692D6B616D00DD9643 /* MenuBarView.swift */; }; 9165B66C2D6B616D00DD9643 /* DNSSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B66B2D6B616D00DD9643 /* DNSSettings.swift */; }; 9165B66E2D6B616D00DD9643 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9165B66D2D6B616D00DD9643 /* Assets.xcassets */; }; 9165B6712D6B616D00DD9643 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9165B6702D6B616D00DD9643 /* Preview Assets.xcassets */; }; 9165B6792D6B61C500DD9643 /* MenuBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B6782D6B61C500DD9643 /* MenuBarController.swift */; }; 9165B67B2D6B62CC00DD9643 /* DNSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B67A2D6B62CC00DD9643 /* DNSManager.swift */; }; 9165B67D2D6B699A00DD9643 /* AddCustomDNSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B67C2D6B699A00DD9643 /* AddCustomDNSView.swift */; }; 9165B67F2D6B6D1800DD9643 /* CustomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9165B67E2D6B6D1800DD9643 /* CustomSheet.swift */; }; 91FFB82B2D6E6619008C0471 /* CustomDNSManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FFB82A2D6E6619008C0471 /* CustomDNSManagerView.swift */; }; 91FFB82D2D6E6639008C0471 /* EditCustomDNSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FFB82C2D6E6639008C0471 /* EditCustomDNSView.swift */; }; 91FFB82F2D6E6868008C0471 /* DNSSpeedTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FFB82E2D6E6868008C0471 /* DNSSpeedTester.swift */; }; 91FFB9002D70E000008C0471 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FFB8FF2D70E000008C0471 /* AboutView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 9165B6642D6B616D00DD9643 /* DNS Easy Switcher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DNS Easy Switcher.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 9165B6672D6B616D00DD9643 /* DNS_Easy_SwitcherApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNS_Easy_SwitcherApp.swift; sourceTree = ""; }; 9165B6692D6B616D00DD9643 /* MenuBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = ""; }; 9165B66B2D6B616D00DD9643 /* DNSSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSSettings.swift; sourceTree = ""; }; 9165B66D2D6B616D00DD9643 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9165B6702D6B616D00DD9643 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 9165B6722D6B616D00DD9643 /* DNS_Easy_Switcher.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNS_Easy_Switcher.entitlements; sourceTree = ""; }; 9165B6782D6B61C500DD9643 /* MenuBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarController.swift; sourceTree = ""; }; 9165B67A2D6B62CC00DD9643 /* DNSManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSManager.swift; sourceTree = ""; }; 9165B67C2D6B699A00DD9643 /* AddCustomDNSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCustomDNSView.swift; sourceTree = ""; }; 9165B67E2D6B6D1800DD9643 /* CustomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSheet.swift; sourceTree = ""; }; 91FFB82A2D6E6619008C0471 /* CustomDNSManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNSManagerView.swift; sourceTree = ""; }; 91FFB82C2D6E6639008C0471 /* EditCustomDNSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDNSView.swift; sourceTree = ""; }; 91FFB82E2D6E6868008C0471 /* DNSSpeedTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSSpeedTester.swift; sourceTree = ""; }; 91FFB8FF2D70E000008C0471 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9165B6612D6B616D00DD9643 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9165B65B2D6B616D00DD9643 = { isa = PBXGroup; children = ( 9165B6662D6B616D00DD9643 /* DNS Easy Switcher */, 9165B6652D6B616D00DD9643 /* Products */, ); sourceTree = ""; }; 9165B6652D6B616D00DD9643 /* Products */ = { isa = PBXGroup; children = ( 9165B6642D6B616D00DD9643 /* DNS Easy Switcher.app */, ); name = Products; sourceTree = ""; }; 9165B6662D6B616D00DD9643 /* DNS Easy Switcher */ = { isa = PBXGroup; children = ( 9165B6672D6B616D00DD9643 /* DNS_Easy_SwitcherApp.swift */, 9165B6692D6B616D00DD9643 /* MenuBarView.swift */, 9165B66B2D6B616D00DD9643 /* DNSSettings.swift */, 9165B66D2D6B616D00DD9643 /* Assets.xcassets */, 9165B6722D6B616D00DD9643 /* DNS_Easy_Switcher.entitlements */, 9165B66F2D6B616D00DD9643 /* Preview Content */, 9165B6782D6B61C500DD9643 /* MenuBarController.swift */, 9165B67A2D6B62CC00DD9643 /* DNSManager.swift */, 9165B67C2D6B699A00DD9643 /* AddCustomDNSView.swift */, 9165B67E2D6B6D1800DD9643 /* CustomSheet.swift */, 91FFB82A2D6E6619008C0471 /* CustomDNSManagerView.swift */, 91FFB82C2D6E6639008C0471 /* EditCustomDNSView.swift */, 91FFB82E2D6E6868008C0471 /* DNSSpeedTester.swift */, 91FFB8FF2D70E000008C0471 /* AboutView.swift */, ); path = "DNS Easy Switcher"; sourceTree = ""; }; 9165B66F2D6B616D00DD9643 /* Preview Content */ = { isa = PBXGroup; children = ( 9165B6702D6B616D00DD9643 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 9165B6632D6B616D00DD9643 /* DNS Easy Switcher */ = { isa = PBXNativeTarget; buildConfigurationList = 9165B6752D6B616D00DD9643 /* Build configuration list for PBXNativeTarget "DNS Easy Switcher" */; buildPhases = ( 9165B6602D6B616D00DD9643 /* Sources */, 9165B6612D6B616D00DD9643 /* Frameworks */, 9165B6622D6B616D00DD9643 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "DNS Easy Switcher"; productName = "DNS Easy Switcher"; productReference = 9165B6642D6B616D00DD9643 /* DNS Easy Switcher.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 9165B65C2D6B616D00DD9643 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { 9165B6632D6B616D00DD9643 = { CreatedOnToolsVersion = 15.0.1; }; }; }; buildConfigurationList = 9165B65F2D6B616D00DD9643 /* Build configuration list for PBXProject "DNS Easy Switcher" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 9165B65B2D6B616D00DD9643; productRefGroup = 9165B6652D6B616D00DD9643 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 9165B6632D6B616D00DD9643 /* DNS Easy Switcher */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 9165B6622D6B616D00DD9643 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9165B6712D6B616D00DD9643 /* Preview Assets.xcassets in Resources */, 9165B66E2D6B616D00DD9643 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9165B6602D6B616D00DD9643 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9165B6792D6B61C500DD9643 /* MenuBarController.swift in Sources */, 9165B67F2D6B6D1800DD9643 /* CustomSheet.swift in Sources */, 9165B67D2D6B699A00DD9643 /* AddCustomDNSView.swift in Sources */, 9165B66A2D6B616D00DD9643 /* MenuBarView.swift in Sources */, 91FFB82B2D6E6619008C0471 /* CustomDNSManagerView.swift in Sources */, 9165B66C2D6B616D00DD9643 /* DNSSettings.swift in Sources */, 91FFB82F2D6E6868008C0471 /* DNSSpeedTester.swift in Sources */, 9165B67B2D6B62CC00DD9643 /* DNSManager.swift in Sources */, 91FFB82D2D6E6639008C0471 /* EditCustomDNSView.swift in Sources */, 91FFB9002D70E000008C0471 /* AboutView.swift in Sources */, 9165B6682D6B616D00DD9643 /* DNS_Easy_SwitcherApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 9165B6732D6B616D00DD9643 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 9165B6742D6B616D00DD9643 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; 9165B6762D6B616D00DD9643 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = "DNS Easy Switcher/DNS_Easy_Switcher.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"DNS Easy Switcher/Preview Content\""; DEVELOPMENT_TEAM = 6645MJMX63; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "DNS Easy Switcher"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0.7; PRODUCT_BUNDLE_IDENTIFIER = com.linfordsoftware.dnseasyswitcher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Debug; }; 9165B6772D6B616D00DD9643 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = "DNS Easy Switcher/DNS_Easy_Switcher.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"DNS Easy Switcher/Preview Content\""; DEVELOPMENT_TEAM = 6645MJMX63; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "DNS Easy Switcher"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0.7; PRODUCT_BUNDLE_IDENTIFIER = com.linfordsoftware.dnseasyswitcher; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9165B65F2D6B616D00DD9643 /* Build configuration list for PBXProject "DNS Easy Switcher" */ = { isa = XCConfigurationList; buildConfigurations = ( 9165B6732D6B616D00DD9643 /* Debug */, 9165B6742D6B616D00DD9643 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 9165B6752D6B616D00DD9643 /* Build configuration list for PBXNativeTarget "DNS Easy Switcher" */ = { isa = XCConfigurationList; buildConfigurations = ( 9165B6762D6B616D00DD9643 /* Debug */, 9165B6772D6B616D00DD9643 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 9165B65C2D6B616D00DD9643 /* Project object */; } ================================================ FILE: DNS Easy Switcher.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: DNS Easy Switcher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: DNS Easy Switcher.xcodeproj/xcshareddata/xcschemes/DNS Easy Switcher.xcscheme ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Greg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # DNS Easy Switcher A simple macOS menu bar app that allows you to quickly switch between different DNS providers (or add custom ones). ![Screenshot of DNS Easy Switcher](screenshot.png) ![downloads](https://img.shields.io/github/downloads/glinford/dns-easy-switcher/total) ## Features - Easy switching between popular DNS providers: - Cloudflare DNS (1.1.1.1) - Quad9 DNS (9.9.9.9) - AdGuard DNS (94.140.14.14) - GetFlix DNS (with list of all locations) - Disable DNS Overrides (DHCP-Provided DNS) - Add and manage your own custom DNS servers - Test DNS speed to find the fastest provider - Flush DNS Cache - Touch ID authentication for DNS changes - Native macOS menu bar integration - Persists your settings between app launches - IPv4 and IPv6 support ## Installation ### Using Homebrew (Recommended) Install DNS Easy Switcher with Homebrew using these commands: ```bash brew tap glinford/tap brew install --cask dns-easy-switcher ``` To update to the latest version when available: ```bash brew upgrade --cask dns-easy-switcher ``` ### Manual Installation 1. Download the latest release from the [Releases](../../releases) page 2. Mount the DMG file 3. Drag DNS Easy Switcher to your Applications folder 4. Launch DNS Easy Switcher from Applications ## First Launch Since DNS Easy Switcher is distributed outside the Mac App Store, macOS may show a security warning when you first launch it. To allow the app to run: 1. Right-click (or Control-click) on DNS Easy Switcher in your Applications folder 2. Select "Open" from the context menu 3. Click "Open" in the dialog that appears 4. Allow system extensions when prompted (required for DNS changes) ![settings](settings.png) ## Important Note Due to macOS security requirements, administrator privileges are required each time you switch DNS settings. If you have a Touch ID-enabled Mac, you can now use Touch ID instead of password entry for authentication. ## Requirements - macOS 14.0 (Sonoma) or later - Administrator privileges (required for changing DNS settings) - Touch ID compatible Mac (for Touch ID authentication feature) ## Tested Configurations | macOS Version | Status | |--------------|--------| | Sonoma 14.5 | ✅ | ## Building from Source 1. Clone the repository: ```bash git clone https://github.com/glinford/dns-easy-switcher.git ``` 2. Open the project in Xcode 15 or later 3. Build and run the project ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## Acknowledgments - [Cloudflare DNS](https://1.1.1.1) for their public DNS service - [Quad9](https://quad9.net) for their secure DNS service - [AdGuard DNS](https://adguard-dns.io/en/welcome.html) for their privacy-focused DNS service with ad blocking capabilities - [GetFlix](https://www.getflix.com.au/setup/dns-servers/) ## Privacy DNS Easy Switcher does not collect any data. All settings are stored locally on your device. ================================================ FILE: Releases/DNS Easy Switcher v1.0.0/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.0/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.0/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.1/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.1 CFBundleSupportedPlatforms MacOSX CFBundleVersion 2 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.1/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.1/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.2/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.2 CFBundleSupportedPlatforms MacOSX CFBundleVersion 2 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.2/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.2/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.3 CFBundleSupportedPlatforms MacOSX CFBundleVersion 4 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/DistributionSummary.plist ================================================ DNS Easy Switcher architectures x86_64 arm64 buildNumber 4 certificate SHA1 8FB842802AF0A81CD08194DB5E6CA6B5FB771823 dateExpires 26/02/2030 type Developer ID Application name DNS Easy Switcher.app team id 6645MJMX63 name Gregory Linford versionNumber 1.0.3 ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/ExportOptions.plist ================================================ destination export method developer-id signingStyle automatic teamID 6645MJMX63 ================================================ FILE: Releases/DNS Easy Switcher v1.0.3/Packaging.log ================================================ 2025-02-25 22:00:11 +0000 Initial pipeline context: Chain (13, self inclusive): 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCreateDestRootStep 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCopyItemStep 2025-02-25 22:00:11 +0000 Running /usr/bin/ditto '-V' '/Users/glinf/Library/Developer/Xcode/Archives/2025-02-25/DNS Easy Switcher 25-02-2025, 22.59.xcarchive/Products/Applications/DNS Easy Switcher.app' '/var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/Root/Applications/DNS Easy Switcher.app' 2025-02-25 22:00:11 +0000 >>> Copying /Users/glinf/Library/Developer/Xcode/Archives/2025-02-25/DNS Easy Switcher 25-02-2025, 22.59.xcarchive/Products/Applications/DNS Easy Switcher.app 2025-02-25 22:00:11 +0000 copying file ./Contents/_CodeSignature/CodeResources ... 2025-02-25 22:00:11 +0000 2672 bytes for ./Contents/_CodeSignature/CodeResources 2025-02-25 22:00:11 +0000 copying file ./Contents/MacOS/DNS Easy Switcher ... 2025-02-25 22:00:11 +0000 664176 bytes for ./Contents/MacOS/DNS Easy Switcher 2025-02-25 22:00:11 +0000 copying file ./Contents/Resources/AppIcon.icns ... 2025-02-25 22:00:11 +0000 67535 bytes for ./Contents/Resources/AppIcon.icns 2025-02-25 22:00:11 +0000 copying file ./Contents/Resources/Assets.car ... 2025-02-25 22:00:11 +0000 745832 bytes for ./Contents/Resources/Assets.car 2025-02-25 22:00:11 +0000 copying file ./Contents/Info.plist ... 2025-02-25 22:00:11 +0000 1572 bytes for ./Contents/Info.plist 2025-02-25 22:00:11 +0000 copying file ./Contents/PkgInfo ... 2025-02-25 22:00:11 +0000 8 bytes for ./Contents/PkgInfo 2025-02-25 22:00:11 +0000 /usr/bin/ditto exited with 0 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionEmbedProfileStep 2025-02-25 22:00:11 +0000 Skipping profile for item: ', codeSigningInfo='<_DVTCodeSigningInformation_Path: 0x60000ca58eb0; isSigned='1', isAdHocSigned='0', signingCertificate='', entitlements='{ }', teamID='6645MJMX63', identifier='com.linfordsoftware.dnseasyswitcher', executablePath='', hardenedRuntime='1'>'> 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionInfoPlistStep 2025-02-25 22:00:11 +0000 Skipping step: IDEDistributionInfoPlistStep because it said so 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionAppThinningPlistStep 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCompileBitcodeStep 2025-02-25 22:00:11 +0000 Skipping step: IDEDistributionCompileBitcodeStep because it said so 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCodeSlimmingStep 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCopyBCSymbolMapsStep 2025-02-25 22:00:11 +0000 Skipping step: IDEDistributionCopyBCSymbolMapsStep because it said so 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionSymbolsStep 2025-02-25 22:00:11 +0000 Skipping step: IDEDistributionSymbolsStep because it said so 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionAppThinningStep 2025-02-25 22:00:11 +0000 Skipping step: IDEDistributionAppThinningStep because it said so 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionArchThinningStep 2025-02-25 22:00:11 +0000 Running /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo '/var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/Root/Applications/DNS Easy Switcher.app/Contents/MacOS/DNS Easy Switcher' '-verify_arch' 'arm64e' 2025-02-25 22:00:11 +0000 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo exited with 1 2025-02-25 22:00:11 +0000 Skipping architecture thinning for item "DNS Easy Switcher" because arch "arm64e" wasn't found 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionODRStep 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionStripXattrsStep 2025-02-25 22:00:11 +0000 Skipping stripping extended attributes because the codesign step will strip them. 2025-02-25 22:00:11 +0000 Processing step: IDEDistributionCodesignStep 2025-02-25 22:00:11 +0000 Entitlements for ', codeSigningInfo='<_DVTCodeSigningInformation_Path: 0x60000ca58eb0; isSigned='1', isAdHocSigned='0', signingCertificate='', entitlements='{ }', teamID='6645MJMX63', identifier='com.linfordsoftware.dnseasyswitcher', executablePath='', hardenedRuntime='1'>'>: { } 2025-02-25 22:00:11 +0000 Associated App Clip Identifiers Filter: Skipping because "com.apple.developer.associated-appclip-app-identifiers" is not present 2025-02-25 22:00:11 +0000 Entitlements for ', codeSigningInfo='<_DVTCodeSigningInformation_Path: 0x60000ca58eb0; isSigned='1', isAdHocSigned='0', signingCertificate='', entitlements='{ }', teamID='6645MJMX63', identifier='com.linfordsoftware.dnseasyswitcher', executablePath='', hardenedRuntime='1'>'> are: { } 2025-02-25 22:00:11 +0000 Writing entitlements for ', codeSigningInfo='<_DVTCodeSigningInformation_Path: 0x60000ca58eb0; isSigned='1', isAdHocSigned='0', signingCertificate='', entitlements='{ }', teamID='6645MJMX63', identifier='com.linfordsoftware.dnseasyswitcher', executablePath='', hardenedRuntime='1'>'> to: /var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/entitlements~~~Eyl0xd 2025-02-25 22:00:11 +0000 Running /usr/bin/codesign '-vvv' '--force' '--sign' '8FB842802AF0A81CD08194DB5E6CA6B5FB771823' '--entitlements' '/var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/entitlements~~~Eyl0xd' '--generate-entitlement-der' '--preserve-metadata=identifier,flags,runtime' '--requirements' '=designated => anchor apple generic and identifier "$self.identifier" and ((cert leaf[field.1.2.840.113635.100.6.1.9] exists) or ( certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and certificate leaf[subject.OU] = "6645MJMX63" ))' '/var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/Root/Applications/DNS Easy Switcher.app' 2025-02-25 22:00:11 +0000 /var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/Root/Applications/DNS Easy Switcher.app: replacing existing signature 2025-02-25 22:00:23 +0000 /var/folders/33/r3mtzdn102x40vpjzrzm9kyh0000gn/T/XcodeDistPipeline.~~~8zKmE7/Root/Applications/DNS Easy Switcher.app: signed app bundle with Mach-O universal (x86_64 arm64) [com.linfordsoftware.dnseasyswitcher] 2025-02-25 22:00:23 +0000 /usr/bin/codesign exited with 0 2025-02-25 22:00:23 +0000 Processing step: IDEDistributionZipODRItemStep 2025-02-25 22:00:23 +0000 Skipping step: IDEDistributionZipODRItemStep because it said so 2025-02-25 22:00:23 +0000 Processing step: IDEDistributionSkipPackagingStep 2025-02-25 22:00:23 +0000 Processing step: IDEDistributionAppStoreInformationStep 2025-02-25 22:00:23 +0000 Skipping step: IDEDistributionAppStoreInformationStep because it said so 2025-02-25 22:00:23 +0000 Processing step: IDEDistributionGenerateProcessedDistributionItems 2025-02-25 22:00:23 +0000 IDEDistributionItem init 2025-02-25 22:00:23 +0000 [OPTIONAL] Didn't find embedded provisioning profile for : Error Domain=NSCocoaErrorDomain Code=4 "No file at " UserInfo={NSLocalizedDescription=No file at } 2025-02-25 22:00:23 +0000 Processing step: IDEDistributionCreateManifestStep 2025-02-25 22:00:23 +0000 Skipping step: IDEDistributionCreateManifestStep because it said so ================================================ FILE: Releases/DNS Easy Switcher v1.0.4/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.4 CFBundleSupportedPlatforms MacOSX CFBundleVersion 4 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.4/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.4/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.5/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.5 CFBundleSupportedPlatforms MacOSX CFBundleVersion 5 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.5/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.5/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.6/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 23F79 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.5 CFBundleSupportedPlatforms MacOSX CFBundleVersion 7 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild DTPlatformName macosx DTPlatformVersion 14.0 DTSDKBuild 23A334 DTSDKName macosx14.0 DTXcode 1501 DTXcodeBuild 15A507 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.6/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.6/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car IhhuUihlv8TNfeE5CB9OZcOX21w= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 /sNzrOXugwAIyPdIJZkIv1275OZELTMDgWfU3DNwkwQ= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20 ================================================ FILE: Releases/DNS Easy Switcher v1.0.7/DNS Easy Switcher.app/Contents/Info.plist ================================================ BuildMachineOSBuild 24F74 CFBundleDevelopmentRegion en CFBundleDisplayName DNS Easy Switcher CFBundleExecutable DNS Easy Switcher CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.linfordsoftware.dnseasyswitcher CFBundleInfoDictionaryVersion 6.0 CFBundleName DNS Easy Switcher CFBundlePackageType APPL CFBundleShortVersionString 1.0.7 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 24F74 DTPlatformName macosx DTPlatformVersion 15.5 DTSDKBuild 24F74 DTSDKName macosx15.5 DTXcode 1640 DTXcodeBuild 16F6 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 14.0 ================================================ FILE: Releases/DNS Easy Switcher v1.0.7/DNS Easy Switcher.app/Contents/PkgInfo ================================================ APPL???? ================================================ FILE: Releases/DNS Easy Switcher v1.0.7/DNS Easy Switcher.app/Contents/_CodeSignature/CodeResources ================================================ files Resources/AppIcon.icns F6YGlmox4KixBMRrSRhTusxdld8= Resources/Assets.car 3jqFr5HGKA9n2WOV5/Xl44NZRrQ= files2 Resources/AppIcon.icns hash2 I0wlcCvBzQjHkKQ1llwB0poCp2bmfglbZW45inQqQV0= Resources/Assets.car hash2 YYZb1v3omIkmYQEJ3ZIxWpYeBvtIZn01EGvVrO8Dau4= rules ^Resources/ ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^version.plist$ rules2 .*\.dSYM($|/) weight 11 ^(.*/)?\.DS_Store$ omit weight 2000 ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ nested weight 10 ^.* ^Info\.plist$ omit weight 20 ^PkgInfo$ omit weight 20 ^Resources/ weight 20 ^Resources/.*\.lproj/ optional weight 1000 ^Resources/.*\.lproj/locversion.plist$ omit weight 1100 ^Resources/Base\.lproj/ weight 1010 ^[^/]+$ nested weight 10 ^embedded\.provisionprofile$ weight 20 ^version\.plist$ weight 20