Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4672a7f028 | ||
|
|
19bd0836d6 | ||
|
|
c35002a671 | ||
|
|
6fb3fadc6e | ||
|
|
8cb414a906 | ||
|
|
a2f3dbac3c | ||
|
|
8fa723ef79 | ||
|
|
665c721bfd | ||
|
|
bd59579501 | ||
|
|
86d6666db1 | ||
|
|
64c1f81492 | ||
|
|
0dd7b0be76 | ||
|
|
b02592772b | ||
|
|
439f215109 | ||
|
|
54dd71fcce | ||
|
|
ffc83b2481 | ||
|
|
f4010abe44 | ||
|
|
41733bf973 | ||
|
|
10288a3810 | ||
|
|
3a09834f5a | ||
|
|
224d24e260 | ||
|
|
28f21667d2 | ||
|
|
dcafe50852 | ||
|
|
c8d961c035 | ||
|
|
9e4ca7d54f | ||
|
|
122a639bea |
@@ -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
|
||||
|
||||
@@ -74,19 +74,18 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0820;
|
||||
LastUpgradeCheck = 0930;
|
||||
LastUpgradeCheck = 1030;
|
||||
ORGANIZATIONNAME = "Gregorio Litenstein Goldzweig";
|
||||
TargetAttributes = {
|
||||
EBA7F0911E856279009EE061 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
LastSwiftMigration = 1000;
|
||||
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)",
|
||||
@@ -186,7 +180,7 @@
|
||||
SWIFT_COMPILATION_MODE = singlefile;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -194,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;
|
||||
@@ -242,7 +234,7 @@
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -250,11 +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_VERSION = 4.2;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -262,11 +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_VERSION = 4.2;
|
||||
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 = "1000"
|
||||
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 |
@@ -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]
|
||||
)
|
||||
|
||||
@@ -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,40 @@ 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.3
|
||||
- Date: 2018-11-15
|
||||
|
||||
- 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.
|
||||
@@ -71,5 +131,3 @@ The user-interface should be pretty self-explanatory; but, there are some things
|
||||
+ Initial release!
|
||||
+ ### Changed
|
||||
+ ### Fixed
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.3</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>225</string>
|
||||
<string>334</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Gregorio Litenstein Goldzweig. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
|
||||
@@ -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 = 1000;
|
||||
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\"\n";
|
||||
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,17 +430,17 @@
|
||||
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;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -434,33 +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;
|
||||
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.2;
|
||||
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;
|
||||
@@ -469,72 +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;
|
||||
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;
|
||||
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.2;
|
||||
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_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";
|
||||
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_VERSION = 4.2;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_EXPORT_DECL = "";
|
||||
WRAPPER_EXTENSION = prefPane;
|
||||
WRAPPER_EXTENSION = prefpane;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
EBD61FEA1E47EC3C00549EE8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
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";
|
||||
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_VERSION = 4.2;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_EXPORT_DECL = "";
|
||||
WRAPPER_EXTENSION = prefPane;
|
||||
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 = "1000"
|
||||
LastUpgradeVersion = "1030"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
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 ] && rm -Rv ~/Library/PreferencePanes/SwiftDefaultApps.prefpane cp -Rf "${CONFIGURATION_BUILD_DIR}/SwiftDefaultApps.prefpane" ~/Library/PreferencePanes/ [ -d "${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app" ] && rm -Rv "${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app"">
|
||||
scriptText = "[ -d ~/Library/PreferencePanes/SwiftDefaultApps.prefpane ] && rm -Rv ~/Library/PreferencePanes/SwiftDefaultApps.prefpane cp -Rf "${CONFIGURATION_BUILD_DIR}/SwiftDefaultApps.prefpane" ~/Library/PreferencePanes/ [ -d "${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app" ] && rm -Rv "${CONFIGURATION_BUILD_DIR}/ThisAppDoesNothing.app" ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -112,7 +126,7 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
||||
@@ -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.") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)")) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.") }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,133 +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!) {
|
||||
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()
|
||||
}
|
||||
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 hash: Int { return (self.appBundleInfo?.appBundleID)?.hash ?? -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 collection’s 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 collection’s 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 sequence’s 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(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)
|
||||
}
|
||||
}
|
||||
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.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", "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.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)
|
||||
}
|
||||
}
|
||||
/** 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: [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()
|
||||
}
|
||||
@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")
|
||||
@@ -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,10 +255,11 @@
|
||||
OBJ_1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1000;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = "Gregorio Litenstein Goldzweig";
|
||||
TargetAttributes = {
|
||||
OBJ_33 = {
|
||||
LastSwiftMigration = "";
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
OBJ_57 = {
|
||||
LastSwiftMigration = "";
|
||||
@@ -267,7 +268,7 @@
|
||||
};
|
||||
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;
|
||||
@@ -380,10 +380,11 @@
|
||||
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.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -402,9 +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_VERSION = 4.2;
|
||||
TARGET_NAME = SwiftCLI;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -423,9 +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_VERSION = 4.2;
|
||||
TARGET_NAME = SwiftCLI;
|
||||
};
|
||||
name = Release;
|
||||
@@ -433,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;
|
||||
@@ -456,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;
|
||||
@@ -470,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.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Release;
|
||||
@@ -484,6 +485,8 @@
|
||||
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,14 +496,14 @@
|
||||
);
|
||||
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_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -510,6 +513,8 @@
|
||||
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;
|
||||
@@ -519,12 +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_VERSION = 4.2;
|
||||
};
|
||||
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 = "1000"
|
||||
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>
|
||||
|
||||