diff --git a/Sources/CLI Components/getApps.swift b/Sources/CLI Components/getApps.swift index 105e6d9..6a219da 100644 --- a/Sources/CLI Components/getApps.swift +++ b/Sources/CLI Components/getApps.swift @@ -22,6 +22,6 @@ class GetApps: Command { if let output = copyStringArrayAsString(LSWrappers.copyAllApps()) { print(output) } - else { throw CLIError.error("There was an error generating the list of applications.") } + else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate the list of installed applications.") } } } diff --git a/Sources/CLI Components/getHandler.swift b/Sources/CLI Components/getHandler.swift index 657aa75..5c00506 100644 --- a/Sources/CLI Components/getHandler.swift +++ b/Sources/CLI Components/getHandler.swift @@ -102,6 +102,6 @@ class ReadCommand: OptionCommand { if (nil != handler) { print(handler!) - } else { throw CLIError.error(("An incompatible combination was used, or no application is registered to handle \(arg)")) } + } else { throw CLIError.error(("SwiftDefaultApps ERROR: An incompatible combination was used, or no application is registered to handle \(arg)")) } } } diff --git a/Sources/CLI Components/getUTIs.swift b/Sources/CLI Components/getUTIs.swift index 64b61e5..362ce5a 100644 --- a/Sources/CLI Components/getUTIs.swift +++ b/Sources/CLI Components/getUTIs.swift @@ -21,7 +21,7 @@ class GetUTIs: Command { if let output = copyDictionaryAsString(LSWrappers.UTType.copyAllUTIs().sorted(by: { $0.0 < $1.0 })) { print(output) } - else { throw CLIError.error("There was an error generating the list.") } + else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate list of UTIs") } } } diff --git a/Sources/CLI Components/setHandler.swift b/Sources/CLI Components/setHandler.swift index 189fa47..18d9af6 100644 --- a/Sources/CLI Components/setHandler.swift +++ b/Sources/CLI Components/setHandler.swift @@ -83,7 +83,8 @@ class SetCommand: OptionCommand { statusCode = kLSUnknownErr break } - if (statusCode == 0) { print("Default handler has succesfully changed to \(bundleID!).") } - else { throw CLIError.error(LSWrappers.LSErrors.init(value: statusCode).print(argument: (app: inApplication, content: self.contentType!))) } + do { + try displayAlert(error: statusCode, arg1: (bundleID != nil ? bundleID : inApplication), arg2: self.contentType!) + } catch { print(error) } } } diff --git a/Sources/Common Sources/LSWrappers.swift b/Sources/Common Sources/LSWrappers.swift index cda17f7..a4d4dd0 100644 --- a/Sources/Common Sources/LSWrappers.swift +++ b/Sources/Common Sources/LSWrappers.swift @@ -323,14 +323,34 @@ class LSWrappers { - Returns: `true` if the bundle identifier is registered with Launch Services as an application, `false` otherwise. */ static func isAppInstalled (withBundleID: String) -> Bool { - let temp = withBundleID as CFString - - if (LSCopyApplicationURLsForBundleIdentifier(temp,nil)?.takeRetainedValue() as NSArray?) != nil { - return true - } - else { - return false - } + if (withBundleID == "cl.fail.lordkamina.ThisAppDoesNothing" && NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: "cl.fail.lordkamina.ThisAppDoesNothing") == nil) { + if (!areWeCLI() && prefPaneLocation() == nil) { return false } + let appSearchPaths: [URL] = [ areWeCLI() ? Bundle.main.bundleURL : Bundle(url: prefPaneLocation()!)!.resourceURL! ] + + FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationDirectory, in: FileManager.SearchPathDomainMask.allDomainsMask) + var appPath: URL? + for path in appSearchPaths { + let appURL = path.appendingPathComponent("ThisAppDoesNothing.app").absoluteURL + if (FileManager.default.isExecutableFile(atPath: appURL.path) == true) { + appPath = appURL + break + } + } + guard appPath != nil else { + return false + } + do { + let ranApp = try NSWorkspace.shared.launchApplication(at: appPath!, options: [NSWorkspace.LaunchOptions.withoutAddingToRecents], configuration: [:]) + if (ranApp.processIdentifier == -1) { + return false + } + else { return true } + } + catch { + return false + } + } + let retval: Bool = ((LSCopyApplicationURLsForBundleIdentifier(withBundleID as CFString,nil)?.takeRetainedValue() as NSArray?) != nil) + return retval } /** Performs a myriad of sanity checks on user input corresponding to a possible application. The main purpose of this function is to make sure we're passing a value as sane as possible to the setHandler functions. diff --git a/Sources/Common Sources/commonFuncs.swift b/Sources/Common Sources/commonFuncs.swift index 346d7f2..cee6617 100644 --- a/Sources/Common Sources/commonFuncs.swift +++ b/Sources/Common Sources/commonFuncs.swift @@ -80,6 +80,8 @@ prefix func /(pattern:String) -> NSRegularExpression? { //// EXTENSIONS +extension String: Error {} + extension DispatchQueue { static let labelPrefix = "io.zamzam.ZamzamKit" static let database = DispatchQueue(label: "\(DispatchQueue.labelPrefix).database", qos: .utility) @@ -232,6 +234,75 @@ extension Dictionary } #endif +/** Determines whether the app is running in Prefpane or CLI form by checking the Main Bundle Identifier. */ +func areWeCLI() -> Bool { + return (Bundle.main.bundleIdentifier == nil) +} + +/** Determines the URL for the location of the SwiftDefaultApps prefpane, is any. */ +func prefPaneLocation() -> URL? { + if let bundle = Bundle(identifier:"cl.fail.lordkamina.SwiftDefaultApps") { + return bundle.bundleURL + } + let prefpaneSearchPaths: [URL] = FileManager.default.urls(for: FileManager.SearchPathDirectory.preferencePanesDirectory, in: FileManager.SearchPathDomainMask.allDomainsMask) + for path in prefpaneSearchPaths { + if (FileManager.default.isExecutableFile(atPath: path.appendingPathComponent("SwiftDefaultApps.prefpane").absoluteURL.path) == true) { + return path.appendingPathComponent("SwiftDefaultApps.prefpane").absoluteURL + } + } + return nil +} + +/** + Display a modal alert + - Parameter error: A numeric value indicating the error code as per LSWrappers.LSErrors. + - Parameter arg1: String containing info pertaining to the error. + - Parameter arg2: String containing info pertaining to the error. +*/ +func displayAlert(error: OSStatus = Int32.min, arg1: String?, arg2: String?) throws { + if (error != 0 && (arg1 == nil || arg2 == nil)) { throw ("Arguments cannot be nil when displaying an error.") } + if (!areWeCLI()) { + let alert = NSAlert() + alert.icon = NSWorkspace.shared.icon(forFile: Bundle(identifier: "cl.fail.lordkamina.SwiftDefaultApps")!.bundlePath) + alert.addButton(withTitle: "OK") + if (error != Int32.min) { + alert.informativeText = LSWrappers.LSErrors(value: error).print(argument: (app: arg1, content: arg2)) + + switch (Int(error)) { + case Int(errSecInvalidBundleInfo)..<0: + alert.messageText = "Error" + alert.alertStyle = .critical + case 0: + alert.messageText = "Success" + alert.alertStyle = .informational + case 1...:alert.messageText = "Success" + alert.alertStyle = .informational + default: + alert.messageText = "Warning" + alert.alertStyle = .warning + } + } + else { + alert.informativeText = "SwiftDefaultApps ERROR: Called displayAlert() with an undefined error code." + } + DispatchQueue.main.async { + alert.runModal() + } + } + else { + if (error < 0) { + throw(LSWrappers.LSErrors.init(value: error).print(argument: (app: arg1, content: arg2))) + } + else if (error == 0) { + print(LSWrappers.LSErrors.init(value: error).print(argument: (app: arg1, content: arg2))) + } + else if (error == Int32.min) { + guard (arg1 != nil) else { throw("SwiftDefaultApps ERROR: Called displayAlert() with an undefined error code and message.") } + print(arg1!) + } + } +} + /** NSFont extensions to style the different kinds of row in the NSTreeView. */ extension NSFont { /** Returns a SmallCaps version of the font it is invoked on. */ diff --git a/Sources/Prefpane Sources/View Controllers/SWDATreeRow.swift b/Sources/Prefpane Sources/View Controllers/SWDATreeRow.swift index fb211e5..6959d92 100644 --- a/Sources/Prefpane Sources/View Controllers/SWDATreeRow.swift +++ b/Sources/Prefpane Sources/View Controllers/SWDATreeRow.swift @@ -105,26 +105,17 @@ internal class SWDATreeRow:NSObject { let type = content.contentType var status = OSStatus() - if let bundleID = self.rowContent?.application?.appBundleID { - status = (type == .URL) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!)) - let alert = NSAlert() - alert.informativeText = (status == 0) ? "Succesfully changed default handler for \(self.rowTitle) to \(self.rowContent?.application?.displayName ?? "Invalid App")" : LSWrappers.LSErrors(value: status).print(argument: (app: (self.rowContent?.application?.displayName)!, content: self.rowTitle)) - alert.icon = ControllersRef.appIcon - alert.messageText = (status == 0) ? "Success" : "Error" - alert.alertStyle = (status == 0) ? .informational : .critical - alert.addButton(withTitle: "OK") - DispatchQueue.main.async { - if let parent = self.parentNode { - for node in parent.children { - node.willChangeValue(forKey: "isDefaultHandler") - node.didChangeValue(forKey: "isDefaultHandler") - } - } - DispatchQueue.main.async { - alert.runModal() - } - } - } + if let bundleID = self.rowContent?.application?.appBundleID { + + status = (type == .URI) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!)) + try! displayAlert(error: status, arg1: (self.rowContent?.application?.displayName), arg2: self.rowTitle) + if let parent = self.parentNode { + for node in parent.children { + node.willChangeValue(forKey: "isDefaultHandler") + node.didChangeValue(forKey: "isDefaultHandler") + } + } + } } } } @@ -149,27 +140,16 @@ internal class SWDATreeRow:NSObject { let contentName = content.contentName let type = content.contentType var status = OSStatus() - if let bundleID = self.rowContent?.application?.appBundleID { - - status = (type == .URL) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!)) - let alert = NSAlert() - alert.informativeText = (status == 0) ? "Succesfully changed default handler for \(content.displayName) to \(self.rowTitle)" : LSWrappers.LSErrors(value: status).print(argument: (app: self.rowTitle, content: contentName)) - alert.icon = ControllersRef.appIcon - alert.messageText = (status == 0) ? "Success" : "Error" - alert.alertStyle = (status == 0) ? .informational : .critical - alert.addButton(withTitle: "OK") - DispatchQueue.main.async { - if let parent = self.parentNode { - for node in parent.children { - node.willChangeValue(forKey: "isDefaultHandler") - node.didChangeValue(forKey: "isDefaultHandler") - } - } - DispatchQueue.main.async { - alert.runModal() - } - } - } + if let bundleID = self.rowContent?.application?.appBundleID { + status = (type == .URI) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!)) + try! displayAlert(error: status, arg1: (self.rowContent?.application?.displayName), arg2: self.rowTitle) + if let parent = self.parentNode { + for node in parent.children { + node.willChangeValue(forKey: "isDefaultHandler") + node.didChangeValue(forKey: "isDefaultHandler") + } + } + } else if (self.rowTitle == "Other...") { let openpanel = NSOpenPanel() openpanel.treatsFilePackagesAsDirectories = false diff --git a/Sources/Prefpane Sources/View Controllers/Subclasses.swift b/Sources/Prefpane Sources/View Controllers/Subclasses.swift index 490d679..81baaf8 100644 --- a/Sources/Prefpane Sources/View Controllers/Subclasses.swift +++ b/Sources/Prefpane Sources/View Controllers/Subclasses.swift @@ -111,19 +111,9 @@ class SWDATabTemplate: DRYView { SWDAHandlersModel.setValue(nil, forKey: "allSchemes") self.setValue(nil, forKey: "contentArrayStore") } - else { - let alert = NSAlert() - alert.informativeText = LSWrappers.LSErrors(value: result).print(argument: (app: "Do Nothing", content: customNewScheme!.stringValue)) - alert.messageText = "Error" - alert.icon = ControllersRef.appIcon - alert.alertStyle = .critical - alert.addButton(withTitle: "OK") - DispatchQueue.main.async { - alert.runModal() - } + try! displayAlert(error: result, arg1: "Do Nothing", arg2: customNewScheme!.stringValue) return } - } /** Identifies which tab the current instance belongs to. */ var tabIndex: Int? = -1