31 Commits
Author SHA1 Message Date
Gregorio Litenstein 4672a7f028 Fix reference to SwiftCLI package. 2024-03-07 15:02:11 -03:00
Gregorio Litenstein 19bd0836d6 Resuming development 2023-08-17 17:42:54 -04:00
Werner Robitza c35002a671 fix formatting 2023-04-19 00:22:42 -04:00
Gregorio LitensteinandJohn Bachir 6fb3fadc6e Update README.md
Co-authored-by: John Bachir <jjb@users.noreply.github.com>
2022-08-06 19:34:38 -04:00
Joris Berthelot 8cb414a906 Add steps to have the Do Nothing app working.
Close https://github.com/Lord-Kamina/SwiftDefaultApps/issues/12
2022-08-06 19:34:38 -04:00
Andrew Udvare a2f3dbac3c Fix Swift error 2022-08-06 19:33:25 -04:00
Muescha 8fa723ef79 add info about brew install 2022-08-06 19:32:48 -04:00
Gregorio Litenstein 665c721bfd Merge pull request #41 from glaucocustodio/patch-1
Update README.md
2020-10-16 09:47:47 -03:00
Glauco Custódio bd59579501 Update README.md
I was missing this info, it might be useful for other people too.
2020-10-16 11:38:29 +01:00
Gregorio Litenstein 86d6666db1 Update project settings. 2019-07-28 22:49:15 -04:00
Gregorio Litenstein 64c1f81492 Version bump and update README.md. 2019-07-26 00:56:24 -04:00
Gregorio Litenstein 0dd7b0be76 SwiftDefaultApps CLI: Fix “Illegal instruction: 4”
Fix crash caused by forcedly unwrapping some optionals when printing the results of a command.
2019-07-26 00:43:46 -04:00
Gregorio Litenstein b02592772b Bump version and update README.md 2019-06-17 00:51:05 -04:00
Gregorio Litenstein 439f215109 SwitCLI: Build static lib instead of framework.
Also, homogenize all xcode projects to 9.3-compatible format.
2019-06-17 00:51:05 -04:00
Gregorio Litenstein 54dd71fcce Include CLI in Release archive. 2019-06-17 00:51:05 -04:00
Gregorio Litenstein ffc83b2481 Sign targets 2019-06-17 00:51:05 -04:00
Gregorio Litenstein f4010abe44 Re-indent 2019-06-17 00:51:05 -04:00
Gregorio Litenstein 41733bf973 Little clean-up of SWDAHandlersModel. 2019-06-17 00:51:05 -04:00
Gregorio Litenstein 10288a3810 Prefpane main: Kill arbitrary timer
Override didSelect() instead to load content when first launched.
2019-06-17 00:51:05 -04:00
Gregorio Litenstein 3a09834f5a Better error management + refactoring
- Implement a function to determine at runtime whether we’re running in CLI or as a prefpane.
- More verbose errors in both cases.
- Try to account for ThisAppDoesNothing appearing unknown to LaunchServices by manually trying to launch it from one of several locations it might be found. (Should fix #9 and #12)
- Fold all errors and success messages into a single displayAlert() function used both in CLI and GUI versions.
2019-06-17 00:51:04 -04:00
Gregorio Litenstein 224d24e260 LSWrappers: Refactoring and minor optimization.
- Account for some additional errors (as well as no error)
- Sort error cases (sort of) by their value
- Add some extra info to LSerror.print
- Minor code clean-up
2019-06-17 00:46:23 -04:00
Gregorio Litenstein 28f21667d2 Updated the SynchronizedArray code. 2019-06-17 00:46:23 -04:00
Gregorio Litenstein dcafe50852 Text Fixes.
- Replaced URL with URI where appropriate.
- Fixed some typos
- Updated README (and corrected the attribution of the SynchronizedArray code)
2019-06-17 00:46:23 -04:00
Gregorio Litenstein c8d961c035 Take care of some compiler warnings. 2019-06-17 00:46:23 -04:00
Gregorio Litenstein 9e4ca7d54f Some clean-up and renames
Renamed SWDAMainPRefpane.swift to main.swift, CLI app from lsreg to swda and renamed asset catalog.
2019-06-17 00:46:22 -04:00
Gregorio Litenstein 122a639bea Update to Swift 5 2019-03-30 11:48:05 -03:00
Gregorio Litenstein 7cbc766230 Update changelog & bump version to 1.1.3. 2018-11-15 10:39:31 -03:00
Gregorio Litenstein 4695232d57 SWDAApplicationInfo: Guard for bundleIdentifier!
Intended to fix odd occurrences of crashes when unwrapping.
2018-11-15 10:34:54 -03:00
Gregorio Litenstein 263b685b45 Update changelog & version bump to 1.1.2 2018-10-07 23:33:13 -03:00
Gregorio Litenstein fee4221691 Force static linking of Swift stdlib. 2018-10-07 23:33:07 -03:00
Gregorio Litenstein d2bc612822 Migrate to Swift 4.2 2018-10-07 23:29:04 -03:00
49 changed files with 2934 additions and 2557 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "Packages/SwiftCLI-2.0.3"]
path = Packages/SwiftCLI-2.0.3
url = https://Lord-Kamina@github.com/Lord-Kamina/SwiftCLI
url = https://github.com/Lord-Kamina/SwiftCLI
+17 -17
View File
@@ -74,19 +74,18 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0820;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1030;
ORGANIZATIONNAME = "Gregorio Litenstein Goldzweig";
TargetAttributes = {
EBA7F0911E856279009EE061 = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
LastSwiftMigration = 1020;
};
};
};
buildConfigurationList = EBA7F08D1E856279009EE061 /* Build configuration list for PBXProject "AppDoesNothing" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -129,10 +128,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
@@ -155,14 +152,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
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;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -177,6 +171,7 @@
"@loader_path/../Frameworks",
"@executable_path/../Frameworks",
);
MACH_O_TYPE = mh_execute;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -185,7 +180,7 @@
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -193,10 +188,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
@@ -234,13 +227,14 @@
"@loader_path/../Frameworks",
"@executable_path/../Frameworks",
);
MACH_O_TYPE = mh_execute;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -248,12 +242,15 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 33JDJVTQZT;
INFOPLIST_FILE = "$(SRCROOT)/Resources/DummyApp_Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = cl.fail.lordkamina.ThisAppDoesNothing;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
};
name = Debug;
};
@@ -261,12 +258,15 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 33JDJVTQZT;
INFOPLIST_FILE = "$(SRCROOT)/Resources/DummyApp_Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = cl.fail.lordkamina.ThisAppDoesNothing;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
};
name = Release;
};
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1030"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -42,9 +42,9 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
buildConfiguration = "Release"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 764 B

After

Width:  |  Height:  |  Size: 764 B

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 575 KiB

After

Width:  |  Height:  |  Size: 575 KiB

+33 -3
View File
@@ -1,8 +1,38 @@
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "SwiftDefaultApps",
name: "SwiftDefaultApps",
products: [
.executable(name: "Prefpane", targets: ["SWDA-Prefpane"]),
.executable(name: "CLI", targets: ["SWDA-CLI"])
],
dependencies: [
.Package(url: "https://github.com/jakeheis/SwiftCLI", majorVersion: 2, minor: 0)
]
.package(url: "https://github.com/Lord-Kamina/SwiftCLI", from: Version("2.0.3+swift5"))
],
targets: [
.target(
name: "SWDA-Common",
path: "Sources",
sources: ["Common Sources/"]
),
.target(
name: "DummyApp",
path: "Sources",
sources: ["DummyApp/"]
),
.target(
name: "SWDA-CLI",
dependencies: ["SwiftCLI", "SWDA-Common", "DummyApp"],
path: "Sources",
sources: ["CLI Components/"]
),
.target(
name: "SWDA-Prefpane",
dependencies: ["SWDA-Common", "DummyApp"],
path: "Sources",
sources: ["Prefpane Sources/"]
)
],
swiftLanguageVersions: [.v4, .v4_2, .v5]
)
+80 -13
View File
@@ -1,3 +1,6 @@
ANNOUNCEMENT: I have recently noticed Apple has introduced replacements for the deprecated APIs used in this prefpane. As a result, slow as it may be, I plan to resume development. I will be taking PRs as well, if somebody wishes to contribute.
========
SwiftDefaultApps
========
@@ -7,26 +10,60 @@ Additionally, I guess it was a good way to teach myself Swift.
Feel free to contribute, comment or report issues at https://github.com/Lord-Kamina/SwiftDefaultApps.
## Installing & Uninstalling
To install, double click on the .prefpane, and you will be prompted to install it.
To uninstall, simply Ctrl+Click on the Prefpane icon and remove it, or move the .prefpane file to the Trash.
* Download latest release from https://github.com/Lord-Kamina/SwiftDefaultApps/releases
* To install, double click on the `.prefpane`, and you will be prompted to install it.
* To uninstall, simply Ctrl+Click on the Prefpane icon and remove it, or move the `.prefpane` file to the Trash.
## Installting with brew
Run:
```bash
brew install swiftdefaultappsprefpane
```
then use Spotlight to open the `SwiftDefaultApps.prefpane`. It will open the System Preferences and you find the app on the bottom of the icons.
## How to use the "Do Nothing" app
The **Do Nothing** dummy app needs to be launched before you can use it in the pref pane. For this, open a terminal and run the following commands:
```bash
appDir="/Library/PreferencePanes/SwiftDefaultApps.prefPane/Contents/Resources/ThisAppDoesNothing.app"
if ! [[ -d "$appDir" ]]; then appDir="$HOME/$appDir"; fi
# Remove quanrantine flag
xattr -d com.apple.quarantine "$appDir"
# Open the app
open "$appDir"
```
After these 2 steps, the **Do Nothing** app should work when you pick it up.
## Usage Notes
This Preference pane will let you view and change default application associations for basically any URL Scheme and/or filetype in macOS.
This Preference pane will let you view and change default application associations for basically any URI Scheme and/or filetype in macOS.
The user-interface should be pretty self-explanatory; but, there are some things that might require an explanation:
- Selecting any URL Scheme or File type (always represented by a UTI), will give you a list of all valid applications for each LaunchServices role. This data is generated by LaunchServices itself.
- Selecting any URI Scheme or File type (always represented by a UTI), will give you a list of all valid applications for each LaunchServices role. This data is generated by LaunchServices itself.
- There's two additional options:
- One of them is **"Do Nothing"**, what this does is register the item to be handled by a dummy application that lives inside the prefpane bundle which basically does nothing. Its only function is being able to open any URL Scheme or UTI whatsoever, printing a line to the console (specifying whatever it was that launched it) and immediately quitting.
- One of them is **"Do Nothing"**, what this does is register the item to be handled by a dummy application which basically does nothing. Its only function is being able to open any URI Scheme or UTI whatsoever, printing a line to the console (specifying whatever it was that launched it) and immediately quitting. By default this application should be located in the `Resources` folder inside the prefpane bundle; SwiftDefaultApps will, however, also look for it in the directory it is itself located in (for the CLI version) and every `Applications`folder in the computer.
- The second is **"Other..."**, which obviously will allow you to select an application not in the list; with a caveat. In recent versions of macOS, the LaunchServices have become quite a bit smarter under the hood than they used to be. In practice, what this means is that although you can choose *any* application to handle anything, only valid associations will be preserved, as the LaunchServices is permanently looking for invalid or stale associations and removing them.
- One final note: In the URL Schemes tab, you have the option of adding a custom URL Scheme (Removing them on demand is neither possible nor necessary, due to the same thing explained above). This options is provided for completeness' sake; you should virtually never need to use it, since Launch Services should be able to properly detect any valid URL Handlers. As a further cautionary note: If you add a custom URL Scheme when it is not needed, you may not be able to remove it except by uninstalling and reinstalling SwiftDefaultApps. Why? Because new schemes are by default associated to *"Do Nothing", which means Launch Services will always find a valid handler as long as SwiftDefaultApps is installed.
- One final note: In the URI Schemes tab, you have the option of adding a custom URI Scheme (Removing them on demand is neither possible nor necessary, due to the same thing explained above). This options is provided for completeness' sake; you should virtually never need to use it, since Launch Services should be able to properly detect any valid URL Handlers. As a further cautionary note: If you add a custom URI Scheme when it is not needed, you may not be able to remove it except by uninstalling and reinstalling SwiftDefaultApps. Why? Because new schemes are by default associated to *"Do Nothing", which means Launch Services will always find a valid handler as long as SwiftDefaultApps is installed.
### How to Find Out File UTI
Run in your terminal the following command (replace my_song.mp3 by your file):
`mdls -name kMDItemContentType -name kMDItemContentTypeTree -name kMDItemKind my_song.mp3`
## Acknowledgments & Attributions
- Using jakeheis' SwiftCLI 2.0 as a base for the CLI version located inside the bundle. (https://github.com/jakeheis/SwiftCLI)
- Using AMTourky's DRYView canned views system (https://github.com/AMTourky/CocoaBindingDryView-ReusableViews/)
- Using merrickluo's SynchronizedArray (https://github.com/merrickluo/SynchronizedArray)
- Using ZamzamKit's SynchronizedArray (https://github.com/ZamzamInc/ZamzamKit)
- Icon made using the following resources:
- Brush, Ruler and Pencil designed by Freepik. (http://www.freepik.com/free-vector/background-of-back-to-school_769298.htm)
- Magnifying glass frame designed by Balintseby / Freepik (http://www.freepik.com/free-vector/realistic-magnifying-glass_789215.htm)
@@ -34,17 +71,49 @@ The user-interface should be pretty self-explanatory; but, there are some things
- Gears designed by Freepik (http://www.freepik.com/free-vector/gray-background-of-gear_956712.htm)
## Current Version
- Version: 1.1.0
- Date: 2017-09-27
- Version: 2.0.1
- Date: 2019-07-26
## Known Issues
-
See https://github.com/Lord-Kamina/SwiftDefaultApps/issues
## TO DO
- Localizations
# Release Notes
## [2.0.1] - 2019-07-26
+ ### Fixed
+ CLI: Crash when displaying the results for setHandler with shortcuts such as internet, browser, email, etc.
## [2.0.0] - 2019-06-12
+ ### Added
+ Signed prefpane, CLI and Dummy apps.
+ Both the prefpane and the CLI version will now automatically try to locate ThisAppDoesNothing.app if it does not appear registered with launch services.
+ ### Changed
+ SwiftCLI is now built and linked as a static library instead of a framework.
+ Updated to Swift 5
+ The content array is now populated by overriding `didSelect()` instead of relying on an arbitrary sleep timer.
+ Changed the CLI app's name from lsreg to swda.
+ Messages in both the CLI and Preference Pane are now a bit more verbose.
+ Under the hood, folded most of the app's feedback to the user into a single `displayAlert()` function.
+ ### Fixed
+ Updated the Swift Package Manager manifest to version 5.
+ Updated the SynchronizedArray code and corrected the attribution; Before, I had inadvertently credited an unrelated project with the same name.
+ Various small optimizations, fixes and text corrections.
## [1.1.3] - 2018-11-15
+ ### Fixed
+ Bug causing crash on related to force-unwrapping bundleIdentifier.
## [1.1.2] - 2018-10-07
+ ### Changed
+ Migrated to Swift 4.2
+ Force Static linking of standard library, which should fix issues running on Mojave.
## [1.1.1] - 2018-04-15
+ ### Changed
+ Small changes for Swift 4.1.
@@ -62,5 +131,3 @@ The user-interface should be pretty self-explanatory; but, there are some things
+ Initial release!
+ ### Changed
+ ### Fixed
+2 -2
View File
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleVersion</key>
<string>223</string>
<string>334</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017 Gregorio Litenstein Goldzweig. All rights reserved.</string>
<key>NSMainNibFile</key>
+96 -76
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
@@ -13,11 +13,12 @@
EB14B8991E4B614D008AB27B /* DryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6ED4281E4A309C00703063 /* DryView.swift */; };
EB14B8A01E4B63E7008AB27B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB14B89F1E4B63E7008AB27B /* Cocoa.framework */; };
EB5BFC661E946ED600253EC3 /* SynchronizedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5BFC651E946ED600253EC3 /* SynchronizedArray.swift */; };
EBA6B0981EB6E5EB0080A783 /* ThisAppDoesNothing.app in Resources */ = {isa = PBXBuildFile; fileRef = EBE2B0A01EB4155000BD157C /* ThisAppDoesNothing.app */; };
EBA6B0A71EB6EB470080A783 /* LICENSE in CopyFiles */ = {isa = PBXBuildFile; fileRef = EBA6B0A31EB6EB300080A783 /* LICENSE */; };
EBA6B0A81EB6EB4A0080A783 /* README.md in CopyFiles */ = {isa = PBXBuildFile; fileRef = EBA6B0A41EB6EB300080A783 /* README.md */; };
EBC487A21E49073400A9A125 /* PreferencePanes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBC487A11E49073400A9A125 /* PreferencePanes.framework */; };
EBC487C01E490FDE00A9A125 /* SWDAMainPrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC487BF1E490FDE00A9A125 /* SWDAMainPrefPane.swift */; };
EBC487C01E490FDE00A9A125 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC487BF1E490FDE00A9A125 /* main.swift */; };
EBD7C46822ECD25D00297A13 /* ThisAppDoesNothing.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = EBE2B0A01EB4155000BD157C /* ThisAppDoesNothing.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EBD7C46B22ECD34200297A13 /* swda in CopyFiles */ = {isa = PBXBuildFile; fileRef = EBA6B09F1EB6E6150080A783 /* swda */; };
EBE2B0991EB409F800BD157C /* Main Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBE2B0981EB409F800BD157C /* Main Assets.xcassets */; };
EBF3342D1E9BD68A00520B7B /* SWDAHandlersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBF3342C1E9BD68A00520B7B /* SWDAHandlersModel.swift */; };
EBF3342F1E9BD91C00520B7B /* ControllersRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBF3342E1E9BD91C00520B7B /* ControllersRef.swift */; };
@@ -52,6 +53,13 @@
remoteGlobalIDString = OBJ_31;
remoteInfo = SwiftCLI;
};
EBD7C46C22ECDDC700297A13 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = EBA6B0991EB6E6140080A783 /* SwiftDefaultApps CLI.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = OBJ_57;
remoteInfo = SwiftDefaultApps;
};
EBE2B09F1EB4155000BD157C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = EBB1976F1E85D26E0072AF70 /* AppDoesNothing.xcodeproj */;
@@ -64,14 +72,25 @@
/* Begin PBXCopyFilesBuildPhase section */
EBA6B0A21EB6EB000080A783 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 8;
buildActionMask = 12;
dstPath = ..;
dstSubfolderSpec = 1;
files = (
EBD7C46B22ECD34200297A13 /* swda in CopyFiles */,
EBA6B0A71EB6EB470080A783 /* LICENSE in CopyFiles */,
EBA6B0A81EB6EB4A0080A783 /* README.md in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 1;
runOnlyForDeploymentPostprocessing = 0;
};
EBD7C46622ECD0B700297A13 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 7;
files = (
EBD7C46822ECD25D00297A13 /* ThisAppDoesNothing.app in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
@@ -85,8 +104,8 @@
EBA6B0A41EB6EB300080A783 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
EBB1976F1E85D26E0072AF70 /* AppDoesNothing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = AppDoesNothing.xcodeproj; sourceTree = "<group>"; };
EBC487A11E49073400A9A125 /* PreferencePanes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PreferencePanes.framework; path = System/Library/Frameworks/PreferencePanes.framework; sourceTree = SDKROOT; };
EBC487BF1E490FDE00A9A125 /* SWDAMainPrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SWDAMainPrefPane.swift; sourceTree = "<group>"; };
EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefPane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftDefaultApps.prefPane; sourceTree = BUILT_PRODUCTS_DIR; };
EBC487BF1E490FDE00A9A125 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefpane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftDefaultApps.prefpane; sourceTree = BUILT_PRODUCTS_DIR; };
EBD61FE51E47EC3C00549EE8 /* Prefpane_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Prefpane_Info.plist; path = ../../Resources/Prefpane_Info.plist; sourceTree = "<group>"; };
EBD620021E47F43C00549EE8 /* SWDAPrefpaneMain.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SWDAPrefpaneMain.xib; sourceTree = "<group>"; };
EBE2B0981EB409F800BD157C /* Main Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Main Assets.xcassets"; sourceTree = "<group>"; };
@@ -151,8 +170,8 @@
EBA6B09A1EB6E6140080A783 /* Products */ = {
isa = PBXGroup;
children = (
EBA6B09F1EB6E6150080A783 /* lsreg */,
EBA6B0A11EB6E6150080A783 /* SwiftCLI.framework */,
EBA6B09F1EB6E6150080A783 /* swda */,
EBA6B0A11EB6E6150080A783 /* libSwiftCLI.a */,
);
name = Products;
sourceTree = "<group>";
@@ -184,7 +203,7 @@
EBD61FDA1E47EC3C00549EE8 /* Products */ = {
isa = PBXGroup;
children = (
EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefPane */,
EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefpane */,
);
name = Products;
sourceTree = "<group>";
@@ -192,7 +211,7 @@
EBD61FDB1E47EC3C00549EE8 /* Prefpane Sources */ = {
isa = PBXGroup;
children = (
EBC487BF1E490FDE00A9A125 /* SWDAMainPrefPane.swift */,
EBC487BF1E490FDE00A9A125 /* main.swift */,
EB5BFC641E946ED600253EC3 /* Model */,
EB0E22771E688DA300937CAB /* View Controllers */,
EB0E22761E688D7500937CAB /* Views */,
@@ -239,19 +258,21 @@
buildPhases = (
EBD61FD41E47EC3C00549EE8 /* Sources */,
EBD61FD51E47EC3C00549EE8 /* Frameworks */,
EBD61FD61E47EC3C00549EE8 /* Headers */,
EBA6B0A21EB6EB000080A783 /* CopyFiles */,
EBD61FD61E47EC3C00549EE8 /* Headers */,
EBD7C46622ECD0B700297A13 /* CopyFiles */,
EBD61FD71E47EC3C00549EE8 /* Resources */,
EBF28D7D1EB50E030027935E /* ShellScript */,
);
buildRules = (
);
dependencies = (
EBD7C46D22ECDDC700297A13 /* PBXTargetDependency */,
EBA6B0961EB6E5B10080A783 /* PBXTargetDependency */,
);
name = SwiftDefaultApps;
productName = SwiftDefaultApps;
productReference = EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefPane */;
productReference = EBD61FD91E47EC3C00549EE8 /* SwiftDefaultApps.prefpane */;
productType = "com.apple.product-type.bundle";
};
/* End PBXNativeTarget section */
@@ -263,19 +284,19 @@
KnownAssetTags = (
Icon,
);
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1030;
ORGANIZATIONNAME = "Gregorio Litenstein Goldzweig";
TargetAttributes = {
EBD61FD81E47EC3C00549EE8 = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0910;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = EBD61FD31E47EC3C00549EE8 /* Build configuration list for PBXProject "SWDA Prefpane" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = English;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -302,17 +323,17 @@
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
EBA6B09F1EB6E6150080A783 /* lsreg */ = {
EBA6B09F1EB6E6150080A783 /* swda */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = lsreg;
path = swda;
remoteRef = EBA6B09E1EB6E6150080A783 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
EBA6B0A11EB6E6150080A783 /* SwiftCLI.framework */ = {
EBA6B0A11EB6E6150080A783 /* libSwiftCLI.a */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = SwiftCLI.framework;
fileType = archive.ar;
path = libSwiftCLI.a;
remoteRef = EBA6B0A01EB6E6150080A783 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@@ -333,7 +354,6 @@
EB0F5A271E6894DF004BAA85 /* SWDAPrefpaneMain.xib in Resources */,
EB0F5A2A1E6894DF004BAA85 /* SWDASchemesUTIsContent.xib in Resources */,
EB0F5A2B1E6894DF004BAA85 /* SWDAPrefpaneTabTemplate.xib in Resources */,
EBA6B0981EB6E5EB0080A783 /* ThisAppDoesNothing.app in Resources */,
EBE2B0991EB409F800BD157C /* Main Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -343,16 +363,16 @@
/* Begin PBXShellScriptBuildPhase section */
EBF28D7D1EB50E030027935E /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 8;
buildActionMask = 12;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/bash\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$INFOPLIST_FILE\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"";
shellScript = "#!/bin/bash\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$INFOPLIST_FILE\")\nbuildNumber=$(($buildNumber + 1))\necho $buildNumber > ~/buildversion.txt\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -361,7 +381,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EBC487C01E490FDE00A9A125 /* SWDAMainPrefPane.swift in Sources */,
EBC487C01E490FDE00A9A125 /* main.swift in Sources */,
EBF334371E9BDB5500520B7B /* SWDAContentProtocol.swift in Sources */,
EBF3342F1E9BD91C00520B7B /* ControllersRef.swift in Sources */,
EBFE66071E688AA80056E549 /* commonFuncs.swift in Sources */,
@@ -384,26 +404,24 @@
name = ThisAppDoesNothing;
targetProxy = EBA6B0951EB6E5B10080A783 /* PBXContainerItemProxy */;
};
EBD7C46D22ECDDC700297A13 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = SwiftDefaultApps;
targetProxy = EBD7C46C22ECDDC700297A13 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
EBD61FE61E47EC3C00549EE8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = SWDADefaultApps;
ASSETCATALOG_COMPILER_APPICON_NAME = SwiftDefaultApps;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = 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;
@@ -412,19 +430,16 @@
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_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2.0.1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -437,35 +452,31 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GENERATE_PKGINFO_FILE = YES;
IBC_FLATTEN_NIBS = NO;
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-D Prefpane";
SDKROOT = macosx;
SWIFT_VERSION = 4.0;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = NO;
SWIFT_FORCE_STATIC_LINK_STDLIB = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
EBD61FE71E47EC3C00549EE8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = SWDADefaultApps;
ASSETCATALOG_COMPILER_APPICON_NAME = SwiftDefaultApps;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = 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;
@@ -474,74 +485,83 @@
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_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2.0.1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 3;
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;
GENERATE_PKGINFO_FILE = YES;
IBC_FLATTEN_NIBS = NO;
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_SWIFT_FLAGS = "-D Prefpane";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = NO;
SWIFT_FORCE_STATIC_LINK_STDLIB = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
EBD61FE91E47EC3C00549EE8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = YES;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1.1.0;
DEVELOPMENT_TEAM = 33JDJVTQZT;
INFOPLIST_FILE = "$(SRCROOT)/Resources/Prefpane_Info.plist";
INSTALL_PATH = "$(HOME)/Library/PreferencePanes";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
INSTALL_PATH = /;
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = cl.fail.lordkamina.SwiftDefaultApps;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = NO;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = NO;
SWIFT_FORCE_STATIC_LINK_STDLIB = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_EXPORT_DECL = "";
WRAPPER_EXTENSION = prefPane;
WRAPPER_EXTENSION = prefpane;
};
name = Debug;
};
EBD61FEA1E47EC3C00549EE8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = YES;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1.1.0;
DEVELOPMENT_TEAM = 33JDJVTQZT;
INFOPLIST_FILE = "$(SRCROOT)/Resources/Prefpane_Info.plist";
INSTALL_PATH = "$(HOME)/Library/PreferencePanes";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
INSTALL_PATH = /;
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = cl.fail.lordkamina.SwiftDefaultApps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_EXPORT_DECL = "";
WRAPPER_EXTENSION = prefPane;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = NO;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = NO;
SWIFT_FORCE_STATIC_LINK_STDLIB = YES;
WRAPPER_EXTENSION = prefpane;
};
name = Release;
};
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1030"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1030"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -20,10 +20,24 @@
ReferencedContainer = "container:SWDA Prefpane.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OBJ_57"
BuildableName = "swda"
BlueprintName = "SwiftDefaultApps"
ReferencedContainer = "container:SwiftDefaultApps CLI.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
@@ -60,7 +74,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
@@ -74,7 +88,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "[ -d ~/Library/PreferencePanes/SwiftDefaultApps.prefpane ] &amp;&amp; rm -Rv ~/Library/PreferencePanes/SwiftDefaultApps.prefpane&#10;cp -Rf &quot;${CONFIGURATION_BUILD_DIR}/SwiftDefaultApps.prefpane&quot; ~/Library/PreferencePanes/&#10;[ -d &quot;${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app&quot; ] &amp;&amp; rm -Rv &quot;${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app&quot;">
scriptText = "[ -d ~/Library/PreferencePanes/SwiftDefaultApps.prefpane ] &amp;&amp; rm -Rv ~/Library/PreferencePanes/SwiftDefaultApps.prefpane&#10;cp -Rf &quot;${CONFIGURATION_BUILD_DIR}/SwiftDefaultApps.prefpane&quot; ~/Library/PreferencePanes/&#10;[ -d &quot;${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app&quot; ] &amp;&amp; rm -Rv &quot;${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
@@ -112,7 +126,7 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
+19 -19
View File
@@ -1,27 +1,27 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
class GetApps: Command {
let name = "getApps"
let signature = ""
let shortDescription = "Returns a list of all registered applications."
func execute(arguments: CommandArguments) throws {
if let output = copyStringArrayAsString(LSWrappers.copyAllApps()) {
print(output)
}
else { throw CLIError.error("There was an error generating the list of applications.") }
}
let name = "getApps"
let signature = ""
let shortDescription = "Returns a list of all registered applications."
func execute(arguments: CommandArguments) throws {
if let output = copyStringArrayAsString(LSWrappers.copyAllApps()) {
print(output)
}
else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate the list of installed applications.") }
}
}
+100 -101
View File
@@ -1,107 +1,106 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
class ReadCommand: OptionCommand {
let name = "getHandler"
let signature = ""
let shortDescription = "Returns the default application registered for the URL scheme or <subtype> you specify."
private var kind: String = ""
private var getAll = false;
private var contentType: String? = nil
private var handler: String? = nil
private var roles: Dictionary = ["editor":LSRolesMask.editor,"viewer":LSRolesMask.viewer,"shell":LSRolesMask.shell,"all":LSRolesMask.all]
private var role: LSRolesMask = LSRolesMask.all
func setupOptions(options: OptionRegistry) {
options.addGroup(name:"type", required:true, conflicting:true)
options.add(keys: ["--UTI"], usage: "Return the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "UTI"
}
options.add(keys: ["--URL"], usage: "Return the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "URL"
}
options.add(flags: ["--internet", "--browser", "--web"], usage: "Returns the default web browser.", group:"type") {
self.contentType = nil
self.kind = "http"
}
options.add(flags: ["--mail", "--email", "--e-mail"], usage: "Returns the default e-mail client.", group:"type") {
self.contentType = nil
self.kind = "mailto"
}
options.add(flags: ["--ftp"], usage: "Returns the default FTP client.", group:"type") {
self.contentType = nil
self.kind = "ftp"
}
options.add(flags: ["--rss"], usage: "Returns the default RSS client.", group:"type") {
self.contentType = nil
self.kind = "RSS"
}
options.add(flags: ["--news"], usage: "Returns the default news client.", group:"type") {
self.contentType = nil
self.kind = "news"
}
options.add(flags: ["--all"], usage: "When this flag is added, a list of all applications registered for that content will printed.") {
self.getAll = true
}
options.add(keys: ["--role"], usage: "--role <Viewer|Editor|Shell|All>, specifies the role with which to register the handler. Default is All.", valueSignature: "role") { [unowned self] (value) in
if let temp = self.roles[value.lowercased()] {
self.role = temp
}
else { self.role = [LSRolesMask.viewer,LSRolesMask.editor] }
}
}
func execute(arguments: CommandArguments) throws {
switch(kind,getAll) {
case ("UTI",true),("URL",true):
if let contentString = self.contentType {
handler = copyStringArrayAsString( ((kind == "URL") ? LSWrappers.Schemes.copyAllHandlers(contentString) : LSWrappers.UTType.copyAllHandlers(contentString, inRoles: role)) )
}
break
case ("UTI",false),("URL",false):
if let contentString = self.contentType {
handler = ((kind == "URL") ? LSWrappers.Schemes.copyDefaultHandler(contentString) : LSWrappers.UTType.copyDefaultHandler(contentString, inRoles: role))
}
break
case ("http",Bool()),("mailto",Bool()),("ftp",Bool()),("rss",Bool()),("news",Bool()):
handler = LSWrappers.Schemes.copyDefaultHandler(kind)
break
default:
handler = nil
break
}
let arg: String
if (self.contentType != nil) { arg = self.contentType! }
else { arg = "<subtype>" }
if (nil != handler) {
print(handler!)
} else { throw CLIError.error(("An incompatible combination was used, or no application is registered to handle \(arg)")) }
}
let name = "getHandler"
let signature = ""
let shortDescription = "Returns the default application registered for the URI Scheme or <subtype> you specify."
private var kind: String = ""
private var getAll = false;
private var contentType: String? = nil
private var handler: String? = nil
private var roles: Dictionary = ["editor":LSRolesMask.editor,"viewer":LSRolesMask.viewer,"shell":LSRolesMask.shell,"all":LSRolesMask.all]
private var role: LSRolesMask = LSRolesMask.all
func setupOptions(options: OptionRegistry) {
options.addGroup(name:"type", required:true, conflicting:true)
options.add(keys: ["--UTI"], usage: "Return the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "UTI"
}
options.add(keys: ["--URL"], usage: "Return the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "URL"
}
options.add(flags: ["--internet", "--browser", "--web"], usage: "Returns the default web browser.", group:"type") {
self.contentType = nil
self.kind = "http"
}
options.add(flags: ["--mail", "--email", "--e-mail"], usage: "Returns the default e-mail client.", group:"type") {
self.contentType = nil
self.kind = "mailto"
}
options.add(flags: ["--ftp"], usage: "Returns the default FTP client.", group:"type") {
self.contentType = nil
self.kind = "ftp"
}
options.add(flags: ["--rss"], usage: "Returns the default RSS client.", group:"type") {
self.contentType = nil
self.kind = "RSS"
}
options.add(flags: ["--news"], usage: "Returns the default news client.", group:"type") {
self.contentType = nil
self.kind = "news"
}
options.add(flags: ["--all"], usage: "When this flag is added, a list of all applications registered for that content will printed.") {
self.getAll = true
}
options.add(keys: ["--role"], usage: "--role <Viewer|Editor|Shell|All>, specifies the role with which to register the handler. Default is All.", valueSignature: "role") { [unowned self] (value) in
if let temp = self.roles[value.lowercased()] {
self.role = temp
}
else { self.role = [LSRolesMask.viewer,LSRolesMask.editor] }
}
}
func execute(arguments: CommandArguments) throws {
switch(kind,getAll) {
case ("UTI",true),("URL",true):
if let contentString = self.contentType {
handler = copyStringArrayAsString( ((kind == "URL") ? LSWrappers.Schemes.copyAllHandlers(contentString) : LSWrappers.UTType.copyAllHandlers(contentString, inRoles: role)) )
}
break
case ("UTI",false),("URL",false):
if let contentString = self.contentType {
handler = ((kind == "URL") ? LSWrappers.Schemes.copyDefaultHandler(contentString) : LSWrappers.UTType.copyDefaultHandler(contentString, inRoles: role))
}
break
case ("http",Bool()),("mailto",Bool()),("ftp",Bool()),("rss",Bool()),("news",Bool()):
handler = LSWrappers.Schemes.copyDefaultHandler(kind)
break
default:
handler = nil
break
}
let arg: String
arg = self.contentType ?? "<subtype>"
if (nil != handler) {
print(handler!)
} else { throw CLIError.error(("SwiftDefaultApps ERROR: An incompatible combination was used, or no application is registered to handle \(arg)")) }
}
}
+20 -20
View File
@@ -1,27 +1,27 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
class GetSchemes: Command {
let name = "getSchemes"
let signature = ""
let shortDescription = "Returns a list of all known URL schemes, accompanied by their default handler."
func execute(arguments: CommandArguments) throws {
if let output = copyDictionaryAsString(LSWrappers.Schemes.copySchemesAndHandlers()?.sorted(by: { $0.0 < $1.0 })) {
print(output)
}
else { throw CLIError.error("There was an error generating the list.") }
}
let name = "getSchemes"
let signature = ""
let shortDescription = "Returns a list of all known URI schemes, accompanied by their default handler."
func execute(arguments: CommandArguments) throws {
if let output = copyDictionaryAsString(LSWrappers.Schemes.copySchemesAndHandlers()?.sorted(by: { $0.0 < $1.0 })) {
print(output)
}
else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate list of URI Schemes.") }
}
}
+20 -20
View File
@@ -1,27 +1,27 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
class GetUTIs: Command {
let name = "getUTIs"
let signature = ""
let shortDescription = "Returns a list of all known UTIs, and their default handler."
func execute(arguments: CommandArguments) throws {
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.") }
}
let name = "getUTIs"
let signature = ""
let shortDescription = "Returns a list of all known UTIs, and their default handler."
func execute(arguments: CommandArguments) throws {
if let output = copyDictionaryAsString(LSWrappers.UTType.copyAllUTIs().sorted(by: { $0.0 < $1.0 })) {
print(output)
}
else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate list of UTIs") }
}
}
+8 -8
View File
@@ -1,15 +1,15 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
CLI.setup(name:"lsreg", version:"1.0", description:"Utility to retrieve and manipulate default applications in macOS.")
CLI.setup(name:"swda", version:"1.0", description:"Utility to retrieve and manipulate default applications in macOS.")
CLI.register(commands: [ ReadCommand(), GetApps(), GetSchemes(), GetUTIs(), SetCommand() ])
exit(CLI.go())
+83 -82
View File
@@ -1,89 +1,90 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
import SwiftCLI
class SetCommand: OptionCommand {
var failOnUnrecognizedOptions = true
let name = "setHandler"
let signature = ""
let shortDescription = "Sets <application> as the default handler for a given <type>/<subtype> combination."
private var kind: String = ""
private var contentType: String? = nil
private var inApplication: String = "None"
private var bundleID: String? = nil
private var statusCode: OSStatus = kLSUnknownErr
private var roles: Dictionary = ["editor":LSRolesMask.editor,"viewer":LSRolesMask.viewer,"shell":LSRolesMask.shell,"all":LSRolesMask.all]
private var role: LSRolesMask = LSRolesMask.all
func setupOptions(options: OptionRegistry) {
options.addGroup(name:"type", required:true, conflicting:true)
options.addGroup(name:"application", required:true, conflicting:true)
options.add(keys: ["--UTI"], usage: "Change the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "UTI"
}
options.add(keys: ["--URL"], usage: "Change the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "URL"
}
options.add(flags: ["--internet", "--browser", "--web"], usage: "Changes the default web browser.", group:"type") {
self.contentType = nil
self.kind = "http"
}
options.add(flags: ["--mail", "--email", "--e-mail"], usage: "Changes the default e-mail client.", group:"type") {
self.contentType = nil
self.kind = "mailto"
}
options.add(flags: ["--ftp"], usage: "Changes the default FTP client.", group:"type") {
self.contentType = nil
self.kind = "ftp"
}
options.add(flags: ["--rss"], usage: "Changes the default RSS client.", group:"type") {
self.contentType = nil
self.kind = "RSS"
}
options.add(flags: ["--news"], usage: "Changes the default news client.", group:"type") {
self.contentType = nil
self.kind = "news"
}
options.add(keys: ["--app", "--application"], usage: "The <application> to register as default handler. Specifying \"None\" will remove the currently registered handler.", valueSignature: "application", group:"application") { [unowned self] (value) in
self.inApplication = value
}
options.add(keys: ["--role"], usage: "--role <Viewer|Editor|Shell|All>, specifies the role with which to register the handler. Default is All.", valueSignature: "role") { [unowned self] (value) in
if let temp = self.roles[value.lowercased()] {
self.role = temp
}
else { self.role = LSRolesMask.all }
}
}
func execute(arguments: CommandArguments) throws {
statusCode = LSWrappers.getBundleID(self.inApplication, outBundleID: &bundleID)
guard (statusCode == 0) else { throw CLIError.error(LSWrappers.LSErrors.init(value: statusCode).print(argument: (app: inApplication, content: self.contentType!))) }
switch(kind) {
case "UTI","URL":
if let contentString = self.contentType {
statusCode = ((kind == "URL") ? LSWrappers.Schemes.setDefaultHandler(contentString, bundleID!) : LSWrappers.UTType.setDefaultHandler(contentString, bundleID!, self.role))
}
break
case "http","mailto","ftp","rss","news":
statusCode = LSWrappers.Schemes.setDefaultHandler(kind, bundleID!)
break
default:
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!))) }
}
var failOnUnrecognizedOptions = true
let name = "setHandler"
let signature = ""
let shortDescription = "Sets <application> as the default handler for a given <type>/<subtype> combination."
private var kind: String = ""
private var contentType: String? = nil
private var inApplication: String = "None"
private var bundleID: String? = nil
private var statusCode: OSStatus = kLSUnknownErr
private var roles: Dictionary = ["editor":LSRolesMask.editor,"viewer":LSRolesMask.viewer,"shell":LSRolesMask.shell,"all":LSRolesMask.all]
private var role: LSRolesMask = LSRolesMask.all
func setupOptions(options: OptionRegistry) {
options.addGroup(name:"type", required:true, conflicting:true)
options.addGroup(name:"application", required:true, conflicting:true)
options.add(keys: ["--UTI"], usage: "Change the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "UTI"
}
options.add(keys: ["--URL"], usage: "Change the default application for <subtype>", valueSignature: "subtype", group:"type") { [unowned self] (value) in
self.contentType = value
self.kind = "URL"
}
options.add(flags: ["--internet", "--browser", "--web"], usage: "Changes the default web browser.", group:"type") {
self.contentType = nil
self.kind = "http"
}
options.add(flags: ["--mail", "--email", "--e-mail"], usage: "Changes the default e-mail client.", group:"type") {
self.contentType = nil
self.kind = "mailto"
}
options.add(flags: ["--ftp"], usage: "Changes the default FTP client.", group:"type") {
self.contentType = nil
self.kind = "ftp"
}
options.add(flags: ["--rss"], usage: "Changes the default RSS client.", group:"type") {
self.contentType = nil
self.kind = "RSS"
}
options.add(flags: ["--news"], usage: "Changes the default news client.", group:"type") {
self.contentType = nil
self.kind = "news"
}
options.add(keys: ["--app", "--application"], usage: "The <application> to register as default handler. Specifying \"None\" will remove the currently registered handler.", valueSignature: "application", group:"application") { [unowned self] (value) in
self.inApplication = value
}
options.add(keys: ["--role"], usage: "--role <Viewer|Editor|Shell|All>, specifies the role with which to register the handler. Default is All.", valueSignature: "role") { [unowned self] (value) in
if let temp = self.roles[value.lowercased()] {
self.role = temp
}
else { self.role = LSRolesMask.all }
}
}
func execute(arguments: CommandArguments) throws {
statusCode = LSWrappers.getBundleID(self.inApplication, outBundleID: &bundleID)
guard (statusCode == 0) else { throw CLIError.error(LSWrappers.LSErrors.init(value: statusCode).print(argument: (app: inApplication, content: self.contentType ?? self.kind))) }
switch(kind) {
case "UTI","URL":
if let contentString = self.contentType {
statusCode = ((kind == "URL") ? LSWrappers.Schemes.setDefaultHandler(contentString, bundleID!) : LSWrappers.UTType.setDefaultHandler(contentString, bundleID!, self.role))
}
break
case "http","mailto","ftp","rss","news":
statusCode = LSWrappers.Schemes.setDefaultHandler(kind, bundleID!)
break
default:
statusCode = kLSUnknownErr
break
}
do {
try displayAlert(error: statusCode, arg1: (bundleID != nil ? bundleID : inApplication), arg2: self.contentType ?? self.kind)
} catch { print(error) }
}
}
+475 -451
View File
@@ -1,11 +1,11 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
@@ -14,449 +14,473 @@ import AppKit
@_silgen_name("_UTCopyDeclaredTypeIdentifiers") func UTCopyDeclaredTypeIdentifiers() -> NSArray
/**
Functions wrapping varied Launch Services tasks to be re-used throughout the application.
*/
Functions wrapping varied Launch Services tasks to be re-used throughout the application.
*/
class LSWrappers {
/**
Wrapper for commonly-used errors associated to Launch Services.
- appNotFound: Application not found at given path/URL.
- notAnApp: Found item at given path/URL but it is not an application bundle.
- invalidFileURL: Trying to locate a file with a scheme different from file://
- invalidScheme: Supplied URL Scheme is malformed or contains invalid characters.
- deletedApp: An application bundle was found, but it is currently in the Trash.
- serverErr: Can't communicate with the Launch Services server.
- incompatibleSys: A valid application bundle was found, but it is not compatible with the current version of macOS.
- invalidBundle: The specified bundle does not have a valid CFBundlePackageType entry.
- defaultErr: Unknown error, for cases not covered above.
*/
internal enum LSErrors:OSStatus {
case appNotFound = -10814
case notAnApp = -10811
case invalidFileURL = 262
case invalidScheme = -30774
case deletedApp = -10660
case serverErr = -10822
case incompatibleSys = -10825
case defaultErr = -10810
case invalidBundle = -67857
init(value: OSStatus) {
switch value {
case -10814: self = .appNotFound
case -30774: self = .invalidScheme
case -10811: self = .notAnApp
case 262: self = .invalidFileURL
case -10660: self = .deletedApp
case -10822: self = .serverErr
case -10825: self = .incompatibleSys
case -67857: self = .invalidBundle
default: self = .defaultErr
}
}
/**
Print a user-readable error message for each error code.
- Parameter argument:
app: The application specified by the user, which could conceivably be a URL, a file-path, a bundle identifier or even a display name.
content: This is only used in the case the user supplies a malformed URL Scheme.
- Returns: Human-readable error message specifying the problem, or unknown error if the problem is something not accounted for here.
*/
func print(argument: (app: String, content: String)) -> String {
switch self {
case .notAnApp: return "\(argument.app) is not a valid application."
case .appNotFound: return "No application found for \(argument.app)"
case .invalidScheme: return "\(argument.content) is not a valid URL Scheme."
case .invalidFileURL: return "\(argument.app) is not a valid filesystem URL."
case .deletedApp: return "\(argument.app) cannot be accessed because it is in the Trash."
case .serverErr: return "There was an error trying to communicate with the Launch Services Server."
case .incompatibleSys: return "\(argument.app) is not compatible with the currently installed version of macOS."
case .invalidBundle: return "\(argument.app) is not a valid Package."
case .defaultErr: return "An unknown error has occurred."
}
}
}
/**
Groups functions dealing with UTIs.
*/
class UTType {
/**
Copies a list of file-extensions for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Returns: An array of strings corresponding to file-extensions for that UTI, or nil.
*/
static func copyExtensionsFor(_ inUTI: String) -> [String]? {
if let result = (UTTypeCopyAllTagsWithClass(inUTI as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue()) {
if let extensions = result as? [String] {
return extensions
}
else { return nil }
}
else { return nil }
}
/**
Copies the bundle identifier of the application currently registered as the default handler for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Parameter inRoles: The specified Launch Services Role to query. Can correspond to "Editor", "Viewer", "Shell" or "None". By default, we are only concerned with viewers and editors (in that order).
- Returns: The Bundle identifier or POSIX path of an application, or nil if no valid handler was found.
*/
static func copyDefaultHandler (_ inUTI:String, inRoles: LSRolesMask = [LSRolesMask.viewer,LSRolesMask.editor], asPath: Bool = true) -> String? {
if let value = LSCopyDefaultRoleHandlerForContentType(inUTI as CFString, inRoles) {
let handlerID = value.takeRetainedValue() as String
if (asPath == true) {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
return handlerURL.path
}
else { return nil }
}
else { return handlerID }
}
else { return nil }
}
/**
Creates a list of all currently registered handlers for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Parameter inRoles: The specified Launch Services Role to query. Can correspond to "Editor", "Viewer", "Shell" or "None". By default, we are only concerned with viewers and editors (in that order).
- Returns: An array of strings corresponding to the Bundle identifiers or POSIX paths of all currently registered handlers, or nil of none were found.
*/
static func copyAllHandlers (_ inUTI:String, inRoles: LSRolesMask = [LSRolesMask.viewer,LSRolesMask.editor], asPath: Bool = true) -> Array<String>? {
var handlers: Array<String> = []
if let value = LSCopyAllRoleHandlersForContentType(inUTI as CFString, inRoles) {
let handlerIDs = (value.takeRetainedValue() as! Array<String>)
if (asPath == true) {
for handlerID in handlerIDs {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
handlers.append(handlerURL.path)
}
}
}
else { return handlerIDs }
}
else { return nil }
return (handlers.isEmpty ? nil : handlers)
}
/**
Creates a keyed dictionary of all currently-registered UTIs and their default handler. Excludes abstract entries like references to physical devices and such things we have no use for.
- Returns: A dictionary with UTIs as keys and bundle identifiers as values.
*/
static func copyAllUTIs () -> [String:String] {
let UTIs = (UTCopyDeclaredTypeIdentifiers() as! Array<String>).filter() { UTTypeConformsTo($0 as CFString,"public.item" as CFString) || UTTypeConformsTo($0 as CFString,"public.content" as CFString)} // Ignore UTIs belonging to devices and such.
var handlers:Array<String> = []
for UTI in UTIs {
if let handler = UTType.copyDefaultHandler(UTI) {
handlers.append(handler)
}
else {
handlers.append("No application set.")
}
}
return Dictionary.init (keys: UTIs, values: handlers)
}
/**
Changes the default handler for a given UTI.
- See Also: `enum LSErrors` above.
- Parameters:
- inContent: A Uniform Type Identifier.
- inBundleID: A bundle-identifier referring to a valid application bundle. Specifying "None" will disable the default handler for that UTI.
- inRoles: The specified Launch Services Role to modify. Can correspond to "Editor", "Viewer", "Shell" or "None".
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func setDefaultHandler (_ inContent: String, _ inBundleID: String, _ inRoles: LSRolesMask = LSRolesMask.all) -> OSStatus {
var retval: OSStatus = 0
if (LSWrappers.isAppInstalled(withBundleID: inBundleID) == true) {
retval = LSSetDefaultRoleHandlerForContentType(inContent as CFString, inRoles, inBundleID as CFString)
}
else { retval = kLSApplicationNotFoundErr }
return retval
}
}
/**
Groups functions dealing with URL Schemes.
*/
class Schemes {
/**
Traverses Info dictionaries of possible handlers for an URL Scheme and gets a display name, if available.
- Parameter inScheme: An URL Scheme.
- Returns: A display name, or `nil` if none was found.
*/
static func getNameForScheme (_ inScheme: String) -> String? {
var schemeName: String? = nil
if let handlers = Schemes.copyAllHandlers(inScheme) {
for handler in handlers {
if let schemeDicts = (Bundle(path:handler)?.infoDictionary?["CFBundleURLTypes"] as? [[String:AnyObject]]) {
for schemeDict in (schemeDicts.filter() { (($0["CFBundleURLSchemes"] as? [String])?.contains() {$0.caseInsensitiveCompare(inScheme) == .orderedSame}) == true } ) {
if let name = (schemeDict["CFBundleURLName"] as? String) {
schemeName = name
return schemeName
}
else { schemeName = nil }
}
}
}
}
return schemeName
}
/**
Creates a list of all currently registered URL Schemes and their default handler.
- Returns: A dictionary with URL Schemes as keys and bundle identifiers as values.
*/
static func copySchemesAndHandlers() -> [String:String]? {
var schemes_array: NSArray?
var apps_array: NSMutableArray?
if (LSCopySchemesAndHandlerURLs(&schemes_array, &apps_array) == 0) {
if let URLArray = (apps_array! as NSArray) as? [URL] {
if let pathsArray = convertAppURLsToPaths(URLArray) {
let schemesHandlers = Dictionary.init (keys: schemes_array as! [String], values: pathsArray)
return schemesHandlers
}
else { return nil }
}
else { return nil }
}
else { return nil }
}
/**
Copies the bundle identifier of the application currently registered as the default handler for a given URL Scheme.
- Parameter inScheme: A valid URL Scheme.
- Returns: The Bundle identifier or POSIX path of an application, or nil if no valid handler was found.
*/
static func copyDefaultHandler (_ inScheme:String, asPath: Bool = true) -> String? {
if let value = LSCopyDefaultHandlerForURLScheme(inScheme as CFString) {
let handlerID = value.takeRetainedValue() as String
if (asPath == true) {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
return handlerURL.path
}
else { return nil }
}
else { return handlerID }
}
else { return nil }
}
/**
Creates a list of all currently registered handlers for a given URL Scheme.
- Parameter inScheme: A valid URL Scheme.
- Returns: An array of strings corresponding to the Bundle identifiers or POSIX paths of all currently registered handlers, or nil of none were found.
*/
static func copyAllHandlers (_ inScheme:String, asPath: Bool = true) -> Array<String>? {
var handlers: Array<String> = []
if let value = LSCopyAllHandlersForURLScheme(inScheme as CFString) {
let handlerIDs = (value.takeRetainedValue() as! Array<String>)
if (asPath == true) {
for handlerID in handlerIDs {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
handlers.append(handlerURL.path)
}
}
}
else { return handlerIDs }
}
else { return nil }
return (handlers.isEmpty ? nil : handlers)
}
/**
Changes the default handler for a given URL Scheme.
- See Also: `enum LSErrors` above.
- Parameters:
- inScheme: A valid URL Scheme.
- inBundleID: A bundle-identifier referring to a valid application bundle. Specifying "None" will disable the default handler for that URL Scheme.
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func setDefaultHandler (_ inScheme: String, _ inBundleID: String) -> OSStatus {
var retval: OSStatus = kLSUnknownErr
if let matches = inScheme =~ /"\\A[a-zA-Z][a-zA-Z0-9.+-]+$" {
if (matches == true) {
if (LSWrappers.isAppInstalled(withBundleID:inBundleID) == true) {
retval = LSSetDefaultHandlerForURLScheme((inScheme as CFString), (inBundleID as CFString))
}
else { retval = kLSApplicationNotFoundErr }
}
else { retval = Int32(kURLUnsupportedSchemeError) }
}
else { retval = Int32(kURLUnsupportedSchemeError) }
return retval
}
}
/**
Creates a list of all currently registered applications.
- Returns: An array of strings corresponding to the paths of all applications currently registered with Launch Services.
*/
static func copyAllApps () -> Array<String>? {
var apps: NSMutableArray?
if (LSCopyAllApplicationURLs(&apps) == 0) {
if let appURLs = (apps! as NSArray) as? [URL] {
if let pathsArray = convertAppURLsToPaths(appURLs) {
return pathsArray
}
else { return nil }
}
else { return nil }
}
else { return nil }
}
/**
Checks whether a given application is registered with Launch Services.
- Parameter withBundleID: A bundle identifier.
- 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
}
}
/**
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.
- See Also: `enum LSErrors` above.
- Parameter inParam: The application to locate. It might correspond to a file-system URL, a POSIX path, a display name, a bundle identifier, or "None".
- Parameter outBundleID: This parameter is populated with a bundle identifier if a valid application bundle corresponding to the input parameter was found.
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func getBundleID (_ inParam: String, outBundleID: inout String?) -> OSStatus {
outBundleID = nil
var errCode = OSStatus()
let filemanager = FileManager.default
if (inParam == "None") { // None is a valid value for our dummy application.
outBundleID = "cl.fail.lordkamina.ThisAppDoesNothing"
return 0
}
if NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: inParam) != nil { // Check whether we have a valid Bundle ID for an application.
outBundleID = inParam
return 0
}
else if let appPath = NSWorkspace.shared.fullPath(forApplication: inParam) { // Or an application designed by name
if let bundle = Bundle(path:appPath) {
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL" || type == "FNDR") {
if let _ = bundle.bundleIdentifier {
outBundleID = bundle.bundleIdentifier
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
if (filemanager.fileExists(atPath: inParam) == true) { return kLSNotAnApplicationErr }
else { return kLSApplicationNotFoundErr }
}
else {
if let bundle = Bundle(path: inParam) { // Is it a valid bundle path?
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL") {
if let _ = bundle.bundleIdentifier {
outBundleID = bundle.bundleIdentifier
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
else {
if (filemanager.fileExists(atPath: inParam) == true) { // Maybe it's a valid file path, but not an app bundle?
return kLSNotAnApplicationErr
}
if let url = URL(string: inParam) { // Let's fallback to an URL.
if (url.path != "") {
if (url.isFileURL == true) {
if (filemanager.fileExists(atPath: url.path) == true) { //Is it a valid app URL?
if let bundle = Bundle(url: url) {
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL") {
outBundleID = bundle.bundleIdentifier!
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
else { return kLSNotAnApplicationErr } // Maybe it's a valid file URL, but not an app bundle?
}
else {
return kLSApplicationNotFoundErr
} // No application found at this location.
}
else { return kLSNotAnApplicationErr }
}
else {
if (url.isFileURL == false) { return Int32(NSFileReadUnsupportedSchemeError) }
}
}
else {
return kLSNotAnApplicationErr
}
}
}
return kLSUnknownErr
}
/**
Creates a list of UTIs and URL Schemes an application claims to be able to handle.
- Note: We perform little if any sanity checks in this function because it is not intended to be exposed to user input.
- Parameter inApp: A POSIX path corresponding to a valid application bundle.
- Returns: A dictionary of sets containing a list of strings corresponding to URL Schemes and Uniform Type Identifiers listed in CFBundleURLTypes and CFBundleDocumentTypes respectively.
*/
static func copySchemesAndUTIsForApp (_ inApp: String) -> [String:[String:Set<String>]]? {
var handledUrlSchemes: [String:Set<String>] = ["Viewer":[]]
var handledUTIs: [String:Set<String>] = ["Editor":[],"Viewer":[],"Shell":[]]
var handledTypes: [String:[String:Set<String>]] = [:]
if let infoDict = Bundle(path: inApp)?.infoDictionary {
guard ((infoDict["CFBundlePackageType"] as? String) == "APPL") else { return nil }
if let schemeDicts = (infoDict["CFBundleURLTypes"] as? [[String:AnyObject]]) {
for schemeDict in schemeDicts {
if let schemesArray = (schemeDict["CFBundleURLSchemes"] as? [String]) {
handledUrlSchemes["Viewer"]!.formUnion(schemesArray)
}
}
}
if let utiDicts = (infoDict["CFBundleDocumentTypes"] as? [[String:AnyObject]]) {
var utiArray: [String] = []
for utiDict in utiDicts.filter({ ($0["CFBundleTypeRole"] as? String == "Editor" || $0["CFBundleTypeRole"] as? String == "Viewer" || $0["CFBundleTypeRole"] as? String == "Shell" || $0["CFBundleTypeRole"] == nil) }) {
let typeRole = utiDict["CFBundleTypeRole"] as? String ?? "Viewer"
if let utiArray = (utiDict["LSItemContentTypes"] as? [String]) {
handledUTIs[typeRole]!.formUnion(utiArray)
}
else if let fileExtArray = (utiDict["CFBundleTypeExtensions"] as? [String]) {
for fileExt in fileExtArray {
if let newUTI = (UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExt as CFString, "public.content" as CFString)?.takeRetainedValue() as String?) {
if ((!UTTypeIsDynamic(newUTI as CFString)) && (handledUTIs[typeRole]!.index(of:newUTI) == nil)) {
utiArray.append(newUTI)
}
}
}
}
handledUTIs[typeRole]!.formUnion(utiArray)
}
}
}
handledTypes["URLs"] = !handledUrlSchemes.isEmpty ? handledUrlSchemes : [:]
handledTypes["UTIs"] = !handledUTIs.isEmpty ? handledUTIs : [:]
return handledTypes
}
/**
Wrapper for commonly-used errors associated to Launch Services.
- invalidBundle: The specified bundle does not have a valid CFBundlePackageType entry.
- invalidScheme: Supplied URI Scheme is malformed or contains invalid characters.
- incompatibleSys: A valid application bundle was found, but it is not compatible with the current version of macOS.
- serverErr: Can't communicate with the Launch Services server.
- appNotFound: Application not found at given path/URL.
- notAnApp: Found item at given path/URL but it is not an application bundle.
- deletedApp: An application bundle was found, but it is currently in the Trash.
- defaultErr: Unknown error, for cases not covered above.
- invalidFileURL: Trying to locate a file with a scheme different from file://
- noError: No error occured.
*/
internal enum LSErrors:OSStatus {
case invalidBundle = -67857
case invalidScheme = -30774
case incompatibleSys = -10825
case serverErr = -10822
case appNotFound = -10814
case notAnApp = -10811
case defaultErr = -10810
case deletedApp = -10660
case invalidFileURL = 262
case noError = 0
init(value: OSStatus) {
switch value {
case -67857: self = .invalidBundle
case -30774: self = .invalidScheme
case -10825: self = .incompatibleSys
case -10822: self = .serverErr
case -10814: self = .appNotFound
case -10811: self = .notAnApp
case -10660: self = .deletedApp
case -10810: self = .defaultErr
case 262: self = .invalidFileURL
case 0: self = .noError
default: self = .defaultErr
}
}
/**
Print a user-readable error message for each error code.
- Parameter argument:
app: The application specified by the user, which could conceivably be a URL, a file-path, a bundle identifier or even a display name.
content: This is only used in the case the user supplies a malformed URI Scheme.
- Returns: Human-readable error message specifying the problem, or unknown error if the problem is something not accounted for here.
*/
func print(argument: (app: String?, content: String?) = (String(),String())) -> String {
var retValue = (self == .noError ? "SwiftDefaultApps SUCCESS: " : "SwiftDefaultApps ERROR \(self.rawValue): ")
switch self {
case .notAnApp: retValue += "\(argument.app!) is not a valid application."
case .appNotFound: retValue += "No application found for \(argument.app!)"
case .invalidScheme: retValue += "\(argument.content!) is not a valid URI Scheme."
case .invalidFileURL: retValue += "\(argument.app!) is not a valid filesystem URL."
case .deletedApp: retValue += "\(argument.app!) cannot be accessed because it is in the Trash."
case .serverErr: retValue += "There was an error trying to communicate with the Launch Services Server."
case .incompatibleSys: retValue += "\(argument.app!) is not compatible with the currently installed version of macOS."
case .invalidBundle: retValue += "\(argument.app!) is not a valid Package."
case .noError: retValue += "Default handler has succesfully changed to \(argument.app!)"
case .defaultErr: retValue += "An unknown error has occurred."
}
if ((self == .appNotFound || self == .deletedApp) && (argument.app == "None" || argument.app == "Do Nothing" || argument.app == "cl.fail.lordkamina.ThisAppDoesNothing")) { retValue += " (This error occurs when ThisAppDoesNothing.app is not found.)" }
return retValue
}
}
/**
Groups functions dealing with UTIs.
*/
class UTType {
/**
Copies a list of file-extensions for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Returns: An array of strings corresponding to file-extensions for that UTI, or nil.
*/
static func copyExtensionsFor(_ inUTI: String) -> [String]? {
if let result = (UTTypeCopyAllTagsWithClass(inUTI as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue()) {
if let extensions = result as? [String] {
return extensions
}
else { return nil }
}
else { return nil }
}
/**
Copies the bundle identifier of the application currently registered as the default handler for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Parameter inRoles: The specified Launch Services Role to query. Can correspond to "Editor", "Viewer", "Shell" or "None". By default, we are only concerned with viewers and editors (in that order).
- Returns: The Bundle identifier or POSIX path of an application, or nil if no valid handler was found.
*/
static func copyDefaultHandler (_ inUTI:String, inRoles: LSRolesMask = [LSRolesMask.viewer,LSRolesMask.editor], asPath: Bool = true) -> String? {
if let value = LSCopyDefaultRoleHandlerForContentType(inUTI as CFString, inRoles) {
let handlerID = value.takeRetainedValue() as String
if (asPath == true) {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
return handlerURL.path
}
else { return nil }
}
else { return handlerID }
}
else { return nil }
}
/**
Creates a list of all currently registered handlers for a given UTI.
- Parameter inUTI: A Uniform Type Identifier.
- Parameter inRoles: The specified Launch Services Role to query. Can correspond to "Editor", "Viewer", "Shell" or "None". By default, we are only concerned with viewers and editors (in that order).
- Returns: An array of strings corresponding to the Bundle identifiers or POSIX paths of all currently registered handlers, or nil of none were found.
*/
static func copyAllHandlers (_ inUTI:String, inRoles: LSRolesMask = [LSRolesMask.viewer,LSRolesMask.editor], asPath: Bool = true) -> Array<String>? {
var handlers: Array<String> = []
if let value = LSCopyAllRoleHandlersForContentType(inUTI as CFString, inRoles) {
let handlerIDs = (value.takeRetainedValue() as! Array<String>)
if (asPath == true) {
for handlerID in handlerIDs {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
handlers.append(handlerURL.path)
}
}
}
else { return handlerIDs }
}
else { return nil }
return (handlers.isEmpty ? nil : handlers)
}
/**
Creates a keyed dictionary of all currently-registered UTIs and their default handler. Excludes abstract entries like references to physical devices and such things we have no use for.
- Returns: A dictionary with UTIs as keys and bundle identifiers as values.
*/
static func copyAllUTIs () -> [String:String] {
let UTIs = (UTCopyDeclaredTypeIdentifiers() as! Array<String>).filter() { UTTypeConformsTo($0 as CFString,"public.item" as CFString) || UTTypeConformsTo($0 as CFString,"public.content" as CFString)} // Ignore UTIs belonging to devices and such.
var handlers:Array<String> = []
for UTI in UTIs {
if let handler = UTType.copyDefaultHandler(UTI) {
handlers.append(handler)
}
else {
handlers.append("No application set.")
}
}
return Dictionary.init (keys: UTIs, values: handlers)
}
/**
Changes the default handler for a given UTI.
- See Also: `enum LSErrors` above.
- Parameters:
- inContent: A Uniform Type Identifier.
- inBundleID: A bundle-identifier referring to a valid application bundle. Specifying "None" will disable the default handler for that UTI.
- inRoles: The specified Launch Services Role to modify. Can correspond to "Editor", "Viewer", "Shell" or "None".
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func setDefaultHandler (_ inContent: String, _ inBundleID: String, _ inRoles: LSRolesMask = LSRolesMask.all) -> OSStatus {
var retval: OSStatus = 0
if (LSWrappers.isAppInstalled(withBundleID: inBundleID) == true) {
retval = LSSetDefaultRoleHandlerForContentType(inContent as CFString, inRoles, inBundleID as CFString)
}
else { retval = kLSApplicationNotFoundErr }
return retval
}
}
/**
Groups functions dealing with URI Schemes.
*/
class Schemes {
/**
Traverses Info dictionaries of possible handlers for an URI Scheme and gets a display name, if available.
- Parameter inScheme: An URI Scheme.
- Returns: A display name, or `nil` if none was found.
*/
static func getNameForScheme (_ inScheme: String) -> String? {
var schemeName: String? = nil
if let handlers = Schemes.copyAllHandlers(inScheme) {
for handler in handlers {
if let schemeDicts = (Bundle(path:handler)?.infoDictionary?["CFBundleURLTypes"] as? [[String:AnyObject]]) {
for schemeDict in (schemeDicts.filter() { (($0["CFBundleURLSchemes"] as? [String])?.contains() {$0.caseInsensitiveCompare(inScheme) == .orderedSame}) == true } ) {
if let name = (schemeDict["CFBundleURLName"] as? String) {
schemeName = name
return schemeName
}
else { schemeName = nil }
}
}
}
}
return schemeName
}
/**
Creates a list of all currently registered URI Schemes and their default handler.
- Returns: A dictionary with URI Schemes as keys and bundle identifiers as values.
*/
static func copySchemesAndHandlers() -> [String:String]? {
var schemes_array: NSArray?
var apps_array: NSMutableArray?
if (LSCopySchemesAndHandlerURLs(&schemes_array, &apps_array) == 0) {
if let URLArray = (apps_array! as NSArray) as? [URL] {
if let pathsArray = convertAppURLsToPaths(URLArray) {
let schemesHandlers = Dictionary.init (keys: schemes_array as! [String], values: pathsArray)
return schemesHandlers
}
else { return nil }
}
else { return nil }
}
else { return nil }
}
/**
Copies the bundle identifier of the application currently registered as the default handler for a given URI Scheme.
- Parameter inScheme: A valid URI Scheme.
- Returns: The Bundle identifier or POSIX path of an application, or nil if no valid handler was found.
*/
static func copyDefaultHandler (_ inScheme:String, asPath: Bool = true) -> String? {
if let value = LSCopyDefaultHandlerForURLScheme(inScheme as CFString) {
let handlerID = value.takeRetainedValue() as String
if (asPath == true) {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
return handlerURL.path
}
else { return nil }
}
else { return handlerID }
}
else { return nil }
}
/**
Creates a list of all currently registered handlers for a given URI Scheme.
- Parameter inScheme: A valid URI Scheme.
- Returns: An array of strings corresponding to the Bundle identifiers or POSIX paths of all currently registered handlers, or nil of none were found.
*/
static func copyAllHandlers (_ inScheme:String, asPath: Bool = true) -> Array<String>? {
var handlers: Array<String> = []
if let value = LSCopyAllHandlersForURLScheme(inScheme as CFString) {
let handlerIDs = (value.takeRetainedValue() as! Array<String>)
if (asPath == true) {
for handlerID in handlerIDs {
if let handlerURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: handlerID) {
handlers.append(handlerURL.path)
}
}
}
else { return handlerIDs }
}
else { return nil }
return (handlers.isEmpty ? nil : handlers)
}
/**
Changes the default handler for a given URI Scheme.
- See Also: `enum LSErrors` above.
- Parameters:
- inScheme: A valid URI Scheme.
- inBundleID: A bundle-identifier referring to a valid application bundle. Specifying "None" will disable the default handler for that URI Scheme.
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func setDefaultHandler (_ inScheme: String, _ inBundleID: String) -> OSStatus {
var retval: OSStatus = kLSUnknownErr
if ((inScheme =~ /"\\A[a-zA-Z][a-zA-Z0-9.+-]+$") == true) {
if (LSWrappers.isAppInstalled(withBundleID:inBundleID) == true) {
retval = LSSetDefaultHandlerForURLScheme((inScheme as CFString), (inBundleID as CFString))
}
else { retval = kLSApplicationNotFoundErr }
}
else { retval = Int32(kURLUnsupportedSchemeError) }
return retval
}
}
/**
Creates a list of all currently registered applications.
- Returns: An array of strings corresponding to the paths of all applications currently registered with Launch Services.
*/
static func copyAllApps () -> Array<String>? {
var apps: NSMutableArray?
if (LSCopyAllApplicationURLs(&apps) == 0) {
if let appURLs = (apps! as NSArray) as? [URL] {
if let pathsArray = convertAppURLsToPaths(appURLs) {
return pathsArray
}
else { return nil }
}
else { return nil }
}
else { return nil }
}
/**
Checks whether a given application is registered with Launch Services.
- Parameter withBundleID: A bundle identifier.
- Returns: `true` if the bundle identifier is registered with Launch Services as an application, `false` otherwise.
*/
static func isAppInstalled (withBundleID: String) -> Bool {
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.
- See Also: `enum LSErrors` above.
- Parameter inParam: The application to locate. It might correspond to a file-system URL, a POSIX path, a display name, a bundle identifier, or "None".
- Parameter outBundleID: This parameter is populated with a bundle identifier if a valid application bundle corresponding to the input parameter was found.
- Returns: A status-code. `0` on success, or a value corresponding to various possible errors.
*/
static func getBundleID (_ inParam: String, outBundleID: inout String?) -> OSStatus {
var errCode = OSStatus()
let filemanager = FileManager.default
if (inParam == "None") { // None is a valid value for our dummy application.
outBundleID = "cl.fail.lordkamina.ThisAppDoesNothing"
return 0
}
if NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: inParam) != nil { // Check whether we have a valid Bundle ID for an application.
outBundleID = inParam
return 0
}
else if let appPath = NSWorkspace.shared.fullPath(forApplication: inParam) { // Or an application designed by name
if let bundle = Bundle(path:appPath) {
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL" || type == "FNDR") {
if let _ = bundle.bundleIdentifier {
outBundleID = bundle.bundleIdentifier
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
if (filemanager.fileExists(atPath: inParam) == true) { return kLSNotAnApplicationErr }
else { return kLSApplicationNotFoundErr }
}
else {
if let bundle = Bundle(path: inParam) { // Is it a valid bundle path?
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL") {
if let _ = bundle.bundleIdentifier {
outBundleID = bundle.bundleIdentifier
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
else {
if (filemanager.fileExists(atPath: inParam) == true) { // Maybe it's a valid file path, but not an app bundle?
return kLSNotAnApplicationErr
}
if let url = URL(string: inParam) { // Let's fallback to an URL.
if (url.path != "") {
if (url.isFileURL == true) {
if (filemanager.fileExists(atPath: url.path) == true) { //Is it a valid app URL?
if let bundle = Bundle(url: url) {
if let type = bundle.getType(outError: &errCode) {
if (type == "APPL") {
outBundleID = bundle.bundleIdentifier!
return 0
}
else { return kLSNotAnApplicationErr }
}
else { return errCode }
}
else { return kLSNotAnApplicationErr } // Maybe it's a valid file URL, but not an app bundle?
}
else {
return kLSApplicationNotFoundErr
} // No application found at this location.
}
else { return kLSNotAnApplicationErr }
}
else {
if (url.isFileURL == false) { return Int32(NSFileReadUnsupportedSchemeError) }
}
}
else {
return kLSNotAnApplicationErr
}
}
}
return kLSUnknownErr
}
/**
Creates a list of UTIs and URI Schemes an application claims to be able to handle.
- Note: We perform little if any sanity checks in this function because it is not intended to be exposed to user input.
- Parameter inApp: A POSIX path corresponding to a valid application bundle.
- Returns: A dictionary of sets containing a list of strings corresponding to URI Schemes and Uniform Type Identifiers listed in CFBundleURLTypes and CFBundleDocumentTypes respectively.
*/
static func copySchemesAndUTIsForApp (_ inApp: String) -> [String:[String:Set<String>]]? {
var handledUriSchemes: [String:Set<String>] = ["Viewer":[]]
var handledUTIs: [String:Set<String>] = ["Editor":[],"Viewer":[],"Shell":[]]
var handledTypes: [String:[String:Set<String>]] = [:]
if let infoDict = Bundle(path: inApp)?.infoDictionary {
guard ((infoDict["CFBundlePackageType"] as? String) == "APPL") else { return nil }
if let schemeDicts = (infoDict["CFBundleURLTypes"] as? [[String:AnyObject]]) {
for schemeDict in schemeDicts {
if let schemesArray = (schemeDict["CFBundleURLSchemes"] as? [String]) {
handledUriSchemes["Viewer"]!.formUnion(schemesArray)
}
}
}
if let utiDicts = (infoDict["CFBundleDocumentTypes"] as? [[String:AnyObject]]) {
var utiArray: [String] = []
for utiDict in utiDicts.filter({ ($0["CFBundleTypeRole"] as? String == "Editor" || $0["CFBundleTypeRole"] as? String == "Viewer" || $0["CFBundleTypeRole"] as? String == "Shell" || $0["CFBundleTypeRole"] == nil) }) {
let typeRole = utiDict["CFBundleTypeRole"] as? String ?? "Viewer"
if let utiArray = (utiDict["LSItemContentTypes"] as? [String]) {
handledUTIs[typeRole]!.formUnion(utiArray)
}
else if let fileExtArray = (utiDict["CFBundleTypeExtensions"] as? [String]) {
for fileExt in fileExtArray {
if let newUTI = (UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExt as CFString, "public.content" as CFString)?.takeRetainedValue() as String?) {
if ((!UTTypeIsDynamic(newUTI as CFString)) && (handledUTIs[typeRole]!.firstIndex(of:newUTI) == nil)) {
utiArray.append(newUTI)
}
}
}
}
handledUTIs[typeRole]!.formUnion(utiArray)
}
}
}
handledTypes["URIs"] = !handledUriSchemes.isEmpty ? handledUriSchemes : [:]
handledTypes["UTIs"] = !handledUTIs.isEmpty ? handledUTIs : [:]
return handledTypes
}
}
+284 -149
View File
@@ -1,59 +1,59 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/**
Converts an array of Application URLs to an array of Application Paths.
- Parameter inArray: An array of file-system URLs corresponding to applications.
- Returns: An array of strings corresponding to the POSIX paths of the supplied applications.
*/
Converts an array of Application URLs to an array of Application Paths.
- Parameter inArray: An array of file-system URLs corresponding to applications.
- Returns: An array of strings corresponding to the POSIX paths of the supplied applications.
*/
func convertAppURLsToPaths (_ inArray:Array<URL>?) -> Array<String>? {
if let URLArray = inArray {
var outputArray: Array<String> = []
for (i, app) in URLArray.enumerated() {
let temp = app.path
outputArray.insert(temp, at:i)
}
return outputArray
}
else { return nil }
if let URLArray = inArray {
var outputArray: Array<String> = []
for (i, app) in URLArray.enumerated() {
let temp = app.path
outputArray.insert(temp, at:i)
}
return outputArray
}
else { return nil }
}
/**
Creates a String representation of a Dictionary of Strings.
- Parameter inDict: A dictionary of Strings.
- Returns: A string corresponding to the contents of the supplied dictionary.
*/
Creates a String representation of a Dictionary of Strings.
- Parameter inDict: A dictionary of Strings.
- Returns: A string corresponding to the contents of the supplied dictionary.
*/
func copyDictionaryAsString (_ inDict: [(key:String, value:String)]?) -> String? {
var output = ""
if let temp = inDict {
for (key, value) in temp {
output+=("\(key)\t\t\t\t\(value)\n")
}
}
else { return nil }
return output
var output = ""
if let temp = inDict {
for (key, value) in temp {
output+=("\(key)\t\t\t\t\(value)\n")
}
}
else { return nil }
return output
}
/**
Creates a String representation of an Array of Strings.
- Parameter inArray: An array of strings.
- Parameter separator: The separator to be used.
- Returns: A string corresponding to the contents of the supplied array.
*/
Creates a String representation of an Array of Strings.
- Parameter inArray: An array of strings.
- Parameter separator: The separator to be used.
- Returns: A string corresponding to the contents of the supplied array.
*/
func copyStringArrayAsString (_ inArray: Array<String>?, separator: String = "\n") -> String? {
if let temp = inArray {
return temp.joined(separator:separator)
}
else { return nil }
if let temp = inArray {
return temp.joined(separator:separator)
}
else { return nil }
}
//// REGEX MATCHING CONVENIENCE OPERATORS.
@@ -62,131 +62,266 @@ infix operator =~
prefix operator /
func =~ (string: String, regex: NSRegularExpression?) -> Bool? {
guard let matches = regex?.numberOfMatches(in:string,
options: [],
range: NSMakeRange(0, string.count))
else { return nil }
return matches > 0
guard let matches = regex?.numberOfMatches(in:string,
options: [],
range: NSMakeRange(0, string.count))
else { return nil }
return matches > 0
}
prefix func /(pattern:String) -> NSRegularExpression? {
let options: NSRegularExpression.Options =
NSRegularExpression.Options.dotMatchesLineSeparators
guard let retval = try? NSRegularExpression(pattern:pattern,
options:options) else { return nil }
return retval
let options: NSRegularExpression.Options =
NSRegularExpression.Options.dotMatchesLineSeparators
guard let retval = try? NSRegularExpression(pattern:pattern,
options:options) else { return nil }
return retval
}
//// EXTENSIONS
extension String: Error {}
extension DispatchQueue {
static let labelPrefix = "io.zamzam.ZamzamKit"
static let database = DispatchQueue(label: "\(DispatchQueue.labelPrefix).database", qos: .utility)
}
public extension Collection {
/// Element at the given index if it exists.
///
/// - Parameter index: index of element.
subscript(safe index: Index) -> Element? {
// http://www.vadimbulavin.com/handling-out-of-bounds-exception/
return indices.contains(index) ? self[index] : nil
}
}
public extension Collection where Iterator.Element == (String, Any) {
/// Converts collection of objects to JSON string
var jsonString: String? {
guard JSONSerialization.isValidJSONObject(self),
let stringData = try? JSONSerialization.data(withJSONObject: self, options: []) else {
return nil
}
return String(data: stringData, encoding: .utf8)
}
}
public extension Array where Element: Equatable {
/// Array with all duplicates removed from it.
///
/// [1, 3, 3, 5, 7, 9].distinct // [1, 3, 5, 7, 9]
var distinct: [Element] {
// https://github.com/SwifterSwift/SwifterSwift
return reduce(into: [Element]()) {
guard !$0.contains($1) else { return }
$0.append($1)
}
}
/// Remove all duplicates from array.
///
/// var array = [1, 3, 3, 5, 7, 9]
/// array.removeDuplicates()
/// array // [1, 3, 5, 7, 9]
mutating func removeDuplicates() {
self = distinct
}
/// Removes the first occurance of the matched element.
///
/// var array = ["a", "b", "c", "d", "e", "a"]
/// array.remove("a")
/// array // ["b", "c", "d", "e", "a"]
///
/// - Parameter element: The element to remove from the array.
mutating func remove(_ element: Element) {
guard let index = firstIndex(of: element) else { return }
remove(at: index)
}
}
extension Bundle {
/**
Copies a bundle's package type, as specified in its Info.plist.
- Parameter outError: Populated with an error-code if the bundle does not correspond to a valid package.
- Returns: The four-letter code specifying the bundle's package type, or `nil` if an error is encountered.
*/
func getType (outError: inout OSStatus) -> String? {
if let info = self.infoDictionary {
if let type = info["CFBundlePackageType"] {
return String(describing: type)
}
else { outError = errSecInvalidBundleInfo; return nil }
}
else { outError = kLSUnknownErr; return nil }
}
/**
Copies a bundle's package type, as specified in its Info.plist.
- Parameter outError: Populated with an error-code if the bundle does not correspond to a valid package.
- Returns: The four-letter code specifying the bundle's package type, or `nil` if an error is encountered.
*/
func getType (outError: inout OSStatus) -> String? {
if let info = self.infoDictionary {
if let type = info["CFBundlePackageType"] {
return String(describing: type)
}
else { outError = errSecInvalidBundleInfo; return nil }
}
else { outError = kLSUnknownErr; return nil }
}
}
extension Dictionary
{
/**
Create a Dictionary by Merging two Arrays.
- Parameter keys: An Array to be used as dictionary keys.
- Parameter values: An Array to be used as values.
*/
public init(keys: [Key], values: [Value])
{
precondition(keys.count == values.count)
self.init()
for (index, key) in keys.enumerated()
{
self[key] = values[index]
}
}
/**
Create a Dictionary by Merging two Arrays.
- Parameter keys: An Array to be used as dictionary keys.
- Parameter values: An Array to be used as values.
*/
public init(keys: [Key], values: [Value])
{
precondition(keys.count == values.count)
self.init()
for (index, key) in keys.enumerated()
{
self[key] = values[index]
}
}
}
#if Prefpane
extension LSRolesMask {
/**
Bridge our custom type to the actual values used in LaunchServices methods.
*/
init (from value: SourceListRoleTypes) {
switch value {
case .Viewer: self = .viewer
case .Editor: self = .editor
case .Shell: self = .shell
case .All: self = .all
}
}
}
extension NSControl {
/** Shrinks a text label until it fits in a single line in its container. */
func fitWidth() {
var absoluteMaxWidth: CGFloat = 0.0
if let tempWidth = (self.superview?.frame.width) {
absoluteMaxWidth = tempWidth
absoluteMaxWidth -= (self.alignmentRectInsets.left + self.alignmentRectInsets.right)
}
let text = self.stringValue
guard self.font != nil else { return }
var font = self.font!
if ControllersRef.sharedInstance.originalFonts[self] == nil {
ControllersRef.sharedInstance.originalFonts[self] = font
}
else { font = ControllersRef.sharedInstance.originalFonts[self]! }
let richText = NSTextFieldCell(textCell:text)
richText.font = font
var neededWidth: CGFloat = richText.cellSize.width
var fontSize = font.pointSize
var newFont = font
guard absoluteMaxWidth > 0.0 else { return }
while (neededWidth >= absoluteMaxWidth) {
guard fontSize > 0.0 else { return }
fontSize -= 0.5
newFont = NSFont(descriptor:font.fontDescriptor, size: fontSize)!
richText.font = newFont
neededWidth = richText.cellSize.width
}
self.setValue(newFont, forKey:"font")
}
}
extension LSRolesMask {
/**
Bridge our custom type to the actual values used in LaunchServices methods.
*/
init (from value: SourceListRoleTypes) {
switch value {
case .Viewer: self = .viewer
case .Editor: self = .editor
case .Shell: self = .shell
case .All: self = .all
}
}
}
extension NSControl {
/** Shrinks a text label until it fits in a single line in its container. */
func fitWidth() {
var absoluteMaxWidth: CGFloat = 0.0
if let tempWidth = (self.superview?.frame.width) {
absoluteMaxWidth = tempWidth
absoluteMaxWidth -= (self.alignmentRectInsets.left + self.alignmentRectInsets.right)
}
let text = self.stringValue
guard self.font != nil else { return }
var font = self.font!
if ControllersRef.sharedInstance.originalFonts[self] == nil {
ControllersRef.sharedInstance.originalFonts[self] = font
}
else { font = ControllersRef.sharedInstance.originalFonts[self]! }
let richText = NSTextFieldCell(textCell:text)
richText.font = font
var neededWidth: CGFloat = richText.cellSize.width
var fontSize = font.pointSize
var newFont = font
guard absoluteMaxWidth > 0.0 else { return }
while (neededWidth >= absoluteMaxWidth) {
guard fontSize > 0.0 else { return }
fontSize -= 0.5
newFont = NSFont(descriptor:font.fontDescriptor, size: fontSize)!
richText.font = newFont
neededWidth = richText.cellSize.width
}
self.setValue(newFont, forKey:"font")
}
}
#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. */
func smallCaps() -> NSFont? {
let settings = [[NSFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseType, NSFontDescriptor.FeatureKey.selectorIdentifier: kLowerCaseSmallCapsSelector]]
let attributes: [NSFontDescriptor.AttributeName: AnyObject] = [NSFontDescriptor.AttributeName.featureSettings: settings as AnyObject, NSFontDescriptor.AttributeName.name: fontName as AnyObject]
return NSFont(descriptor: NSFontDescriptor(fontAttributes: attributes), size: pointSize)
}
/** Returns a **Bold** version of the font it is invoked on. */
func bold() -> NSFont? {
let fontManager = NSFontManager.shared
return fontManager.convert(self, toHaveTrait: .boldFontMask)
}
/**
Returns an _Italic_ version of the font it is invoked on.
- Note: Not currently used.
*/
func italic() -> NSFont? {
let fontManager = NSFontManager.shared
return fontManager.convert(self, toHaveTrait: .italicFontMask)
}
/** Returns a SmallCaps version of the font it is invoked on. */
func smallCaps() -> NSFont? {
let settings = [[NSFontDescriptor.FeatureKey.typeIdentifier: kLowerCaseType, NSFontDescriptor.FeatureKey.selectorIdentifier: kLowerCaseSmallCapsSelector]]
let attributes: [NSFontDescriptor.AttributeName: AnyObject] = [NSFontDescriptor.AttributeName.featureSettings: settings as AnyObject, NSFontDescriptor.AttributeName.name: fontName as AnyObject]
return NSFont(descriptor: NSFontDescriptor(fontAttributes: attributes), size: pointSize)
}
/** Returns a **Bold** version of the font it is invoked on. */
func bold() -> NSFont? {
let fontManager = NSFontManager.shared
return fontManager.convert(self, toHaveTrait: .boldFontMask)
}
/**
Returns an _Italic_ version of the font it is invoked on.
- Note: Not currently used.
*/
func italic() -> NSFont? {
let fontManager = NSFontManager.shared
return fontManager.convert(self, toHaveTrait: .italicFontMask)
}
}
@@ -1,129 +1,133 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/** Represents information about an application and its associated URL Schemes and UTIs as an object. */
/** Represents information about an application and its associated URI Schemes and UTIs as an object. */
class SWDAApplicationInfo: NSObject {
var displayName: String?
var appPath: String?
var appURL: String?
var appDescription: String?
var appVersion: String?
var appBundleID: String?
var appIcon: NSImage?
lazy var handlers: [SWDAContentHandler] = { return [] }()
lazy var handledContent: [String: [String:[SWDAContentHandler]?]] = {
var wrappedHandlers: [String: [String:[SWDAContentHandler]?]] = [:]
if let handlers = LSWrappers.copySchemesAndUTIsForApp(self.appPath!) {
var wrappedURLHandlers: [String:[SWDAContentHandler]] = ["Viewer":[]]
var wrappedUTIHandlers: [String:[SWDAContentHandler]] = ["Viewer":[],"Editor":[],"Shell":[]]
for handlerRole in handlers["UTIs"]! {
let role = String(describing:handlerRole.key)
var handler: SWDAContentHandler?
for utiHandler in handlerRole.value {
handler = SWDAContentHandler(SWDAContentItem(type:SWDAContentType(rawValue:"UTI")!, utiHandler), appName: self.appBundleID!, role: SourceListRoleTypes(rawValue:role)!)
if let handler = handler {
wrappedUTIHandlers[role]!.append(handler)
}
}
}
for handlerRole in handlers["URLs"]! {
let role = String(describing:handlerRole.key)
var handler: SWDAContentHandler?
for urlHandler in handlerRole.value {
handler = SWDAContentHandler(SWDAContentItem(type:SWDAContentType(rawValue:"URL")!, urlHandler), appName: self.appBundleID!, role: SourceListRoleTypes(rawValue:role)!)
if let handler = handler {
wrappedURLHandlers[role]!.append(handler)
}
}
}
wrappedHandlers["UTIs"] = wrappedUTIHandlers
wrappedHandlers["URLs"] = wrappedURLHandlers
return wrappedHandlers
}
else { return ["URLs":["Viewer":[]],"UTIs":["Editor":[], "Viewer":[], "Shell":[]]] }
}()
/** Only applications that handle at least one URL Scheme or UTI will show up in the "Applications" tab. */
lazy var handlesURLs: Bool = {
for role in (self.handledContent["URLs"]!).values {
if !(role!.isEmpty) { return true }
}
return false
}()
lazy var handlesUTIs: Bool = {
for role in (self.handledContent["UTIs"]!).values {
if !(role!.isEmpty) { return true }
}
return false
}()
init?(_ inParam: String) {
var bundleID: String?
switch inParam {
case "Other...":
self.displayName = "Other..."
self.appVersion = ""
self.appPath = ""
self.appIcon = NSImage(byReferencingFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns")
default:
let result = LSWrappers.getBundleID(inParam, outBundleID: &bundleID)
if (result == 0) {
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID!)
guard appURL != nil else { return nil }
if let bundle = Bundle(url: appURL!) {
self.appBundleID = bundle.bundleIdentifier!
let name = (bundle.object(forInfoDictionaryKey: "CFBundleName") as? String)
let displayName = (bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)
var tempName: String?
if let displayName = displayName {
tempName = displayName
}
sanity: if let name = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String { // Theoretically, apps should always have a CFBundleName and most of the time, also a CFBundleDisplayName. In practice? Not so much. We should be prepared to handle broken apps.
guard (tempName == nil || tempName == "") else { break sanity }
tempName = name
}
else {
tempName = FileManager.default.displayName(atPath: bundle.bundlePath)
}
self.displayName = tempName
self.appURL = String(describing:bundle.bundleURL)
if let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
self.appVersion = version
}
else if let version = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String {
self.appVersion = version
}
else { self.appVersion = nil }
self.appPath = bundle.bundlePath
self.appIcon = NSWorkspace.shared.icon(forFile: bundle.bundlePath)
}
else {
NSLog(LSWrappers.LSErrors.init(value:result).print(argument: (app: inParam, content:"")))
return nil
}
}
else {
NSLog(LSWrappers.LSErrors.init(value:result).print(argument: (app: inParam, content:"")))
return nil
}
}
super.init()
}
var displayName: String?
var appPath: String?
var appURL: String?
var appDescription: String?
var appVersion: String?
var appBundleID: String?
var appIcon: NSImage?
lazy var handlers: [SWDAContentHandler] = { return [] }()
lazy var handledContent: [String: [String:[SWDAContentHandler]?]] = {
var wrappedHandlers: [String: [String:[SWDAContentHandler]?]] = [:]
if let handlers = LSWrappers.copySchemesAndUTIsForApp(self.appPath!) {
var wrappedURIHandlers: [String:[SWDAContentHandler]] = ["Viewer":[]]
var wrappedUTIHandlers: [String:[SWDAContentHandler]] = ["Viewer":[],"Editor":[],"Shell":[]]
for handlerRole in handlers["UTIs"]! {
let role = String(describing:handlerRole.key)
var handler: SWDAContentHandler?
for utiHandler in handlerRole.value {
handler = SWDAContentHandler(SWDAContentItem(type:SWDAContentType(rawValue:"UTI")!, utiHandler), appName: self.appBundleID!, role: SourceListRoleTypes(rawValue:role)!)
if let handler = handler {
wrappedUTIHandlers[role]!.append(handler)
}
}
}
for handlerRole in handlers["URIs"]! {
let role = String(describing:handlerRole.key)
var handler: SWDAContentHandler?
for urlHandler in handlerRole.value {
handler = SWDAContentHandler(SWDAContentItem(type:SWDAContentType(rawValue:"URI")!, urlHandler), appName: self.appBundleID!, role: SourceListRoleTypes(rawValue:role)!)
if let handler = handler {
wrappedURIHandlers[role]!.append(handler)
}
}
}
wrappedHandlers["UTIs"] = wrappedUTIHandlers
wrappedHandlers["URIs"] = wrappedURIHandlers
return wrappedHandlers
}
else { return ["URIs":["Viewer":[]],"UTIs":["Editor":[], "Viewer":[], "Shell":[]]] }
}()
/** Only applications that handle at least one URI Scheme or UTI will show up in the "Applications" tab. */
lazy var handlesURIs: Bool = {
for role in (self.handledContent["URIs"]!).values {
if !(role!.isEmpty) { return true }
}
return false
}()
lazy var handlesUTIs: Bool = {
for role in (self.handledContent["UTIs"]!).values {
if !(role!.isEmpty) { return true }
}
return false
}()
init?(_ inParam: String) {
var bundleID: String?
switch inParam {
case "Other...":
self.displayName = "Other..."
self.appVersion = ""
self.appPath = ""
self.appIcon = NSImage(byReferencingFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns")
default:
let result = LSWrappers.getBundleID(inParam, outBundleID: &bundleID)
if (result == 0) {
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID!)
guard appURL != nil else { return nil }
if let bundle = Bundle(url: appURL!) {
guard bundle.bundleIdentifier != nil else {
NSLog(LSWrappers.LSErrors.invalidBundle.print(argument: (app: inParam, content:"")))
return nil;
}
self.appBundleID = bundle.bundleIdentifier!
let name = (bundle.object(forInfoDictionaryKey: "CFBundleName") as? String)
let displayName = (bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)
var tempName: String?
if let displayName = displayName {
tempName = displayName
}
sanity: if let name = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String { // Theoretically, apps should always have a CFBundleName and most of the time, also a CFBundleDisplayName. In practice? Not so much. We should be prepared to handle broken apps.
guard (tempName == nil || tempName == "") else { break sanity }
tempName = name
}
else {
tempName = FileManager.default.displayName(atPath: bundle.bundlePath)
}
self.displayName = tempName
self.appURL = String(describing:bundle.bundleURL)
if let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
self.appVersion = version
}
else if let version = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String {
self.appVersion = version
}
else { self.appVersion = nil }
self.appPath = bundle.bundlePath
self.appIcon = NSWorkspace.shared.icon(forFile: bundle.bundlePath)
}
else {
NSLog(LSWrappers.LSErrors.init(value:result).print(argument: (app: inParam, content:"")))
return nil
}
}
else {
NSLog(LSWrappers.LSErrors.init(value:result).print(argument: (app: inParam, content:"")))
return nil
}
}
super.init()
}
}
@@ -1,199 +1,199 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/** Abstract protocol adopted by SWDAContentItem and SWDAApplicationItem, which define the appropriate classes to populate the NSTableView with. */
internal protocol SWDAContentProtocol: class {
var contentType: SWDAContentType { get set }
var contentName: String { get set }
var contentDescription: String { get set }
var displayName: String { get set }
var contentHandlers: [SWDATreeRow] { get set }
func getDescription() -> String
var appIcon: NSImage? { get }
var getExtensions: String { get }
internal protocol SWDAContentProtocol: NSObject {
var contentType: SWDAContentType { get set }
var contentName: String { get set }
var contentDescription: String { get set }
var displayName: String { get set }
var contentHandlers: [SWDATreeRow] { get set }
func getDescription() -> String
var appIcon: NSImage? { get }
var getExtensions: String { get }
}
/** The main class adopting SWDAContentProtocol, represents either a UTI or an URL; it's name, associated handlers, description in the case of URL Schemes and associated file-extensions in the case of UTIs.*/
/** The main class adopting SWDAContentProtocol, represents either a UTI or an URL; it's name, associated handlers, description in the case of URI Schemes and associated file-extensions in the case of UTIs.*/
class SWDAContentItem: NSObject, SWDAContentProtocol {
/** Generates the NSTreeView displaying all the handlers associated to this content type. Results are sorted alphabetically, with the exception of the special "Other..." and "Do Nothing" entries. */
@objc lazy var contentHandlers: [SWDATreeRow] = {
var roles: [SWDATreeRow] = []
var rolesArray: [SourceListRoleTypes] = []
switch self.contentType {
case .UTI:
rolesArray = [.Viewer, .Editor, .Shell]
case .URL:
rolesArray = [.Viewer]
case .Application: return []
}
for role in rolesArray {
var tempChildren: [SWDATreeRow] = []
let category = SWDATreeRow(role.rawValue)
if let handlerAppNames = (self.contentType == .URL) ? LSWrappers.Schemes.copyAllHandlers(self.contentName, asPath:true) : LSWrappers.UTType.copyAllHandlers(self.contentName, inRoles: LSRolesMask(from: role), asPath:true) {
for app in handlerAppNames {
let handler = SWDAContentHandler(self, appName:app, role:role)
if let tempName = handler.application?.displayName {
let displayName = (tempName.lowercased().range(of:".app") != nil) ? tempName : "\(tempName).app"
if (displayName != "Do Nothing.app") {
let row = SWDATreeRow(displayName, content: handler)
category.addChild(row)
}
}
else {
let displayName = FileManager.default.displayName(atPath: app)
if (displayName != "Do Nothing.app") {
let row = SWDATreeRow(displayName, content: handler)
category.addChild(row)
}
}
}
}
let other = SWDATreeRow("Other...",content: SWDAContentHandler(self, appName: "Other...", role:role))
category.addChild(other)
let none = SWDATreeRow("Do Nothing",content: SWDAContentHandler(self, appName: "cl.fail.lordkamina.ThisAppDoesNothing", role:role))
category.addChild(none)
category.children.sort(){($0.rowTitle < $1.rowTitle) && ($0.rowTitle != "Other..." && $0.rowTitle != "Do Nothing")}
roles.append(category)
}
return roles
}()
@objc var contentDescription: String = ""
@objc var contentName: String = ""
var contentType: SWDAContentType
/** Initializer. contentDescription is only really used for the "Internet" Tab. */
init (type: SWDAContentType, _ contentName: String, _ contentDescription: String? = nil) {
self.contentType = type
super.init()
self.setValue(contentName, forKey: "contentName")
if let description = contentDescription {
self.setValue(description, forKey: "contentDescription")
}
else {
self.setValue(self.getDescription(), forKey: "contentDescription")
}
}
@objc lazy var displayName: String = {
return self.value(forKey:ControllersRef.TabData.displayNameKeyPath) as! String
}()
@objc lazy var getExtensions: String = {
guard self.contentType == .UTI else { return "" }
if let extensions = copyStringArrayAsString(LSWrappers.UTType.copyExtensionsFor(self.contentName), separator:", ") {
return "Extensions: \(extensions)"
}
else { return "Extensions: " }
}()
func getDescription () -> String {
switch self.contentType {
case .URL:
if let contentDescription = LSWrappers.Schemes.getNameForScheme(self.contentName as String) {
self.setValue(contentDescription, forKey: "contentDescription")
return self.contentDescription
}
else { return "" }
case .UTI: if let desc = (UTTypeCopyDescription(self.contentName as CFString)?.takeRetainedValue() as String?) {
return desc
}
else { return "" }
case .Application: return ""
}
}
@objc lazy var appPath: String = { return "" }()
@objc var appIcon: NSImage? = nil
/** Generates the NSTreeView displaying all the handlers associated to this content type. Results are sorted alphabetically, with the exception of the special "Other..." and "Do Nothing" entries. */
@objc lazy var contentHandlers: [SWDATreeRow] = {
var roles: [SWDATreeRow] = []
var rolesArray: [SourceListRoleTypes] = []
switch self.contentType {
case .UTI:
rolesArray = [.Viewer, .Editor, .Shell]
case .URI:
rolesArray = [.Viewer]
case .Application: return []
}
for role in rolesArray {
var tempChildren: [SWDATreeRow] = []
let category = SWDATreeRow(role.rawValue)
if let handlerAppNames = (self.contentType == .URI) ? LSWrappers.Schemes.copyAllHandlers(self.contentName, asPath:true) : LSWrappers.UTType.copyAllHandlers(self.contentName, inRoles: LSRolesMask(from: role), asPath:true) {
for app in handlerAppNames {
let handler = SWDAContentHandler(self, appName:app, role:role)
if let tempName = handler.application?.displayName {
let displayName = (tempName.lowercased().range(of:".app") != nil) ? tempName : "\(tempName).app"
if (displayName != "Do Nothing.app") {
let row = SWDATreeRow(displayName, content: handler)
category.addChild(row)
}
}
else {
let displayName = FileManager.default.displayName(atPath: app)
if (displayName != "Do Nothing.app") {
let row = SWDATreeRow(displayName, content: handler)
category.addChild(row)
}
}
}
}
let other = SWDATreeRow("Other...",content: SWDAContentHandler(self, appName: "Other...", role:role))
category.addChild(other)
let none = SWDATreeRow("Do Nothing",content: SWDAContentHandler(self, appName: "cl.fail.lordkamina.ThisAppDoesNothing", role:role))
category.addChild(none)
category.children.sort(){($0.rowTitle < $1.rowTitle) && ($0.rowTitle != "Other..." && $0.rowTitle != "Do Nothing")}
roles.append(category)
}
return roles
}()
@objc var contentDescription: String = ""
@objc var contentName: String = ""
var contentType: SWDAContentType
/** Initializer. contentDescription is only really used for the "Internet" Tab. */
init (type: SWDAContentType, _ contentName: String, _ contentDescription: String? = nil) {
self.contentType = type
super.init()
self.setValue(contentName, forKey: "contentName")
if let description = contentDescription {
self.setValue(description, forKey: "contentDescription")
}
else {
self.setValue(self.getDescription(), forKey: "contentDescription")
}
}
@objc lazy var displayName: String = {
return self.value(forKey:ControllersRef.TabData.displayNameKeyPath) as! String
}()
@objc lazy var getExtensions: String = {
guard self.contentType == .UTI else { return "" }
if let extensions = copyStringArrayAsString(LSWrappers.UTType.copyExtensionsFor(self.contentName), separator:", ") {
return "Extensions: \(extensions)"
}
else { return "Extensions: " }
}()
func getDescription () -> String {
switch self.contentType {
case .URI:
if let contentDescription = LSWrappers.Schemes.getNameForScheme(self.contentName as String) {
self.setValue(contentDescription, forKey: "contentDescription")
return self.contentDescription
}
else { return "" }
case .UTI: if let desc = (UTTypeCopyDescription(self.contentName as CFString)?.takeRetainedValue() as String?) {
return desc
}
else { return "" }
case .Application: return ""
}
}
@objc lazy var appPath: String = { return "" }()
@objc var appIcon: NSImage? = nil
}
/** Our other NSObject subclass adopting the SWDAContentProtocol, represents an Application and all of its associated URL Schemes and UTIs. If an application declares no UTIs, it looks for File Extensions and displays the UTI preferred to represent that extension. */
/** Our other NSObject subclass adopting the SWDAContentProtocol, represents an Application and all of its associated URI Schemes and UTIs. If an application declares no UTIs, it looks for File Extensions and displays the UTI preferred to represent that extension. */
class SWDAApplicationItem: NSObject, SWDAContentProtocol {
@objc lazy var contentHandlers: [SWDATreeRow] = {
var allHandlers: [SWDATreeRow] = []
if let bundle = self.appBundleInfo {
let handledContent = bundle.handledContent
if (bundle.handlesURLs == true) {
let URLs = SWDATreeRow("URL Schemes")
let roles = ["Viewer"]
for role in roles {
let row = SWDATreeRow(role)
for handler in ((handledContent["URLs"]![role]!)!) {
let content = handler.content as! SWDAContentItem
let rowName = content.contentName
let child = SWDATreeRow(rowName, content: handler)
row.addChild(child)
}
if (row.children.count > 0) { URLs.addChildren(of: row) }
}
allHandlers.append(URLs)
}
if (bundle.handlesUTIs == true) {
let UTIs = SWDATreeRow("Uniform Type Identifiers")
let roles = ["Viewer", "Editor", "Shell"]
for role in roles {
let row = SWDATreeRow(role)
for handler in ((handledContent["UTIs"]![role]!)!) {
let content = handler.content as! SWDAContentItem
let rowName = content.contentName
let child = SWDATreeRow(rowName, content: handler)
row.addChild(child)
}
if (row.children.count > 0) { UTIs.addChild(row) }
}
allHandlers.append(UTIs)
}
}
return allHandlers
}()
@objc var contentDescription: String = ""
@objc var contentName: String = ""
var contentType: SWDAContentType
@objc var appBundleInfo: SWDAApplicationInfo?
/** Determine a hashValue from the Bundle ID to prevent duplicate Applications, since in practice Launch Services will not allow us to choose a specific version of an Application but rather choose the best according to its own set of criteria outlined in the Launch Services Programming Guide. */
override var hashValue: Int { return (self.appBundleInfo?.appBundleID)?.hashValue ?? -1 }
init? (_ app: String) {
self.contentType = .Application
if let appInfo = SWDAApplicationInfo(app) {
self.appBundleInfo = appInfo
guard (appInfo.handlesURLs == true || appInfo.handlesUTIs == true) else { return nil }
}
else { return nil }
super.init()
self.setValue(self.appBundleInfo?.displayName, forKey: "contentName")
self.setValue(self.getDescription(), forKey: "contentDescription")
}
@objc lazy var displayName: String = {
return self.value(forKey:"contentName") as! String
}()
@objc lazy var appPath: String = {
if let path = self.appBundleInfo?.appPath {
return path
}
else { return "" }
}()
@objc lazy var appIcon: NSImage? = {
if let icon = self.appBundleInfo?.appIcon {
return icon
}
else { return nil }
}()
@objc var getExtensions: String = ""
func getDescription () -> String {
if let version = self.appBundleInfo?.appVersion {
return "Version: \(version)"
}
else { return "" }
}
@objc lazy var contentHandlers: [SWDATreeRow] = {
var allHandlers: [SWDATreeRow] = []
if let bundle = self.appBundleInfo {
let handledContent = bundle.handledContent
if (bundle.handlesURIs == true) {
let URIs = SWDATreeRow("URI Schemes")
let roles = ["Viewer"]
for role in roles {
let row = SWDATreeRow(role)
for handler in ((handledContent["URIs"]![role]!)!) {
let content = handler.content as! SWDAContentItem
let rowName = content.contentName
let child = SWDATreeRow(rowName, content: handler)
row.addChild(child)
}
if (row.children.count > 0) { URIs.addChildren(of: row) }
}
allHandlers.append(URIs)
}
if (bundle.handlesUTIs == true) {
let UTIs = SWDATreeRow("Uniform Type Identifiers")
let roles = ["Viewer", "Editor", "Shell"]
for role in roles {
let row = SWDATreeRow(role)
for handler in ((handledContent["UTIs"]![role]!)!) {
let content = handler.content as! SWDAContentItem
let rowName = content.contentName
let child = SWDATreeRow(rowName, content: handler)
row.addChild(child)
}
if (row.children.count > 0) { UTIs.addChild(row) }
}
allHandlers.append(UTIs)
}
}
return allHandlers
}()
@objc var contentDescription: String = ""
@objc var contentName: String = ""
var contentType: SWDAContentType
@objc var appBundleInfo: SWDAApplicationInfo?
/** Determine a hashValue from the Bundle ID to prevent duplicate Applications, since in practice Launch Services will not allow us to choose a specific version of an Application but rather choose the best according to its own set of criteria outlined in the Launch Services Programming Guide. */
override var hash: Int { return (self.appBundleInfo?.appBundleID)?.hash ?? -1 }
init? (_ app: String) {
self.contentType = .Application
if let appInfo = SWDAApplicationInfo(app) {
self.appBundleInfo = appInfo
guard (appInfo.handlesURIs == true || appInfo.handlesUTIs == true) else { return nil }
}
else { return nil }
super.init()
self.setValue(self.appBundleInfo?.displayName, forKey: "contentName")
self.setValue(self.getDescription(), forKey: "contentDescription")
}
@objc lazy var displayName: String = {
return self.value(forKey:"contentName") as! String
}()
@objc lazy var appPath: String = {
if let path = self.appBundleInfo?.appPath {
return path
}
else { return "" }
}()
@objc lazy var appIcon: NSImage? = {
if let icon = self.appBundleInfo?.appIcon {
return icon
}
else { return nil }
}()
@objc var getExtensions: String = ""
func getDescription () -> String {
if let version = self.appBundleInfo?.appVersion {
return "Version: \(version)"
}
else { return "" }
}
}
@@ -1,128 +1,122 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import Foundation
/** Represent LSRolesMask in a more convenient format for us. */
enum SourceListRoleTypes:String {
case Editor, Viewer, Shell, All
case Editor, Viewer, Shell, All
}
/** Represent possible kinds of NSObject conforming to SWDAContentProtocol */
internal enum SWDAContentType: String {
case UTI, URL, Application
case UTI, URI, Application
}
/** Our main model class, tasked with getting and populating the content arrays associated with each tab. */
class SWDAHandlersModel: NSObject {
@objc static var allUTIs: [SWDAContentItem]?
@objc static var allSchemes: [SWDAContentItem]?
@objc static var internetSchemes: [SWDAContentItem]?
@objc static var allApps: [SWDAApplicationItem]?
/**
This function is indirectly called by the contentArray variable in each SWDATabTemplate instance, it's responsible for asynchronously populating each model array with the appropriate content and sending messages for the ProgressAlert to update itself.
- Parameter view: The SWDATabView instance that requested the content. We pass this so we can then modify it's backing-store in a KVO-compliant manner via setValue(_:forKey:)
- Parameter type: String representation of what that SWDATabTemplate is asking for.
- Parameter competionHandler: Code block to execute upon completion. In practice, what this does is invoke setValue(_:forKey:) with the resulting array as the value.
*/
static internal func populateContentArray(in view: SWDATabTemplate, with type: String, completionHandler: @escaping () -> Void) {
let progressAlert = ProgressAlert()
guard (ControllersRef.sharedInstance.thePrefPane?.mainCustomView.window != nil) else { return }
let window = (ControllersRef.sharedInstance.thePrefPane?.mainCustomView.window)!
progressAlert.beginSheetModal(for: window, completionHandler: nil)
var codeBlock: (_ : Int) -> Void = { index in }
var sourceItems: [String] = []
var sourceDescriptions: [String:String] = [:]
var outputItems: Any // By using the SynchronizedArray class, we can concurrently create the larger Content Arrays across multiple threads, in a safe manner.
if (type == "Applications") {
outputItems = SynchronizedArray<SWDAApplicationItem>()
}
else {
outputItems = SynchronizedArray<SWDAContentItem>()
}
switch type {
case "Internet":
sourceItems = ["http","mailto","news","rss","ftp","im"]
sourceDescriptions = ["http":"Web Browser","mailto":"E-Mail","news":"News","rss":"RSS","ftp":"FTP","im":"Instant Messaging"] // In practice, these are just shortcuts for the most commonly-used URL Schemes.
codeBlock = {
index in
let i = Int(index)
(outputItems as! SynchronizedArray<SWDAContentItem>).append(SWDAContentItem(type:SWDAContentType(rawValue: "URL")!, sourceItems[i], sourceDescriptions[sourceItems[i]]))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "URLs":
if let schemesHandlers = LSWrappers.Schemes.copySchemesAndHandlers() {
sourceItems = Array(schemesHandlers.keys)
}
else { return }
codeBlock = {
index in
let i = Int(index)
if sourceItems[i] != "*" {
(outputItems as! SynchronizedArray<SWDAContentItem>).append(SWDAContentItem(type:SWDAContentType(rawValue: "URL")!, sourceItems[i]))
}
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "UTIs":
sourceItems = Array(LSWrappers.UTType.copyAllUTIs().keys)
codeBlock = {
index in
let i = Int(index)
(outputItems as! SynchronizedArray<SWDAContentItem>).append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, sourceItems[i]))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "Applications":
if let apps = LSWrappers.copyAllApps() {
sourceItems = apps
}
else { return }
codeBlock = {
index in
let i = Int(index)
let app = sourceItems[i]
if let wrappedApp = SWDAApplicationItem(app) {
if ((outputItems as! SynchronizedArray<SWDAApplicationItem>).first() { $0.appBundleInfo?.appBundleID == wrappedApp.appBundleInfo?.appBundleID } == nil) { (outputItems as! SynchronizedArray<SWDAApplicationItem>).append(wrappedApp) }
}
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
default: return
}
progressAlert.maxValue = Double(sourceItems.count)
DispatchQueue.global(qos: .userInteractive).async {
DispatchQueue.concurrentPerform(iterations: sourceItems.count, execute: codeBlock)
DispatchQueue.main.sync { [weak window] in // Using sync for the finalization of the array ensures all the data is in place before reporting completion to the View Controller.
var finalArray: Array<AnyObject>
if (type == "Applications") {
finalArray = (outputItems as! SynchronizedArray<SWDAApplicationItem>).innerArray
}
else {
finalArray = (outputItems as! SynchronizedArray<SWDAContentItem>).innerArray
}
setValue(finalArray, forKey: ControllersRef.TabData.modelArrayKeyPath)
window?.endSheet(progressAlert.window)
completionHandler()
}
}
}
@objc static var allUTIs: [SWDAContentItem]?
@objc static var allSchemes: [SWDAContentItem]?
@objc static var internetSchemes: [SWDAContentItem]?
@objc static var allApps: [SWDAApplicationItem]?
/**
This function is indirectly called by the contentArray variable in each SWDATabTemplate instance, it's responsible for asynchronously populating each model array with the appropriate content and sending messages for the ProgressAlert to update itself.
- Parameter view: The SWDATabView instance that requested the content. We pass this so we can then modify its backing-store in a KVO-compliant manner via setValue(_:forKey:)
- Parameter type: String representation of what that SWDATabTemplate is asking for.
- Parameter competionHandler: Code block to execute upon completion. In practice, what this does is invoke setValue(_:forKey:) with the resulting array as the value.
*/
static internal func populateContentArray(in view: SWDATabTemplate, with type: String, completionHandler: @escaping () -> Void) {
let progressAlert = ProgressAlert()
guard (ControllersRef.sharedInstance.thePrefPane?.mainCustomView.window != nil) else { return }
let window = (ControllersRef.sharedInstance.thePrefPane?.mainCustomView.window)!
progressAlert.beginSheetModal(for: window, completionHandler: nil)
var codeBlock: (_ : Int) -> Void = { index in }
var sourceItems: [String] = []
var sourceDescriptions: [String:String] = [:]
// By using the SynchronizedArray class, we can concurrently c breyt6 te the larger Content Arrays across multiple threads, in a safe manner.
var outputItems: SynchronizedArray<SWDAContentProtocol>
if (type == "Applications") {
outputItems = SynchronizedArray([SWDAApplicationItem]())
} else {
outputItems = SynchronizedArray([SWDAContentItem]())
}
switch type {
case "Internet":
sourceItems = ["http","mailto","news","rss","ftp","im"]
sourceDescriptions = ["http":"Web Browser","mailto":"E-Mail","news":"News","rss":"RSS","ftp":"FTP","im":"Instant Messaging"] // In practice, these are just shortcuts for the most commonly-used URI Schemes.
codeBlock = {
index in
let i = Int(index)
outputItems.append(SWDAContentItem(type:SWDAContentType(rawValue: "URI")!, sourceItems[i], sourceDescriptions[sourceItems[i]]))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "URIs":
if let schemesHandlers = LSWrappers.Schemes.copySchemesAndHandlers() {
sourceItems = Array(schemesHandlers.keys)
}
else { return }
codeBlock = {
index in
let i = Int(index)
if sourceItems[i] != "*" {
outputItems.append(SWDAContentItem(type:SWDAContentType(rawValue: "URI")!, sourceItems[i]))
}
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "UTIs":
sourceItems = Array(LSWrappers.UTType.copyAllUTIs().keys)
codeBlock = {
index in
let i = Int(index)
outputItems.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, sourceItems[i]))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
case "Applications":
if let apps = LSWrappers.copyAllApps() {
sourceItems = apps
}
else { return }
codeBlock = {
index in
let i = Int(index)
let app = sourceItems[i]
if let wrappedApp = SWDAApplicationItem(app) {
if (outputItems .first() { ($0 as! SWDAApplicationItem).appBundleInfo?.appBundleID == wrappedApp.appBundleInfo?.appBundleID } == nil) { outputItems.append(wrappedApp) }
}
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
default: return
}
progressAlert.maxValue = Double(sourceItems.count)
DispatchQueue.global(qos: .userInteractive).async {
DispatchQueue.concurrentPerform(iterations: sourceItems.count, execute: codeBlock)
DispatchQueue.main.sync { [weak window] in // Using sync for the finalization of the array ensures all the data is in place before reporting completion to the View Controller.
let finalArray = outputItems.innerArray
setValue(finalArray, forKey: ControllersRef.TabData.modelArrayKeyPath)
window?.endSheet(progressAlert.window)
completionHandler()
}
}
}
}
@@ -1,273 +1,434 @@
/*
Copyright (c) 2016 Messiah <luoyongmao@gmail.com>
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.
*/
//
// SynchronizedArray.swift
// ZamzamKit
// http://basememara.com/creating-thread-safe-arrays-in-swift/
//
// Created by Basem Emara on 2/27/17.
// Copyright © 2017 Zamzam Inc. All rights reserved.
//
//
import Foundation
/// A thread-safe array.
public class SynchronizedArray<Element> {
fileprivate let queue = DispatchQueue(label: "io.zamzam.ZamzamKit.SynchronizedArray", attributes: .concurrent)
fileprivate var array = [Element]()
private let queue = DispatchQueue(label: "\(DispatchQueue.labelPrefix).SynchronizedArray", qos: .utility, attributes: .concurrent)
private var array = [Element]()
public init() { }
public convenience init(_ array: [Element]) {
self.init()
self.array = array
}
}
// MARK: - Properties
public extension SynchronizedArray {
/// Extract the inner array.
var innerArray: [Element] { return self.array }
/// The first element of the collection.
var first: Element? {
var result: Element?
queue.sync { result = self.array.first }
return result
}
/// The last element of the collection.
var last: Element? {
var result: Element?
queue.sync { result = self.array.last }
return result
}
/// The number of elements in the array.
var count: Int {
var result = 0
queue.sync { result = self.array.count }
return result
}
/// A Boolean value indicating whether the collection is empty.
var isEmpty: Bool {
var result = false
queue.sync { result = self.array.isEmpty }
return result
}
/// A textual representation of the array and its elements.
var description: String {
var result = ""
queue.sync { result = self.array.description }
return result
}
/// Extract the inner array.
var innerArray: [Element] { return self.array }
/// The first element of the collection.
var first: Element? {
var result: Element?
queue.sync { result = self.array.first }
return result
}
/// The last element of the collection.
var last: Element? {
var result: Element?
queue.sync { result = self.array.last }
return result
}
/// The number of elements in the array.
var count: Int {
var result = 0
queue.sync { result = self.array.count }
return result
}
/// A Boolean value indicating whether the collection is empty.
var isEmpty: Bool {
var result = false
queue.sync { result = self.array.isEmpty }
return result
}
/// A textual representation of the array and its elements.
var description: String {
var result = ""
queue.sync { result = self.array.description }
return result
}
}
// MARK: - Immutable
public extension SynchronizedArray {
/// Returns the first element of the sequence that satisfies the given predicate or nil if no such element is found.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
/// - Returns: The first match or nil if there was no match.
func first(where predicate: (Element) -> Bool) -> Element? {
var result: Element?
queue.sync { result = self.array.first(where: predicate) }
return result
}
/// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
///
/// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
/// - Returns: An array of the elements that includeElement allowed.
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
var result = [Element]()
queue.sync { result = self.array.filter(isIncluded) }
return result
}
/// Returns the first index in which an element of the collection satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil.
func index(where predicate: (Element) -> Bool) -> Int? {
var result: Int?
queue.sync { result = self.array.index(where: predicate) }
return result
}
/// Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
///
/// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false.
/// - Returns: A sorted array of the collections elements.
func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
var result = [Element]()
queue.sync { result = self.array.sorted(by: areInIncreasingOrder) }
return result
}
/// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
///
/// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
/// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
func flatMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
var result = [ElementOfResult]()
queue.sync { result = self.array.compactMap(transform) }
return result
}
/// Calls the given closure on each element in the sequence in the same order as a for-in loop.
///
/// - Parameter body: A closure that takes an element of the sequence as a parameter.
func forEach(_ body: (Element) -> Void) {
queue.sync { self.array.forEach(body) }
}
/// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false.
func contains(where predicate: (Element) -> Bool) -> Bool {
var result = false
queue.sync { result = self.array.contains(where: predicate) }
return result
}
public extension SynchronizedArray {
/// Returns the first element of the sequence that satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
/// - Returns: The first element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate.
func first(where predicate: (Element) -> Bool) -> Element? {
var result: Element?
queue.sync { result = self.array.first(where: predicate) }
return result
}
/// Returns the last element of the sequence that satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
/// - Returns: The last element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate.
func last(where predicate: (Element) -> Bool) -> Element? {
var result: Element?
queue.sync { result = self.array.last(where: predicate) }
return result
}
/// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
///
/// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
/// - Returns: An array of the elements that includeElement allowed.
func filter(_ isIncluded: @escaping (Element) -> Bool) -> SynchronizedArray {
var result: SynchronizedArray?
queue.sync { result = SynchronizedArray(self.array.filter(isIncluded)) }
return result ?? self
}
/// Returns the first index in which an element of the collection satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil.
func firstIndex(where predicate: (Element) -> Bool) -> Int? {
var result: Int?
queue.sync { result = self.array.firstIndex(where: predicate) }
return result
}
/// Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
///
/// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false.
/// - Returns: A sorted array of the collections elements.
func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> SynchronizedArray {
var result: SynchronizedArray?
queue.sync { result = SynchronizedArray(self.array.sorted(by: areInIncreasingOrder)) }
return result ?? self
}
/// Returns an array containing the results of mapping the given closure over the sequences elements.
///
/// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
/// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
func map<ElementOfResult>(_ transform: @escaping (Element) -> ElementOfResult) -> [ElementOfResult] {
var result = [ElementOfResult]()
queue.sync { result = self.array.map(transform) }
return result
}
/// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
///
/// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
/// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
func compactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
var result = [ElementOfResult]()
queue.sync { result = self.array.compactMap(transform) }
return result
}
/// Returns the result of combining the elements of the sequence using the given closure.
///
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value. initialResult is passed to nextPartialResult the first time the closure is executed.
/// - nextPartialResult: A closure that combines an accumulating value and an element of the sequence into a new accumulating value, to be used in the next call of the nextPartialResult closure or returned to the caller.
/// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult.
func reduce<ElementOfResult>(_ initialResult: ElementOfResult, _ nextPartialResult: @escaping (ElementOfResult, Element) -> ElementOfResult) -> ElementOfResult {
var result: ElementOfResult?
queue.sync { result = self.array.reduce(initialResult, nextPartialResult) }
return result ?? initialResult
}
/// Returns the result of combining the elements of the sequence using the given closure.
///
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// - updateAccumulatingResult: A closure that updates the accumulating value with an element of the sequence.
/// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult.
func reduce<ElementOfResult>(into initialResult: ElementOfResult, _ updateAccumulatingResult: @escaping (inout ElementOfResult, Element) -> Void) -> ElementOfResult {
var result: ElementOfResult?
queue.sync { result = self.array.reduce(into: initialResult, updateAccumulatingResult) }
return result ?? initialResult
}
/// Calls the given closure on each element in the sequence in the same order as a for-in loop.
///
/// - Parameter body: A closure that takes an element of the sequence as a parameter.
func forEach(_ body: (Element) -> Void) {
queue.sync { self.array.forEach(body) }
}
/// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false.
func contains(where predicate: (Element) -> Bool) -> Bool {
var result = false
queue.sync { result = self.array.contains(where: predicate) }
return result
}
/// Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate.
///
/// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element satisfies a condition.
/// - Returns: true if the sequence contains only elements that satisfy predicate; otherwise, false.
func allSatisfy(_ predicate: (Element) -> Bool) -> Bool {
var result = false
queue.sync { result = self.array.allSatisfy(predicate) }
return result
}
}
// MARK: - Mutable
public extension SynchronizedArray {
/// Adds a new element at the end of the array.
///
/// - Parameter element: The element to append to the array.
func append( _ element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
/// Adds a new element at the end of the array.
///
/// - Parameter element: The element to append to the array.
func append( _ elements: [Element]) {
queue.async(flags: .barrier) {
self.array += elements
}
}
/// Inserts a new element at the specified position.
///
/// - Parameters:
/// - element: The new element to insert into the array.
/// - index: The position at which to insert the new element.
func insert( _ element: Element, at index: Int) {
queue.async(flags: .barrier) {
self.array.insert(element, at: index)
}
}
/// Removes and returns the element at the specified position.
///
/// - Parameters:
/// - index: The position of the element to remove.
/// - completion: The handler with the removed element.
func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
let element = self.array.remove(at: index)
DispatchQueue.main.async {
completion?(element)
}
}
}
/// Removes and returns the element at the specified position.
///
/// - Parameters:
/// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
/// - completion: The handler with the removed element.
func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
guard let index = self.array.index(where: predicate) else { return }
let element = self.array.remove(at: index)
DispatchQueue.main.async {
completion?(element)
}
}
}
/// Removes all elements from the array.
///
/// - Parameter completion: The handler with the removed elements.
func removeAll(completion: (([Element]) -> Void)? = nil) {
queue.async(flags: .barrier) {
let elements = self.array
self.array.removeAll()
DispatchQueue.main.async {
completion?(elements)
}
}
}
/// Adds a new element at the end of the array.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - element: The element to append to the array.
/// - completion: The block to execute when completed.
func append(_ element: Element, completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
self.array.append(element)
DispatchQueue.main.async { completion?() }
}
}
/// Adds new elements at the end of the array.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - element: The elements to append to the array.
/// - completion: The block to execute when completed.
func append(_ elements: [Element], completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
self.array += elements
DispatchQueue.main.async { completion?() }
}
}
/// Inserts a new element at the specified position.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - element: The new element to insert into the array.
/// - index: The position at which to insert the new element.
/// - completion: The block to execute when completed.
func insert(_ element: Element, at index: Int, completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
self.array.insert(element, at: index)
DispatchQueue.main.async { completion?() }
}
}
/// Removes and returns the first element of the collection.
///
/// The collection must not be empty.
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameter completion: The handler with the removed element.
func removeFirst(completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
let element = self.array.removeFirst()
DispatchQueue.main.async { completion?(element) }
}
}
/// Removes the specified number of elements from the beginning of the collection.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - k: The number of elements to remove from the collection.
/// - completion: The block to execute when remove completed.
func removeFirst(_ k: Int, completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
defer { DispatchQueue.main.async { completion?() } }
guard 0...self.array.count ~= k else { return }
self.array.removeFirst(k)
}
}
/// Removes and returns the last element of the collection.
///
/// The collection must not be empty.
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameter completion: The handler with the removed element.
func removeLast(completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
let element = self.array.removeLast()
DispatchQueue.main.async { completion?(element) }
}
}
/// Removes the specified number of elements from the end of the collection.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - k: The number of elements to remove from the collection.
/// - completion: The block to execute when remove completed.
func removeLast(_ k: Int, completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
defer { DispatchQueue.main.async { completion?() } }
guard 0...self.array.count ~= k else { return }
self.array.removeLast(k)
}
}
/// Removes and returns the element at the specified position.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - index: The position of the element to remove.
/// - completion: The handler with the removed element.
func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
queue.async(flags: .barrier) {
let element = self.array.remove(at: index)
DispatchQueue.main.async { completion?(element) }
}
}
/// Removes and returns the elements that meet the criteria.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
/// - completion: The handler with the removed elements.
func remove(where predicate: @escaping (Element) -> Bool, completion: (([Element]) -> Void)? = nil) {
queue.async(flags: .barrier) {
var elements = [Element]()
while let index = self.array.firstIndex(where: predicate) {
elements.append(self.array.remove(at: index))
}
DispatchQueue.main.async { completion?(elements) }
}
}
/// Removes all elements from the array.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameter completion: The handler with the removed elements.
func removeAll(completion: (([Element]) -> Void)? = nil) {
queue.async(flags: .barrier) {
let elements = self.array
self.array.removeAll()
DispatchQueue.main.async { completion?(elements) }
}
}
}
public extension SynchronizedArray {
/// Accesses the element at the specified position if it exists.
/// Accesses the element at the specified position if it exists.
///
/// - Parameter index: The position of the element to access.
/// - Returns: optional element if it exists.
subscript(index: Int) -> Element? {
get {
var result: Element?
queue.sync {
guard self.array.startIndex..<self.array.endIndex ~= index else { return }
result = self.array[index]
}
return result
}
set {
guard let newValue = newValue else { return }
queue.async(flags: .barrier) {
self.array[index] = newValue
}
}
}
subscript(index: Int) -> Element? {
get {
var result: Element?
queue.sync { result = self.array[safe: index] }
return result
}
set {
guard let newValue = newValue else { return }
queue.async(flags: .barrier) {
self.array[index] = newValue
}
}
}
}
// MARK: - Equatable
public extension SynchronizedArray where Element: Equatable {
/// Returns a Boolean value indicating whether the sequence contains the given element.
///
/// - Parameter element: The element to find in the sequence.
/// - Returns: true if the element was found in the sequence; otherwise, false.
func contains(_ element: Element) -> Bool {
var result = false
queue.sync { result = self.array.contains(element) }
return result
}
public extension SynchronizedArray where Element: Equatable {
/// Returns a Boolean value indicating whether the sequence contains the given element.
///
/// - Parameter element: The element to find in the sequence.
/// - Returns: true if the element was found in the sequence; otherwise, false.
func contains(_ element: Element) -> Bool {
var result = false
queue.sync { result = self.array.contains(element) }
return result
}
/// Removes the specified element.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameter element: An element to search for in the collection.
func remove(_ element: Element, completion: (() -> Void)? = nil) {
queue.async(flags: .barrier) {
self.array.remove(element)
DispatchQueue.main.async { completion?() }
}
}
/// Removes the specified element.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - left: The collection to remove from.
/// - right: An element to search for in the collection.
static func -= (left: inout SynchronizedArray, right: Element) {
left.remove(right)
}
}
// MARK: - Infix operators
public extension SynchronizedArray {
/// Adds a new element at the end of the array.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - left: The collection to append to.
/// - right: The element to append to the array.
static func += (left: inout SynchronizedArray, right: Element) {
left.append(right)
}
/// Adds new elements at the end of the array.
///
/// The task is performed asynchronously due to thread-locking management.
///
/// - Parameters:
/// - left: The collection to append to.
/// - right: The elements to append to the array.
static func += (left: inout SynchronizedArray, right: [Element]) {
left.append(right)
}
}
static func +=(left: inout SynchronizedArray, right: Element) {
left.append(right)
}
static func +=(left: inout SynchronizedArray, right: [Element]) {
left.append(right)
}
private extension SynchronizedArray {
//swiftlint:disable file_length
}
@@ -1,155 +1,155 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/** Utilitary Singleton containing references to the proper instances of View controllers, tabs, taba data and so on. */
final internal class ControllersRef: NSObject {
@IBOutlet weak var thePrefPane: SWDAMainPrefPane?
@IBOutlet weak var theTabView: NSTabView!
@IBOutlet weak var theMainView: NSView!
@IBOutlet weak var tabViewController: SWDATabViewController?
/** Store references to the original font of an NSControl to revert effects of fitWidth() when appropriate. */
var originalFonts: [NSControl:NSFont] = [:]
/** Store a reference to the main icon, since we cannot use mainBundle on a preference pane. */
static let appIcon: NSImage = NSWorkspace.shared.icon(forFile: Bundle(identifier: "cl.fail.lordkamina.SwiftDefaultApps")!.bundlePath)
/** Information relevant to the different tabs, such as labels, Keypaths for their content arrays, and such. */
enum TabData {
/** Transform Tab names to abstract cases. */
enum tabNames:String {
case Internet
case URLs
case UTIs
case Applications
init? (value: String?) {
if let value = value {
switch value {
case "Internet": self = .Internet
case "URL Schemes": self = .URLs
case "Uniform Type Identifiers": self = .UTIs
case "Applications": self = .Applications
default: return nil
}
}
else { return nil }
}
}
/** Return a tab's label. */
static var Tab: String? {
let name = tabNames(value:(ControllersRef.sharedInstance.tabViewController?.currentTab?.label))
return name?.rawValue
}
/** Store a reference to the appropriate Keypath to an item's Display Name (the title of the Detail View). */
static var displayNameKeyPath: String {
if let Tab = Tab {
switch Tab {
case "Internet": return "contentDescription"
case "URLs": return "contentName"
case "UTIs": return "contentName"
case "Applications": return "displayName"
default: return "contentName"
}
}
else { return "contentName" }
}
/** Stores a reference to the appropriate Keypath for each tab's Content Array. */
static var modelArrayKeyPath: String {
if let Tab = Tab {
var modelArray = ""
switch Tab {
case "Internet": modelArray = #keyPath(SWDAHandlersModel.internetSchemes)
case "URLs": modelArray = #keyPath(SWDAHandlersModel.allSchemes)
case "UTIs": modelArray = #keyPath(SWDAHandlersModel.allUTIs)
case "Applications": modelArray = #keyPath(SWDAHandlersModel.allApps)
default: modelArray = ""
}
return modelArray
}
else { return "" }
}
/** Return or populate each tab's Content Array. */
static func getContentArray (for view: SWDATabTemplate, initialSetup: Bool = false) {
if let Tab = Tab {
let modelArray = (initialSetup == true) ? #keyPath(SWDAHandlersModel.internetSchemes) : modelArrayKeyPath
if (SWDAHandlersModel.value(forKey:modelArray) != nil) {
view.setValue((SWDAHandlersModel.value(forKey:modelArray) as! Array<AnyObject>), forKey: "contentArrayStore")
}
else {
SWDAHandlersModel.populateContentArray(in: view, with: Tab) { [unowned view] in
view.setValue((SWDAHandlersModel.value(forKey:modelArray) as! Array<AnyObject>), forKey: "contentArrayStore")
}
}
}
else { return }
}
/** Determines whether to show the Item Description in the Detail View. */
static var shouldShowDescription: Bool {
if let Tab = Tab {
switch Tab {
case "Internet": return false
case "URLs": return true
case "UTIs": return true
case "Applications": return true
default: return true
}
}
else { return true }
}
/** Determines whether to show the "Add" button for custom URL Schemes. */
static var shouldShowAddRemove: Bool {
if let Tab = Tab {
switch Tab {
case "URLs": return true
default: return false
}
}
else { return false }
}
/** Determines whether to show an Application's path and a Reveal in Finder button. */
static var shouldShowAppPath: Bool {
if let Tab = Tab {
switch Tab {
case "Applications": return true
default: return false
}
}
else { return false }
}
/** Determines whether to show a list of file extensions associated with a given UTI. */
static var shouldShowFileExts: Bool {
if let Tab = Tab {
switch Tab {
case "UTIs": return true
default: return false
}
}
else { return false }
}
}
/** Initialize the singleton in a thread-safe way. */
static let sharedInstance: ControllersRef = ControllersRef()
/** Make sure there are no stray instances. */
private override init() {
super.init()
}
@IBOutlet weak var thePrefPane: SWDAMainPrefPane?
@IBOutlet weak var theTabView: NSTabView!
@IBOutlet weak var theMainView: NSView!
@IBOutlet weak var tabViewController: SWDATabViewController?
/** Store references to the original font of an NSControl to revert effects of fitWidth() when appropriate. */
var originalFonts: [NSControl:NSFont] = [:]
/** Store a reference to the main icon, since we cannot use mainBundle on a preference pane. */
static let appIcon: NSImage = NSWorkspace.shared.icon(forFile: Bundle(identifier: "cl.fail.lordkamina.SwiftDefaultApps")!.bundlePath)
/** Information relevant to the different tabs, such as labels, Keypaths for their content arrays, and such. */
enum TabData {
/** Transform Tab names to abstract cases. */
enum tabNames:String {
case Internet
case URIs
case UTIs
case Applications
init? (value: String?) {
if let value = value {
switch value {
case "Internet": self = .Internet
case "URI Schemes": self = .URIs
case "Uniform Type Identifiers": self = .UTIs
case "Applications": self = .Applications
default: return nil
}
}
else { return nil }
}
}
/** Return a tab's label. */
static var Tab: String? {
let name = tabNames(value:(ControllersRef.sharedInstance.tabViewController?.currentTab?.label))
return name?.rawValue
}
/** Store a reference to the appropriate Keypath to an item's Display Name (the title of the Detail View). */
static var displayNameKeyPath: String {
if let Tab = Tab {
switch Tab {
case "Internet": return "contentDescription"
case "URIs": return "contentName"
case "UTIs": return "contentName"
case "Applications": return "displayName"
default: return "contentName"
}
}
else { return "contentName" }
}
/** Stores a reference to the appropriate Keypath for each tab's Content Array. */
static var modelArrayKeyPath: String {
if let Tab = Tab {
var modelArray = ""
switch Tab {
case "Internet": modelArray = #keyPath(SWDAHandlersModel.internetSchemes)
case "URIs": modelArray = #keyPath(SWDAHandlersModel.allSchemes)
case "UTIs": modelArray = #keyPath(SWDAHandlersModel.allUTIs)
case "Applications": modelArray = #keyPath(SWDAHandlersModel.allApps)
default: modelArray = ""
}
return modelArray
}
else { return "" }
}
/** Return or populate each tab's Content Array. */
static func getContentArray (for view: SWDATabTemplate, initialSetup: Bool = false) {
if let Tab = Tab {
let modelArray = (initialSetup == true) ? #keyPath(SWDAHandlersModel.internetSchemes) : modelArrayKeyPath
if (SWDAHandlersModel.value(forKey:modelArray) != nil) {
view.setValue((SWDAHandlersModel.value(forKey:modelArray) as! Array<AnyObject>), forKey: "contentArrayStore")
}
else {
SWDAHandlersModel.populateContentArray(in: view, with: Tab) { [unowned view] in
view.setValue((SWDAHandlersModel.value(forKey:modelArray) as! Array<AnyObject>), forKey: "contentArrayStore")
}
}
}
else { return }
}
/** Determines whether to show the Item Description in the Detail View. */
static var shouldShowDescription: Bool {
if let Tab = Tab {
switch Tab {
case "Internet": return false
case "URIs": return true
case "UTIs": return true
case "Applications": return true
default: return true
}
}
else { return true }
}
/** Determines whether to show the "Add" button for custom URI Schemes. */
static var shouldShowAddRemove: Bool {
if let Tab = Tab {
switch Tab {
case "URIs": return true
default: return false
}
}
else { return false }
}
/** Determines whether to show an Application's path and a Reveal in Finder button. */
static var shouldShowAppPath: Bool {
if let Tab = Tab {
switch Tab {
case "Applications": return true
default: return false
}
}
else { return false }
}
/** Determines whether to show a list of file extensions associated with a given UTI. */
static var shouldShowFileExts: Bool {
if let Tab = Tab {
switch Tab {
case "UTIs": return true
default: return false
}
}
else { return false }
}
}
/** Initialize the singleton in a thread-safe way. */
static let sharedInstance: ControllersRef = ControllersRef()
/** Make sure there are no stray instances. */
private override init() {
super.init()
}
}
@@ -18,99 +18,99 @@
import Cocoa
@IBDesignable class DRYView: NSView {
private let dRYViewKVOContext = UnsafeMutableRawPointer(bitPattern: 1)
@IBOutlet weak var descriptionControl: NSTextField?
@IBOutlet weak var titleControl: NSTextField?
@IBInspectable var nibName: String?
{
didSet
{
guard let theNibName = self.nibName
else { return }
var objects: NSArray? = NSArray()
self.bundle?.loadNibNamed(NSNib.Name(rawValue: theNibName), owner: self, topLevelObjects: &objects)
if let theObjects = objects
{
for view in theObjects
{
if let theView = view as? NSView
{
self.addSubview(theView)
theView.translatesAutoresizingMaskIntoConstraints = false
self.selfViewConstraintsToBeFollowedByView(theView)
break
}
}
}
}
}
lazy var bundle: Bundle? =
{
guard let theNibName = self.nibName
else {return nil}
var objects: NSArray? = NSArray()
var isLoaded = Bundle.main.loadNibNamed(NSNib.Name(rawValue: theNibName), owner: self, topLevelObjects: &objects)
if isLoaded
{
return Bundle.main
}
else
{
return Bundle(for: self.classForCoder)
}
}()
func selfViewConstraintsToBeFollowedByView(_ view: NSView)
{
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width , relatedBy: .equal, toItem: view, attribute: .width , multiplier: 1, constant: 0)
let centerXConstraint = NSLayoutConstraint(item: self, attribute: .centerX , relatedBy: .equal, toItem: view, attribute: .centerX , multiplier: 1, constant: 0)
centerXConstraint.identifier = "DRYView Auto CenterX"
let centerYConstraint = NSLayoutConstraint(item: self, attribute: .centerY , relatedBy: .equal, toItem: view, attribute: .centerY , multiplier: 1, constant: 0)
centerYConstraint.identifier = "DRYView Auto CenterY"
widthConstraint.identifier = "DRYView Auto Width"
let heightConstraint = NSLayoutConstraint(item: self, attribute: .height , relatedBy: .equal, toItem: view, attribute: .height , multiplier: 1, constant: 0)
heightConstraint.identifier = "DRYView Auto Height"
self.addConstraints([centerXConstraint, centerYConstraint, widthConstraint, heightConstraint])
}
@objc var inspectedObject: NSObject? {
didSet {
self.willChangeValue(forKey: "inspectedObject")
self.didChangeValue(forKey: "inspectedObject")
self.titleControl?.fitWidth()
self.descriptionControl?.fitWidth()
}
}
@IBOutlet var inspectedObjectControllerReference: NSObjectController? {
didSet {
self.inspectedObject = self.inspectedObjectControllerReference?.content as? NSObject
addObserver(self, forKeyPath: #keyPath(inspectedObjectControllerReference.content), options: .new, context: dRYViewKVOContext)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (dRYViewKVOContext == context) else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if (dRYViewKVOContext == context) {
self.inspectedObject = self.inspectedObjectControllerReference?.content as? NSObject
}
}
deinit
{
if let _ = inspectedObject {
removeObserver(self, forKeyPath: #keyPath(inspectedObjectControllerReference.content), context: dRYViewKVOContext)
}
}
private let dRYViewKVOContext = UnsafeMutableRawPointer(bitPattern: 1)
@IBOutlet weak var descriptionControl: NSTextField?
@IBOutlet weak var titleControl: NSTextField?
@IBInspectable var nibName: String?
{
didSet
{
guard let theNibName = self.nibName
else { return }
var objects: NSArray? = NSArray()
self.bundle?.loadNibNamed(theNibName, owner: self, topLevelObjects: &objects)
if let theObjects = objects
{
for view in theObjects
{
if let theView = view as? NSView
{
self.addSubview(theView)
theView.translatesAutoresizingMaskIntoConstraints = false
self.selfViewConstraintsToBeFollowedByView(theView)
break
}
}
}
}
}
lazy var bundle: Bundle? =
{
guard let theNibName = self.nibName
else {return nil}
var objects: NSArray? = NSArray()
var isLoaded = Bundle.main.loadNibNamed(theNibName, owner: self, topLevelObjects: &objects)
if isLoaded
{
return Bundle.main
}
else
{
return Bundle(for: self.classForCoder)
}
}()
func selfViewConstraintsToBeFollowedByView(_ view: NSView)
{
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width , relatedBy: .equal, toItem: view, attribute: .width , multiplier: 1, constant: 0)
let centerXConstraint = NSLayoutConstraint(item: self, attribute: .centerX , relatedBy: .equal, toItem: view, attribute: .centerX , multiplier: 1, constant: 0)
centerXConstraint.identifier = "DRYView Auto CenterX"
let centerYConstraint = NSLayoutConstraint(item: self, attribute: .centerY , relatedBy: .equal, toItem: view, attribute: .centerY , multiplier: 1, constant: 0)
centerYConstraint.identifier = "DRYView Auto CenterY"
widthConstraint.identifier = "DRYView Auto Width"
let heightConstraint = NSLayoutConstraint(item: self, attribute: .height , relatedBy: .equal, toItem: view, attribute: .height , multiplier: 1, constant: 0)
heightConstraint.identifier = "DRYView Auto Height"
self.addConstraints([centerXConstraint, centerYConstraint, widthConstraint, heightConstraint])
}
@objc var inspectedObject: NSObject? {
didSet {
self.willChangeValue(forKey: "inspectedObject")
self.didChangeValue(forKey: "inspectedObject")
self.titleControl?.fitWidth()
self.descriptionControl?.fitWidth()
}
}
@IBOutlet var inspectedObjectControllerReference: NSObjectController? {
didSet {
self.inspectedObject = self.inspectedObjectControllerReference?.content as? NSObject
addObserver(self, forKeyPath: #keyPath(inspectedObjectControllerReference.content), options: .new, context: dRYViewKVOContext)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (dRYViewKVOContext == context) else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if (dRYViewKVOContext == context) {
self.inspectedObject = self.inspectedObjectControllerReference?.content as? NSObject
}
}
deinit
{
if let _ = inspectedObject {
removeObserver(self, forKeyPath: #keyPath(inspectedObjectControllerReference.content), context: dRYViewKVOContext)
}
}
}
@@ -1,46 +1,46 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/** Create a progressbar and display it on a modal sheet while the Content Array is populated asynchronously. */
class ProgressAlert: NSAlert {
var progressBar = NSProgressIndicator()
override init() {
progressBar.isIndeterminate = false
progressBar.style = .bar
super.init()
self.messageText = ""
self.informativeText = "Loading..."
self.icon = ControllersRef.appIcon
self.accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
self.accessoryView?.addSubview(progressBar)
self.layout()
self.accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
self.addButton(withTitle: "")
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
}
func increment(by value:Double) {
progressBar.increment(by: value)
}
var maxValue: Double {
get {
return progressBar.maxValue
}
set {
progressBar.maxValue = newValue
}
}
var progressBar = NSProgressIndicator()
override init() {
progressBar.isIndeterminate = false
progressBar.style = .bar
super.init()
self.messageText = ""
self.informativeText = "Loading..."
self.icon = ControllersRef.appIcon
self.accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
self.accessoryView?.addSubview(progressBar)
self.layout()
self.accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
self.addButton(withTitle: "")
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
}
func increment(by value:Double) {
progressBar.increment(by: value)
}
var maxValue: Double {
get {
return progressBar.maxValue
}
set {
progressBar.maxValue = newValue
}
}
}
@@ -1,240 +1,220 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
/** Represent an instance of a kind of Content (UTI/URL) and a valid associated application in a given role. */
class SWDAContentHandler: NSObject {
var content: NSObjectProtocol & SWDAContentProtocol
var appName: String = "None"
var application: SWDAApplicationInfo? {
if let info = SWDAApplicationInfo(self.appName) {
return info
}
else { return nil }
}
var roleMask: SourceListRoleTypes?
init(_ content: NSObjectProtocol & SWDAContentProtocol, appName: String, role: SourceListRoleTypes?) {
self.content = content
self.appName = appName
if let role = role {
self.roleMask = role
}
super.init()
}
var content: NSObjectProtocol & SWDAContentProtocol
var appName: String = "None"
var application: SWDAApplicationInfo? {
if let info = SWDAApplicationInfo(self.appName) {
return info
}
else { return nil }
}
var roleMask: SourceListRoleTypes?
init(_ content: NSObjectProtocol & SWDAContentProtocol, appName: String, role: SourceListRoleTypes?) {
self.content = content
self.appName = appName
if let role = role {
self.roleMask = role
}
super.init()
}
}
/** Our NSObject sub-class that represents a ContentHandler, foundation of the Detail Outline view and as such responsible for most of the heavy lifting */
internal class SWDATreeRow:NSObject {
/** Used chiefly for cosmetic purposes in presenting and styling the rows. */
enum levels {
case header, role, handler
}
/** Required functionality for our NSTreeView. */
weak var parentNode: SWDATreeRow?
@objc var children: [SWDATreeRow] = []
@objc var count: Int { return children.count }
@objc var isLeaf: Bool { return children.count < 1}
/** Dirty trick to avoid subclassing NSOutlineView. */
@objc var shouldFauxIndent: Bool {
return self.rowLevel == .handler
}
/** Actually determine what kind of row we are. */
var rowLevel: levels {
if (self.parentNode == nil) { return .header }
else {
if (self.children.count < 1) { return .handler }
else { return .role }
}
}
/** Reference to the System Font; not strictly needed but in case it changes in the future, this way we only actually need to modify it in one place. */
var baseFont: NSFont = NSFont.systemFont(ofSize:0)
/** Bindings-compatible determination of the font to use. */
@objc var rowFont: NSFont? {
switch self.rowLevel {
case .header: return self.baseFont.smallCaps()
case .role: return self.baseFont.bold()
default: return self.baseFont
}
}
/** Show header rows in a different color. */
@objc var textColor: NSColor? {
switch self.rowLevel {
case .header: return NSColor.headerColor
default: return NSColor.controlTextColor
}
}
/** What the Tree Row will actually display as a label. */
@objc var rowTitle: String
/** Content and associated application. Optional to account for dummy rows like Headers. */
var rowContent: SWDAContentHandler?
var roleMask: SourceListRoleTypes?
@objc lazy var appIcon: NSImage? = { return self.rowContent?.application?.appIcon }()
/** Binding-compatible determination used exclusively for SWDATreeRows in the Applications tab. Returns true if the currently selected application is the default handler for the content represented by this row. It's only enabled when its state is off, since in practice it's not actually possible to remove handlers from LaunchServices. Rather, the service takes care of its own clean-up if it detects an UTI or URL Scheme does not have any valid handlers. */
@objc var isHandlingContent: Bool {
get {
guard (ControllersRef.sharedInstance.tabViewController!.currentTab?.label == "Applications") else { return false }
guard (self.rowContent != nil) else { return false }
let contentType = self.rowContent?.content.contentType
guard (contentType != .Application) else { return false }
let defaultHandler = (contentType == .URL) ? LSWrappers.Schemes.copyDefaultHandler(self.rowTitle, asPath: false) : LSWrappers.UTType.copyDefaultHandler(self.rowTitle, inRoles: LSRolesMask(from:self.roleMask!), asPath: false)
return self.rowContent?.application?.appBundleID?.lowercased() == defaultHandler?.lowercased()
}
set {
guard (ControllersRef.sharedInstance.tabViewController!.currentTab?.label == "Applications") else { return }
if let content = (self.rowContent?.content as? SWDAContentItem) {
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 \(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()
}
}
}
}
}
}
/** Binding-compatible determination used in every tab except for Applications. Returns true if the application represented by the current row is the default handler for the selected content type. Allows "Other" to specify apps that aren't detected as valid handlers for whatever reason. Note that setting something to be handled by "Other" will not _actually_ work unless the application's Info.plist adequately declares association with that content. */
@objc var isDefaultHandler: Bool {
get {
if let content = (self.rowContent?.content as? SWDAContentItem) {
var handler: String?
switch content.contentType {
case .Application: return false
case .UTI: handler = LSWrappers.UTType.copyDefaultHandler(content.contentName, inRoles:LSRolesMask(from:self.roleMask!), asPath: false)
case .URL: handler = LSWrappers.Schemes.copyDefaultHandler(content.contentName, asPath: false)
}
guard (handler != nil) else { return false }
return (handler?.lowercased() == rowContent?.application?.appBundleID?.lowercased())
}
else { return false }
}
set {
if let content = (self.rowContent?.content as? SWDAContentItem) {
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()
}
}
}
else if (self.rowTitle == "Other...") {
let openpanel = NSOpenPanel()
openpanel.treatsFilePackagesAsDirectories = false
openpanel.allowsMultipleSelection = false
openpanel.canChooseDirectories = false
openpanel.resolvesAliases = true
openpanel.canChooseFiles = true
openpanel.allowedFileTypes = ["app"]
openpanel.allowsOtherFileTypes = true
openpanel.title = "Choose a default application for: \(content.displayName)"
openpanel.prompt = "Add"
openpanel.runModal()
if !openpanel.urls.isEmpty {
let handler = SWDAContentHandler(content, appName:openpanel.urls[0].path, role:self.roleMask)
if let tempName = handler.application?.displayName {
let displayName = (tempName.lowercased().range(of:".app") != nil) ? tempName : "\(tempName).app"
let row = SWDATreeRow(displayName, content: handler)
self.parentNode?.addChild(row)
row.isDefaultHandler = true
}
else {
let displayName = FileManager.default.displayName(atPath: openpanel.urls[0].path)
let row = SWDATreeRow(displayName, content: handler)
self.parentNode?.addChild(row)
row.isDefaultHandler = true
}
self.parentNode?.children.sort(){($0.rowTitle < $1.rowTitle) && ($0.rowTitle != "Other..." && $0.rowTitle != "Do Nothing")}
self.parentNode?.willChangeValue(forKey: "children")
self.parentNode?.didChangeValue(forKey: "children")
}
}
}
}
}
/**
Add a Child Row
- Parameter child: An instance of SWDATreeRow to be added as a child.
*/
func addChild (_ child: SWDATreeRow) {
guard (self.children.index(of: child) == nil) else { return }
child.parentNode = self
self.children.append(child)
}
/**
Add all the children of a given SWDATreeRow as children of this instance.
- Parameter row: An instance of SWDATreeRow to take children from.
- Note: This is only used to hide the "Viewer" role from URL Schemes, since handler roles are not currently implemented for URL Handlers.
*/
func addChildren (of row: SWDATreeRow) {
for child in row.children {
guard (self.children.index(of: child) == nil) else { return }
child.parentNode = self
self.children.append(child)
}
}
init(_ name: String, content: SWDAContentHandler? = nil) {
self.rowTitle = name
if let content = content {
self.rowContent = content
self.roleMask = content.roleMask
}
super.init()
}
/** Used chiefly for cosmetic purposes in presenting and styling the rows. */
enum levels {
case header, role, handler
}
/** Required functionality for our NSTreeView. */
weak var parentNode: SWDATreeRow?
@objc var children: [SWDATreeRow] = []
@objc var count: Int { return children.count }
@objc var isLeaf: Bool { return children.count < 1}
/** Dirty trick to avoid subclassing NSOutlineView. */
@objc var shouldFauxIndent: Bool {
return self.rowLevel == .handler
}
/** Actually determine what kind of row we are. */
var rowLevel: levels {
if (self.parentNode == nil) { return .header }
else {
if (self.children.count < 1) { return .handler }
else { return .role }
}
}
/** Reference to the System Font; not strictly needed but in case it changes in the future, this way we only actually need to modify it in one place. */
var baseFont: NSFont = NSFont.systemFont(ofSize:0)
/** Bindings-compatible determination of the font to use. */
@objc var rowFont: NSFont? {
switch self.rowLevel {
case .header: return self.baseFont.smallCaps()
case .role: return self.baseFont.bold()
default: return self.baseFont
}
}
/** Show header rows in a different color. */
@objc var textColor: NSColor? {
switch self.rowLevel {
case .header: return NSColor.headerColor
default: return NSColor.controlTextColor
}
}
/** What the Tree Row will actually display as a label. */
@objc var rowTitle: String
/** Content and associated application. Optional to account for dummy rows like Headers. */
var rowContent: SWDAContentHandler?
var roleMask: SourceListRoleTypes?
@objc lazy var appIcon: NSImage? = { return self.rowContent?.application?.appIcon }()
/** Binding-compatible determination used exclusively for SWDATreeRows in the Applications tab. Returns true if the currently selected application is the default handler for the content represented by this row. It's only enabled when its state is off, since in practice it's not actually possible to remove handlers from LaunchServices. Rather, the service takes care of its own clean-up if it detects an UTI or URL Scheme does not have any valid handlers. */
@objc var isHandlingContent: Bool {
get {
guard (ControllersRef.sharedInstance.tabViewController!.currentTab?.label == "Applications") else { return false }
guard (self.rowContent != nil) else { return false }
let contentType = self.rowContent?.content.contentType
guard (contentType != .Application) else { return false }
let defaultHandler = (contentType == .URI) ? LSWrappers.Schemes.copyDefaultHandler(self.rowTitle, asPath: false) : LSWrappers.UTType.copyDefaultHandler(self.rowTitle, inRoles: LSRolesMask(from:self.roleMask!), asPath: false)
return self.rowContent?.application?.appBundleID?.lowercased() == defaultHandler?.lowercased()
}
set {
guard (ControllersRef.sharedInstance.tabViewController!.currentTab?.label == "Applications") else { return }
if let content = (self.rowContent?.content as? SWDAContentItem) {
let contentName = content.contentName
let type = content.contentType
var status = OSStatus()
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")
}
}
}
}
}
}
/** Binding-compatible determination used in every tab except for Applications. Returns true if the application represented by the current row is the default handler for the selected content type. Allows "Other" to specify apps that aren't detected as valid handlers for whatever reason. Note that setting something to be handled by "Other" will not _actually_ work unless the application's Info.plist adequately declares association with that content. */
@objc var isDefaultHandler: Bool {
get {
if let content = (self.rowContent?.content as? SWDAContentItem) {
var handler: String?
switch content.contentType {
case .Application: return false
case .UTI: handler = LSWrappers.UTType.copyDefaultHandler(content.contentName, inRoles:LSRolesMask(from:self.roleMask!), asPath: false)
case .URI: handler = LSWrappers.Schemes.copyDefaultHandler(content.contentName, asPath: false)
}
guard (handler != nil) else { return false }
return (handler?.lowercased() == rowContent?.application?.appBundleID?.lowercased())
}
else { return false }
}
set {
if let content = (self.rowContent?.content as? SWDAContentItem) {
let contentName = content.contentName
let type = content.contentType
var status = OSStatus()
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
openpanel.allowsMultipleSelection = false
openpanel.canChooseDirectories = false
openpanel.resolvesAliases = true
openpanel.canChooseFiles = true
openpanel.allowedFileTypes = ["app"]
openpanel.allowsOtherFileTypes = true
openpanel.title = "Choose a default application for: \(content.displayName)"
openpanel.prompt = "Add"
openpanel.runModal()
if !openpanel.urls.isEmpty {
let handler = SWDAContentHandler(content, appName:openpanel.urls[0].path, role:self.roleMask)
if let tempName = handler.application?.displayName {
let displayName = (tempName.lowercased().range(of:".app") != nil) ? tempName : "\(tempName).app"
let row = SWDATreeRow(displayName, content: handler)
self.parentNode?.addChild(row)
row.isDefaultHandler = true
}
else {
let displayName = FileManager.default.displayName(atPath: openpanel.urls[0].path)
let row = SWDATreeRow(displayName, content: handler)
self.parentNode?.addChild(row)
row.isDefaultHandler = true
}
self.parentNode?.children.sort(){($0.rowTitle < $1.rowTitle) && ($0.rowTitle != "Other..." && $0.rowTitle != "Do Nothing")}
self.parentNode?.willChangeValue(forKey: "children")
self.parentNode?.didChangeValue(forKey: "children")
}
}
}
}
}
/**
Add a Child Row
- Parameter child: An instance of SWDATreeRow to be added as a child.
*/
func addChild (_ child: SWDATreeRow) {
guard (self.children.firstIndex(of: child) == nil) else { return }
child.parentNode = self
self.children.append(child)
}
/**
Add all the children of a given SWDATreeRow as children of this instance.
- Parameter row: An instance of SWDATreeRow to take children from.
- Note: This is only used to hide the "Viewer" role from URL Schemes, since handler roles are not currently implemented for URL Handlers.
*/
func addChildren (of row: SWDATreeRow) {
for child in row.children {
guard (self.children.firstIndex(of: child) == nil) else { return }
child.parentNode = self
self.children.append(child)
}
}
init(_ name: String, content: SWDAContentHandler? = nil) {
self.rowTitle = name
if let content = content {
self.rowContent = content
self.roleMask = content.roleMask
}
super.init()
}
}
@@ -1,327 +1,317 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <g.litenstein@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return., Gregorio Litenstein.
* ----------------------------------------------------------------------------
*/
import AppKit
extension DRYView {
/** Bridge required to make currentTab really dependent on the selectedTabViewItemIndex */
@objc weak var tabViewController: SWDATabViewController? { return ControllersRef.sharedInstance.tabViewController }
/** Reference to the currently selected TabViewItem */
@objc dynamic var currentTab: String? {
if let selectedTab = ControllersRef.sharedInstance.tabViewController?.currentTab {
return selectedTab.label
}
else { return nil }
}
/** Open Finder and Reveal the currently selected Application. */
@IBAction func revealAppInFinder(_ sender: NSButton) {
NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath:sender.title)])
}
/** Determines whether to show a Description in the Detail View */
@objc var showDescriptionBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:true) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowDescription)
}
/** Determines whether to show a path and Reveal In Finder button */
@objc var showPathBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowAppPath)
}
/** The only tab where an "Add" button is at all meaningful is the URL Schemes. */
@objc var showAddRemoveBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowAddRemove)
}
/** Determines whether to ahow a list of file extensions associated with a given UTI. */
@objc var showFileExtensionsBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowFileExts)
}
@objc class func keyPathsForValuesAffectingShowDescriptionBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowPathBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowAddRemoveBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowFileExtensionsBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingCurrentTab() -> Set<String> {
return Set(["tabViewController.selectedTabViewItemIndex"])
}
/** Bridge required to make currentTab really dependent on the selectedTabViewItemIndex */
@objc weak var tabViewController: SWDATabViewController? { return ControllersRef.sharedInstance.tabViewController }
/** Reference to the currently selected TabViewItem */
@objc dynamic var currentTab: String? {
if let selectedTab = ControllersRef.sharedInstance.tabViewController?.currentTab {
return selectedTab.label
}
else { return nil }
}
/** Open Finder and Reveal the currently selected Application. */
@IBAction func revealAppInFinder(_ sender: NSButton) {
NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath:sender.title)])
}
/** Determines whether to show a Description in the Detail View */
@objc var showDescriptionBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:true) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowDescription)
}
/** Determines whether to show a path and Reveal In Finder button */
@objc var showPathBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowAppPath)
}
/** The only tab where an "Add" button is at all meaningful is the URI Schemes. */
@objc var showAddRemoveBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowAddRemove)
}
/** Determines whether to ahow a list of file extensions associated with a given UTI. */
@objc var showFileExtensionsBool: NSNumber {
guard (self.currentTab != nil) else { return NSNumber(booleanLiteral:false) }
return NSNumber(booleanLiteral:ControllersRef.TabData.shouldShowFileExts)
}
@objc class func keyPathsForValuesAffectingShowDescriptionBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowPathBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowAddRemoveBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingShowFileExtensionsBool() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(inspectedObject)])
}
@objc class func keyPathsForValuesAffectingCurrentTab() -> Set<String> {
return Set(["tabViewController.selectedTabViewItemIndex"])
}
}
/** NSView subclass used to implement automatic sorting of the NSTableView. */
class SWDATableView: NSView {
@IBOutlet weak var arrayController: NSArrayController?
@IBOutlet weak var tableView: NSTableView?
let defaultSortDescriptors = [NSSortDescriptor(key: "displayName", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
override func awakeFromNib() {
if let ac = arrayController {
if let tableView = tableView {
ac.bind(NSBindingName(rawValue: "sortDescriptors"), to:tableView, withKeyPath: "sortDescriptors", options:nil)
tableView.sortDescriptors = defaultSortDescriptors
tableView.selectRowIndexes(NSIndexSet(index: 0) as IndexSet, byExtendingSelection: false)
self.tableView?.becomeFirstResponder()
}
}
}
@IBOutlet weak var arrayController: NSArrayController?
@IBOutlet weak var tableView: NSTableView?
let defaultSortDescriptors = [NSSortDescriptor(key: "displayName", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
override func awakeFromNib() {
if let ac = arrayController {
if let tableView = tableView {
ac.bind(NSBindingName(rawValue: "sortDescriptors"), to:tableView, withKeyPath: "sortDescriptors", options:nil)
tableView.sortDescriptors = defaultSortDescriptors
tableView.selectRowIndexes(NSIndexSet(index: 0) as IndexSet, byExtendingSelection: false)
self.tableView?.becomeFirstResponder()
}
}
}
}
/** By subclassing (instead of extending) DRYView for this, we make sure variables such as the contentArray and other possibly-work-intensive code is only executed on instances of the tab and not also in the detail view.*/
class SWDATabTemplate: DRYView {
@IBOutlet weak var progressAlert: ProgressAlert?
@IBOutlet weak var titleView: NSView?
@IBOutlet weak var customNewScheme: NSTextField?
@IBOutlet var arrayController: NSArrayController!
@IBOutlet var tableView: NSTableView!
/** Let's save selected item for each tab, update selectionIndex with the current value of our selection when our selection would be replaced with an empty or invalid one. */
@objc var tableIndexes = NSIndexSet(index: 0)
{
willSet {
if (newValue.count < 1) { self.selectionIndex = self.tableIndexes.firstIndex }
}
}
/** Add a custom URL Scheme and assign our dummy app as the default handler. In practice this should almost never be necessary but sometimes Launch Services move in mysterious ways. */
@IBAction func addCustomScheme(_ sender: NSButton) {
guard (customNewScheme?.stringValue != nil) && (customNewScheme?.stringValue != "") else { return }
let result = LSWrappers.Schemes.setDefaultHandler(customNewScheme!.stringValue, "cl.fail.lordkamina.ThisAppDoesNothing")
if (result == 0) {
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()
}
return
}
}
/** Identifies which tab the current instance belongs to. */
var tabIndex: Int? = -1
/** This is where we'll store our selection indexes when switching tabs. */
var selectionIndex: Int? = nil
/** Backing Store for the list of items on each tab. */
@objc var contentArrayStore: Array<AnyObject>?
/** Check whether contentArrayStore holds anything; return that if it does or populate it asynchronously if it doesn't. */
@objc var contentArray: Array<AnyObject>? {
guard self.nibName == "SWDAPrefpaneTabTemplate" else { return [] }
if (self.tabIndex == ControllersRef.sharedInstance.tabViewController?.selectedTabViewItemIndex) {
if (self.contentArrayStore == nil) {
ControllersRef.TabData.getContentArray(for: self)
return nil
}
else {
if let content = self.value(forKey:"contentArrayStore") as? Array<AnyObject> {
defer { self.tableView?.becomeFirstResponder() }
return content
}
else { return nil }
}
}
else {
return nil
}
}
@objc class func keyPathsForValuesAffectingContentArray() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(contentArrayStore)])
}
@objc class func keyPathsForValuesAffectingContentArrayStore() -> Set<String> {
return Set([#keyPath(currentTab)])
}
@IBOutlet weak var progressAlert: ProgressAlert?
@IBOutlet weak var titleView: NSView?
@IBOutlet weak var customNewScheme: NSTextField?
@IBOutlet var arrayController: NSArrayController!
@IBOutlet var tableView: NSTableView!
/** Let's save selected item for each tab, update selectionIndex with the current value of our selection when our selection would be replaced with an empty or invalid one. */
@objc var tableIndexes = NSIndexSet(index: 0)
{
willSet {
if (newValue.count < 1) { self.selectionIndex = self.tableIndexes.firstIndex }
}
}
/** Add a custom URI Scheme and assign our dummy app as the default handler. In practice this should almost never be necessary but sometimes Launch Services move in mysterious ways. */
@IBAction func addCustomScheme(_ sender: NSButton) {
guard (customNewScheme?.stringValue != nil) && (customNewScheme?.stringValue != "") else { return }
let result = LSWrappers.Schemes.setDefaultHandler(customNewScheme!.stringValue, "cl.fail.lordkamina.ThisAppDoesNothing")
if (result == 0) {
SWDAHandlersModel.setValue(nil, forKey: "allSchemes")
self.setValue(nil, forKey: "contentArrayStore")
}
try! displayAlert(error: result, arg1: "Do Nothing", arg2: customNewScheme!.stringValue)
return
}
/** Identifies which tab the current instance belongs to. */
var tabIndex: Int? = -1
/** This is where we'll store our selection indexes when switching tabs. */
var selectionIndex: Int? = nil
/** Backing Store for the list of items on each tab. */
@objc var contentArrayStore: Array<AnyObject>?
/** Check whether contentArrayStore holds anything; return that if it does or populate it asynchronously if it doesn't. */
@objc var contentArray: Array<AnyObject>? {
guard self.nibName == "SWDAPrefpaneTabTemplate" else { return [] }
if (self.tabIndex == ControllersRef.sharedInstance.tabViewController?.selectedTabViewItemIndex) {
if (self.contentArrayStore == nil) {
ControllersRef.TabData.getContentArray(for: self)
return nil
}
else {
if let content = self.value(forKey:"contentArrayStore") as? Array<AnyObject> {
defer { self.tableView?.becomeFirstResponder() }
return content
}
else { return nil }
}
}
else {
return nil
}
}
@objc class func keyPathsForValuesAffectingContentArray() -> Set<String> {
return Set([#keyPath(currentTab), #keyPath(contentArrayStore)])
}
@objc class func keyPathsForValuesAffectingContentArrayStore() -> Set<String> {
return Set([#keyPath(currentTab)])
}
}
/** NSTabView subclass, initializes instances of the canned tabs, builds the tabView and handles saving data such as selection indexes between multiple tabs. */
class SWDATabViewController: NSTabViewController {
/** KVO context */
private let tabViewKVOContext = UnsafeMutableRawPointer(bitPattern: 1)
/** Return an instance of the currently selected NSTabViewItem */
var currentTab: NSTabViewItem? {
get {
guard self.isViewLoaded != false else { return nil }
let index = self.selectedTabViewItemIndex
guard index != -1 else { return nil }
let tab = self.tabView.tabViewItem(at: index)
return tab
}
}
/** Preserve selection indexes when switching tabs. */
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (context == tabViewKVOContext) else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if (tabViewKVOContext == context) {
guard (change![.oldKey]! as! Int != -1) else { return }
let oldTab = self.childViewControllers[change![.oldKey]! as! Int].view as! SWDATabTemplate
let newTab = self.childViewControllers[change![.newKey]! as! Int].view as! SWDATabTemplate
if (newTab.selectionIndex != 0) {
newTab.setValue(NSIndexSet(index: newTab.selectionIndex!), forKey: "tableIndexes")
}
else {
newTab.setValue(NSIndexSet(index:0), forKey: "tableIndexes")
}
newTab.tableView?.scrollRowToVisible(newTab.arrayController.selectionIndex)
}
}
/** Initialize the NSTabView, create the tabs and assign their NIB. */
override func viewDidLoad() {
self.tabView.tabViewType = .topTabsBezelBorder
addObserver(self, forKeyPath: #keyPath(selectedTabViewItemIndex), options: [.old, .new], context: tabViewKVOContext)
let tabViewController = self
self.tabView.translatesAutoresizingMaskIntoConstraints = false
let leading = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView, attribute: .leading , relatedBy: .equal, toItem: self.tabView, attribute: .leading , multiplier: 1, constant: -20)
leading.identifier = "TabView Leading"
let trailing = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView, attribute: .trailing , relatedBy: .equal, toItem: self.tabView, attribute: .trailing , multiplier: 1, constant: 20)
trailing.identifier = "TabView Trailng"
let centerX = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView, attribute: .centerX , relatedBy: .equal, toItem: self.tabView, attribute: .centerX , multiplier: 1, constant: 0)
centerX.identifier = "TabView CenterX"
let top = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView, attribute: .top , relatedBy: .equal, toItem: self.tabView, attribute: .top , multiplier: 1, constant: -20)
top.identifier = "TabView Top"
let bottom = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView, attribute: .bottom , relatedBy: .equal, toItem: self.tabView, attribute: .bottom , multiplier: 1, constant: 10)
bottom.identifier = "TabView Bottom"
let tabs = ["Internet", "URL Schemes", "Uniform Type Identifiers", "Applications"]
ControllersRef.sharedInstance.theMainView.addConstraints([centerX, leading, trailing, bottom, top])
for tab in tabs {
let newTabVC = NSViewController.init()
newTabVC.view = SWDATabTemplate.init()
let index = tabs.index(of: tab)
(newTabVC.view as! SWDATabTemplate).tabIndex = (index != nil) ? index! : nil
(newTabVC.view as! SWDATabTemplate).nibName = "SWDAPrefpaneTabTemplate"
tabViewController.addChildViewController(newTabVC)
let newTab = tabViewController.tabViewItem(for: newTabVC)!
newTab.label = tab
}
}
class func keyPathsForValuesAffectingCurrentTab() -> Set<String> {
return Set([#keyPath(selectedTabViewItemIndex)])
}
deinit {
if let _ = tabViewKVOContext {
removeObserver(self, forKeyPath: #keyPath(selectedTabViewItemIndex), context: tabViewKVOContext)
}
}
/** KVO context */
private let tabViewKVOContext = UnsafeMutableRawPointer(bitPattern: 1)
/** Return an instance of the currently selected NSTabViewItem */
var currentTab: NSTabViewItem? {
get {
guard self.isViewLoaded != false else { return nil }
let index = self.selectedTabViewItemIndex
guard index != -1 else { return nil }
let tab = self.tabView.tabViewItem(at: index)
return tab
}
}
/** Preserve selection indexes when switching tabs. */
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (context == tabViewKVOContext) else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if (tabViewKVOContext == context) {
guard (change![.oldKey]! as! Int != -1) else { return }
let oldTab = self.children[change![.oldKey]! as! Int].view as! SWDATabTemplate
let newTab = self.children[change![.newKey]! as! Int].view as! SWDATabTemplate
if (newTab.selectionIndex != 0) {
newTab.setValue(NSIndexSet(index: newTab.selectionIndex!), forKey: "tableIndexes")
}
else {
newTab.setValue(NSIndexSet(index:0), forKey: "tableIndexes")
}
newTab.tableView?.scrollRowToVisible(newTab.arrayController.selectionIndex)
}
}
/** Initialize the NSTabView, create the tabs and assign their NIB. */
override func viewDidLoad() {
self.tabView.tabViewType = .topTabsBezelBorder
addObserver(self, forKeyPath: #keyPath(selectedTabViewItemIndex), options: [.old, .new], context: tabViewKVOContext)
let tabViewController = self
self.tabView.translatesAutoresizingMaskIntoConstraints = false
let leading = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView!, attribute: .leading , relatedBy: .equal, toItem: self.tabView, attribute: .leading , multiplier: 1, constant: -20)
leading.identifier = "TabView Leading"
let trailing = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView!, attribute: .trailing , relatedBy: .equal, toItem: self.tabView, attribute: .trailing , multiplier: 1, constant: 20)
trailing.identifier = "TabView Trailng"
let centerX = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView!, attribute: .centerX , relatedBy: .equal, toItem: self.tabView, attribute: .centerX , multiplier: 1, constant: 0)
centerX.identifier = "TabView CenterX"
let top = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView!, attribute: .top , relatedBy: .equal, toItem: self.tabView, attribute: .top , multiplier: 1, constant: -20)
top.identifier = "TabView Top"
let bottom = NSLayoutConstraint(item: ControllersRef.sharedInstance.theMainView!, attribute: .bottom , relatedBy: .equal, toItem: self.tabView, attribute: .bottom , multiplier: 1, constant: 10)
bottom.identifier = "TabView Bottom"
let tabs = ["Internet", "URI Schemes", "Uniform Type Identifiers", "Applications"]
ControllersRef.sharedInstance.theMainView.addConstraints([centerX, leading, trailing, bottom, top])
for tab in tabs {
let newTabVC = NSViewController.init()
newTabVC.view = SWDATabTemplate.init()
let index = tabs.firstIndex(of: tab)
(newTabVC.view as! SWDATabTemplate).tabIndex = (index != nil) ? index! : nil
(newTabVC.view as! SWDATabTemplate).nibName = "SWDAPrefpaneTabTemplate"
tabViewController.addChild(newTabVC)
let newTab = tabViewController.tabViewItem(for: newTabVC)!
newTab.label = tab
}
}
class func keyPathsForValuesAffectingCurrentTab() -> Set<String> {
return Set([#keyPath(selectedTabViewItemIndex)])
}
deinit {
if let _ = tabViewKVOContext {
removeObserver(self, forKeyPath: #keyPath(selectedTabViewItemIndex), context: tabViewKVOContext)
}
}
}
/**
NSTreeController sub-class that acts mostly as a bridge between the DRYViews and a couple Bindings.
Single outlineView delegate function implemented obeying strictly to a cosmetic issue.
*/
NSTreeController sub-class that acts mostly as a bridge between the DRYViews and a couple Bindings.
Single outlineView delegate function implemented obeying strictly to a cosmetic issue.
*/
class SWDATreeController: NSTreeController, NSOutlineViewDelegate {
required init?(coder: NSCoder) {
super.init(coder: coder)
}
@IBOutlet weak var outlineView: NSOutlineView?
@IBOutlet weak var dryView: DRYView?
/**
Return true if "Applications" is the currently selected tab.
*/
@objc var showPathBool: NSNumber {
if let view = dryView {
return view.value(forKey:"showPathBool") as! NSNumber
}
else { return NSNumber(booleanLiteral: false) }
}
/** Expand tree items by default.
*/
override var content: Any? {
didSet {
self.outlineView?.expandItem(nil, expandChildren: true)
}
}
/**
Determines whether to show the disclosure button for a group row depending on the selected tab and its place in the hierarchy.
*/
func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool {
let row = item as! NSTreeNode
if let object = row.representedObject as? SWDATreeRow {
if object.rowLevel == .role { return false }
else {
if (object.rowLevel == .header) {
if let isItRole = SourceListRoleTypes(rawValue: object.rowTitle) {
return false
}
else { return true }
}
if let children = row.children {
return children.count > 0
}
else { return false }
}
}
else { return false }
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
@IBOutlet weak var outlineView: NSOutlineView?
@IBOutlet weak var dryView: DRYView?
/**
Return true if "Applications" is the currently selected tab.
*/
@objc var showPathBool: NSNumber {
if let view = dryView {
return view.value(forKey:"showPathBool") as! NSNumber
}
else { return NSNumber(booleanLiteral: false) }
}
/** Expand tree items by default.
*/
override var content: Any? {
didSet {
self.outlineView?.expandItem(nil, expandChildren: true)
}
}
/**
Determines whether to show the disclosure button for a group row depending on the selected tab and its place in the hierarchy.
*/
func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool {
let row = item as! NSTreeNode
if let object = row.representedObject as? SWDATreeRow {
if object.rowLevel == .role { return false }
else {
if (object.rowLevel == .header) {
if let isItRole = SourceListRoleTypes(rawValue: object.rowTitle) {
return false
}
else { return true }
}
if let children = row.children {
return children.count > 0
}
else { return false }
}
}
else { return false }
}
}
@IBDesignable
class HyperlinkTextField: NSTextField {
@IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [NSAttributedStringKey:AnyObject] = [
NSAttributedStringKey.foregroundColor: NSColor.blue,
NSAttributedStringKey.backgroundColor: NSColor.clear,
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared.open(URL(string: self.href)!)
}
convenience init (frame: NSRect, url: String, text: String? = nil) {
self.init(frame:frame)
self.drawsBackground = false
self.isEditable = false
self.isBezeled = false
if let text = text {
self.stringValue = text
}
else { self.stringValue = url }
self.href = url
self.awakeFromNib()
}
@IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [NSAttributedString.Key:AnyObject] = [
NSAttributedString.Key.foregroundColor: NSColor.blue,
NSAttributedString.Key.backgroundColor: NSColor.clear,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue as AnyObject
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared.open(URL(string: self.href)!)
}
convenience init (frame: NSRect, url: String, text: String? = nil) {
self.init(frame:frame)
self.drawsBackground = false
self.isEditable = false
self.isBezeled = false
if let text = text {
self.stringValue = text
}
else { self.stringValue = url }
self.href = url
self.awakeFromNib()
}
}
@@ -24,15 +24,14 @@ class SWDAMainPrefPane: NSPreferencePane {
}
/** Add the TabView Controller to the Main View and load content for the default tab. */
override func mainViewDidLoad() {
super.mainViewDidLoad()
self.tabViewController!.view.translatesAutoresizingMaskIntoConstraints = false
ControllersRef.sharedInstance.theMainView.addSubview(self.tabViewController!.tabView)
defer {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(350)) {
ControllersRef.TabData.getContentArray(for: (ControllersRef.sharedInstance.tabViewController?.tabViewItems[0].view as! SWDATabTemplate), initialSetup: true)
}
}
}
super.mainViewDidLoad()
self.tabViewController!.view.translatesAutoresizingMaskIntoConstraints = false
ControllersRef.sharedInstance.theMainView.addSubview(self.tabViewController!.tabView)
}
/** Initialize the content array when the pane is first opened. */
override func didSelect() {
ControllersRef.TabData.getContentArray(for: (ControllersRef.sharedInstance.tabViewController?.tabViewItems[0].view as! SWDATabTemplate), initialSetup: true)
}
@IBAction func showAboutDialog(_ sender: NSButton) {
let mainBundle = Bundle(identifier: "cl.fail.lordkamina.SwiftDefaultApps")
+32 -32
View File
@@ -10,13 +10,13 @@
EB1B57371E00158900A28130 /* getHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1B57351E00152000A28130 /* getHandler.swift */; };
EB310F131E25090600EF24D4 /* getApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB310F121E25090600EF24D4 /* getApps.swift */; };
EB4449551E2E427E001EC6A2 /* setHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB852DAD1E2D701700D9E5D6 /* setHandler.swift */; };
EB7D00751DFF40ED0080202B /* SwiftCLI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* SwiftCLI.framework */; };
EB7D007B1DFF8DF20080202B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7D00781DFF8DF20080202B /* Cocoa.framework */; };
EB7D007C1DFF8DF20080202B /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7D00791DFF8DF20080202B /* CoreServices.framework */; };
EB7D007D1DFF8DF20080202B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7D007A1DFF8DF20080202B /* Foundation.framework */; };
EB7E13591E4007CB00D28090 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* main.swift */; };
EB9A7F8E1E688ED600F7E83B /* commonFuncs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB9A7F8C1E688ED600F7E83B /* commonFuncs.swift */; };
EB9A7F8F1E688ED600F7E83B /* LSWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB9A7F8D1E688ED600F7E83B /* LSWrappers.swift */; };
EBB91C1722B56D2E0072B8E7 /* libSwiftCLI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* libSwiftCLI.a */; };
EBBCEE781E257BFF00DFC648 /* getSchemes.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBCEE771E257BFF00DFC648 /* getSchemes.swift */; };
EBBCEE7A1E25BDD700DFC648 /* getUTIs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBCEE791E25BDD700DFC648 /* getUTIs.swift */; };
OBJ_38 /* ChainableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* ChainableCommand.swift */; };
@@ -78,8 +78,8 @@
OBJ_25 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
OBJ_26 /* VersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionCommand.swift; sourceTree = "<group>"; };
OBJ_28 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
OBJ_31 /* SwiftCLI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftCLI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_32 /* lsreg */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = lsreg; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_31 /* libSwiftCLI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSwiftCLI.a; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_32 /* swda */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = swda; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; };
OBJ_9 /* ChainableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainableCommand.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -96,10 +96,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 0;
files = (
EBB91C1722B56D2E0072B8E7 /* libSwiftCLI.a in Frameworks */,
EB7D007B1DFF8DF20080202B /* Cocoa.framework in Frameworks */,
EB7D007C1DFF8DF20080202B /* CoreServices.framework in Frameworks */,
EB7D007D1DFF8DF20080202B /* Foundation.framework in Frameworks */,
EB7D00751DFF40ED0080202B /* SwiftCLI.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -150,8 +150,8 @@
OBJ_30 /* Products */ = {
isa = PBXGroup;
children = (
OBJ_31 /* SwiftCLI.framework */,
OBJ_32 /* lsreg */,
OBJ_31 /* libSwiftCLI.a */,
OBJ_32 /* swda */,
);
name = Products;
sourceTree = "<group>";
@@ -228,8 +228,8 @@
);
name = SwiftCLI;
productName = SwiftCLI;
productReference = OBJ_31 /* SwiftCLI.framework */;
productType = "com.apple.product-type.framework";
productReference = OBJ_31 /* libSwiftCLI.a */;
productType = "com.apple.product-type.library.static";
};
OBJ_57 /* SwiftDefaultApps */ = {
isa = PBXNativeTarget;
@@ -246,7 +246,7 @@
);
name = SwiftDefaultApps;
productName = SwDefaultApps;
productReference = OBJ_32 /* lsreg */;
productReference = OBJ_32 /* swda */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
@@ -255,19 +255,20 @@
OBJ_1 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "Gregorio Litenstein Goldzweig";
TargetAttributes = {
OBJ_33 = {
LastSwiftMigration = 0900;
LastSwiftMigration = 1020;
};
OBJ_57 = {
LastSwiftMigration = 0900;
LastSwiftMigration = "";
};
};
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SwiftDefaultApps CLI" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -339,7 +340,6 @@
OBJ_3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -377,12 +377,14 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-DXcode";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
USE_HEADERMAP = NO;
};
name = Debug;
@@ -401,10 +403,9 @@
PRODUCT_BUNDLE_IDENTIFIER = SwiftCLI;
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGET_NAME = SwiftCLI;
};
name = Debug;
@@ -423,10 +424,9 @@
PRODUCT_BUNDLE_IDENTIFIER = SwiftCLI;
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGET_NAME = SwiftCLI;
};
name = Release;
@@ -434,7 +434,6 @@
OBJ_4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -457,7 +456,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = s;
GCC_OPTIMIZATION_LEVEL = 3;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -471,10 +470,11 @@
MACOSX_DEPLOYMENT_TARGET = 10.12;
OTHER_SWIFT_FLAGS = "-DXcode";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
USE_HEADERMAP = NO;
};
name = Release;
@@ -482,8 +482,11 @@
OBJ_59 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = 33JDJVTQZT;
FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
HEADER_SEARCH_PATHS = "";
INFOPLIST_FILE = SwDefaultApps.xcodeproj/SwDefaultApps_Info.plist;
@@ -493,25 +496,25 @@
);
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_NAME = lsreg;
PRODUCT_NAME = swda;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGET_NAME = SwDefaultApps;
};
name = Debug;
};
OBJ_60 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = 33JDJVTQZT;
FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
HEADER_SEARCH_PATHS = "";
INFOPLIST_FILE = SwDefaultApps.xcodeproj/SwDefaultApps_Info.plist;
@@ -521,15 +524,12 @@
);
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_NAME = lsreg;
PRODUCT_NAME = swda;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGET_NAME = SwDefaultApps;
};
name = Release;
};
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:/Users/Koji/SwDefaultApps/SwiftDefaultApps.xcodeproj">
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,30 +0,0 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "6905E4CA861C743F3E8A8C5447173D531DEA16D7",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"6905E4CA861C743F3E8A8C5447173D531DEA16D7" : 9223372036854775807,
"1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E" : 9223372036854775807
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "3A4F54B3-D06F-4F29-B1C3-B2AAC23011F4",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"6905E4CA861C743F3E8A8C5447173D531DEA16D7" : "SwiftDefaultApps\/",
"1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E" : "SwiftDefaultApps\/Packages\/SwiftCLI-2.0.3\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "SwiftDefaultApps CLI",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SwiftDefaultApps CLI.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Lord-Kamina\/SwiftCLI.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Lord-Kamina\/SwiftDefaultApps.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "6905E4CA861C743F3E8A8C5447173D531DEA16D7"
}
]
}
@@ -1,30 +0,0 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "6905E4CA861C743F3E8A8C5447173D531DEA16D7",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"6905E4CA861C743F3E8A8C5447173D531DEA16D7" : 9223372036854775807,
"1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E" : 9223372036854775807
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "945ED44B-7987-41E0-BEAC-F39B17716DDC",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"6905E4CA861C743F3E8A8C5447173D531DEA16D7" : "SwiftDefaultApps\/",
"1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E" : "SwiftDefaultApps\/Packages\/SwiftCLI-2.0.3\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "SwiftDefaultApps",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SwiftDefaultApps.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/jakeheis\/SwiftCLI",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1FA2FB8C2BC57F03F22E05F4E40A3BD4587C910E"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Lord-Kamina\/SwiftDefaultApps.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "6905E4CA861C743F3E8A8C5447173D531DEA16D7"
}
]
}
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
<dict/>
</plist>
@@ -4,8 +4,6 @@
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>BuildSystemType</key>
<string>Latest</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1030"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OBJ_57"
BuildableName = "lsreg"
BuildableName = "swda"
BlueprintName = "SwiftDefaultApps"
ReferencedContainer = "container:SwiftDefaultApps CLI.xcodeproj">
</BuildableReference>
@@ -74,7 +74,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "OBJ_57"
BuildableName = "lsreg"
BuildableName = "swda"
BlueprintName = "SwiftDefaultApps"
ReferencedContainer = "container:SwiftDefaultApps CLI.xcodeproj">
</BuildableReference>