18 Commits
Author SHA1 Message Date
acheronfail 222ccffb7b chore: build against macOS 10.14 SDK 2018-11-02 15:52:58 +11:00
acheronfail 0f7f7524a7 chore: update dependencies 2018-11-02 15:52:39 +11:00
acheronfail 20192b6cb2 version 1.2.3 2018-06-06 07:17:38 +10:00
acheronfail ec78ffb864 fix: update views to work better with new macOS interface styles 2018-06-06 07:11:12 +10:00
acheronfail 9ce676c51c fixup: indentation 2018-06-05 22:52:36 +10:00
acheronfail 1ad62f4bf8 chore: update to Swift 4.2 - fixes #4 2018-06-05 22:51:31 +10:00
acheronfail fee08c000b version 1.2.2 2018-05-31 07:17:42 +10:00
acheronfail e087df3b93 feat: space now picks color; arrow keys cycle between formats 2018-05-31 07:17:16 +10:00
acheronfail a7d112a8ba chore: doc updates 2018-05-30 17:28:46 +10:00
acheronfail b4175bae13 version 1.2.1 2018-05-30 16:51:48 +10:00
acheronfail a63fd85bc5 fix: ensure cursor is hidden even when apps change 2018-05-30 16:51:32 +10:00
acheronfail ff1063c8e2 chore: move CGImage extension into its own file 2018-05-30 15:33:09 +10:00
acheronfail 1305e4366a chore: refactoring code; move NSBezierPath extension to its own file 2018-05-30 15:32:51 +10:00
acheronfail 4cad88d0cd chore: updated cursor api usage definitions 2018-05-30 15:32:44 +10:00
acheronfail 58f3ef9ecd fix: add grid before crosshair 2018-05-26 09:07:34 +10:00
acheronfail 8b907ac7ed tweak: reduce emoji overkill in README 2018-05-26 09:05:18 +10:00
acheronfail e5ce05dc89 tweak: README changes 2018-05-26 02:24:23 +10:00
acheronfail 42f88bcf53 chore: update demo image - fixes #13 2018-05-26 02:16:13 +10:00
37 changed files with 313 additions and 252 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
github "SwiftyJSON/SwiftyJSON" "4.1.0"
github "SwiftyJSON/SwiftyJSON" "4.2.0"
github "acheronfail/MASShortcut" "2.3.6-custom"
github "emaloney/CleanroomLogger" "6.0.2"
github "sindresorhus/LaunchAtLogin" "v2.1.0"
github "sindresorhus/LaunchAtLogin" "v2.4.0"
@@ -1,2 +1 @@
* text=auto
*.swift text eol=lf
* text=auto eol=lf
+14
View File
@@ -0,0 +1,14 @@
Pod::Spec.new do |s|
s.name = 'LaunchAtLogin'
s.version = '2.4.0'
s.summary = 'Add "Launch at Login" functionality to your sandboxed macOS app in seconds'
s.license = 'MIT'
s.homepage = 'https://github.com/sindresorhus/LaunchAtLogin'
s.social_media_url = 'https://twitter.com/sindresorhus'
s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' }
s.source = { :git => 'https://github.com/sindresorhus/LaunchAtLogin.git', :tag => "v#{s.version}" }
s.source_files = 'LaunchAtLogin', 'LaunchAtLoginHelper'
s.resource = 'LaunchAtLogin/copy-helper.sh'
s.swift_version = '4.2'
s.platform = :macos, '10.12'
end
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
@@ -32,8 +32,8 @@
E32E9B741EB87EA3000FEEE9 /* LaunchAtLoginHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LaunchAtLoginHelper.app; sourceTree = BUILT_PRODUCTS_DIR; };
E32E9B761EB87EA3000FEEE9 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = main.swift; sourceTree = "<group>"; usesTabs = 1; };
E32E9B7F1EB87EA3000FEEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E32E9B831EB87F57000FEEE9 /* LaunchAtLoginHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LaunchAtLoginHelper.entitlements; sourceTree = "<group>"; };
E32E9B921EB889AE000FEEE9 /* copy-helper.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = "copy-helper.sh"; sourceTree = "<group>"; usesTabs = 1; };
E3B8C38A20C0003300272EC0 /* LaunchAtLoginHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LaunchAtLoginHelper.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -89,7 +89,7 @@
children = (
E32E9B761EB87EA3000FEEE9 /* main.swift */,
E32E9B7F1EB87EA3000FEEE9 /* Info.plist */,
E32E9B831EB87F57000FEEE9 /* LaunchAtLoginHelper.entitlements */,
E3B8C38A20C0003300272EC0 /* LaunchAtLoginHelper.entitlements */,
);
path = LaunchAtLoginHelper;
sourceTree = "<group>";
@@ -151,17 +151,20 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 0940;
ORGANIZATIONNAME = "Sindre Sorhus";
TargetAttributes = {
E32E9B621EB87D7B000FEEE9 = {
CreatedOnToolsVersion = 8.3.2;
LastSwiftMigration = 0900;
LastSwiftMigration = "";
};
E32E9B731EB87EA3000FEEE9 = {
CreatedOnToolsVersion = 8.3.2;
LastSwiftMigration = 0900;
LastSwiftMigration = "";
SystemCapabilities = {
com.apple.HardenedRuntime = {
enabled = 1;
};
com.apple.Sandbox = {
enabled = 1;
};
@@ -170,7 +173,7 @@
};
};
buildConfigurationList = E32E9B5D1EB87D7B000FEEE9 /* Build configuration list for PBXProject "LaunchAtLogin" */;
compatibilityVersion = "Xcode 9.3";
compatibilityVersion = "Xcode 10.0";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
@@ -265,7 +268,7 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
@@ -328,7 +331,7 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@@ -357,7 +360,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -379,8 +382,7 @@
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -388,7 +390,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -408,8 +410,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
@@ -421,6 +422,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = YG56YK5RN5;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = LaunchAtLoginHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -430,8 +432,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -443,6 +444,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = YG56YK5RN5;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = LaunchAtLoginHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -452,8 +454,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
+1 -5
View File
@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -15,12 +13,10 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<string>2.4.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>MIT © Sindre Sorhus</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -13,5 +13,5 @@ codesign --force --entitlements="$CODE_SIGN_ENTITLEMENTS" --sign="$EXPANDED_CODE
if [[ $CONFIGURATION == "Release" ]]; then
rm -rf "$origin_helper_path"
rm "$(dirname $origin_helper_path)/copy-helper.sh"
rm "$(dirname "$origin_helper_path")/copy-helper.sh"
fi
@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
+21
View File
@@ -0,0 +1,21 @@
# Before and after
With `LaunchAtLogin`, you only have to do 2 steps instead of 13!
```diff
- 1. Create a new target that will be the helper app that launches your app
- 2. Set `LSBackgroundOnly` to true in the `Info.plist` file
- 3. Set `Skip Install` to `YES` in the build settings for the helper app
- 4. Enable sandboxing for the helper app
- 5. Add a new `Copy Files` build phase to the main app
- 6. Select `Wrapper` as destination
- 7. Enter `Contents/Library/LoginItems` as subpath
- 8. Add the helper build product to the build phase
- 9. Copy-paste some boilerplate code into the helper app
- 10. Remember to replace `bundleid.of.main.app` and `MainExectuableName` with your own values
- 11. Copy-paste some code to register the helper app into your main app
- 12. Make sure the main app and helper app use the same code signing certificate
- 13. Manually verify that you did everything correctly
+ 1. Install this package
+ 2. Add a new "Run Script Phase"
```
+23 -22
View File
@@ -2,25 +2,7 @@
> Add "Launch at Login" functionality to your sandboxed macOS app in seconds
It's usually quite a convoluted and error-prone process to add this. **No more!**
```diff
- 1. Create a new target that will be the helper app that launches your app
- 2. Set `LSBackgroundOnly` to true in the `Info.plist` file
- 3. Set `Skip Install` to `YES` in the build settings for the helper app
- 4. Enable sandboxing for the helper app
- 5. Add a new `Copy Files` build phase to the main app
- 6. Select `Wrapper` as destination
- 7. Enter `Contents/Library/LoginItems` as subpath
- 8. Add the helper build product to the build phase
- 9. Copy-paste some boilerplate code into the helper app
- 10. Remember to replace `bundleid.of.main.app` and `MainExectuableName` with your own values
- 11. Copy-paste some code to register the helper app into your main app
- 12. Make sure the main app and helper app use the same code signing certificate
- 13. Manually verify that you did everything correctly
+ 1. Install this package
+ 2. Add a new "Run Script Phase"
```
It's usually quite a [convoluted and error-prone process](before-after.md) to add this. **No more!**
It's App Store compatible and used in my [Lungo](https://blog.sindresorhus.com/lungo-b364a6c2745f) and [Battery Indicator](https://sindresorhus.com/battery-indicator) app.
@@ -30,16 +12,24 @@ You might also find my [`create-dmg`](https://github.com/sindresorhus/create-dmg
## Requirements
- macOS 10.12+
- Xcode 9.3+
- Swift 4.1+
- Xcode 10+
- Swift 4.2+
## Install with [Carthage](https://github.com/Carthage/Carthage#getting-started)
## Install
#### Carthage
```
github "sindresorhus/LaunchAtLogin"
```
#### CocoaPods
```ruby
pod 'LaunchAtLogin'
```
<a href="https://www.patreon.com/sindresorhus">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
@@ -49,10 +39,18 @@ github "sindresorhus/LaunchAtLogin"
Add a new ["Run Script Phase"](http://stackoverflow.com/a/39633955/64949) below "Embed Frameworks" in "Build Phases" with the following:
Carthage:
```sh
./Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh
```
CocoaPods:
```sh
./Pods/LaunchAtLogin/LaunchAtLogin/copy-helper.sh
```
Use it in your app:
```swift
@@ -77,7 +75,10 @@ The framework bundles the helper app needed to launch your app and copies it int
## Related
- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults
- [Preferences](https://github.com/sindresorhus/Preferences) - Add a preferences window to your macOS app in minutes
- [DockProgress](https://github.com/sindresorhus/DockProgress) - Show progress in your app's Dock icon
- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift)
## License
-1
View File
@@ -1 +0,0 @@
3.0
+2 -2
View File
@@ -1,6 +1,6 @@
language: objective-c
osx_image: xcode9.3
xcode_sdk: iphonesimulator10.0
osx_image: xcode10
xcode_sdk: iphonesimulator12.0
script:
- set -o pipefail
- travis_retry xcodebuild -workspace SwiftyJSON.xcworkspace -scheme "SwiftyJSON iOS" -destination "platform=iOS Simulator,name=iPhone 6" build-for-testing test | xcpretty
@@ -129,7 +129,7 @@
TargetAttributes = {
A82A1C1819D926B8009A653D = {
CreatedOnToolsVersion = 6.0.1;
LastSwiftMigration = 0820;
LastSwiftMigration = 0930;
ProvisioningStyle = Manual;
};
};
@@ -313,7 +313,8 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.swiftyjson.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
@@ -328,7 +329,8 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.swiftyjson.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
};
name = Release;
};
-37
View File
@@ -24,22 +24,6 @@ import Foundation
// MARK: - Error
// swiftlint:disable line_length
/// Error domain
@available(*, deprecated, message: "ErrorDomain is deprecated. Use `SwiftyJSONError.errorDomain` instead.", renamed: "SwiftyJSONError.errorDomain")
public let ErrorDomain: String = "SwiftyJSONErrorDomain"
/// Error code
@available(*, deprecated, message: "ErrorUnsupportedType is deprecated. Use `SwiftyJSONError.unsupportedType` instead.", renamed: "SwiftyJSONError.unsupportedType")
public let ErrorUnsupportedType: Int = 999
@available(*, deprecated, message: "ErrorIndexOutOfBounds is deprecated. Use `SwiftyJSONError.indexOutOfBounds` instead.", renamed: "SwiftyJSONError.indexOutOfBounds")
public let ErrorIndexOutOfBounds: Int = 900
@available(*, deprecated, message: "ErrorWrongType is deprecated. Use `SwiftyJSONError.wrongType` instead.", renamed: "SwiftyJSONError.wrongType")
public let ErrorWrongType: Int = 901
@available(*, deprecated, message: "ErrorNotExist is deprecated. Use `SwiftyJSONError.notExist` instead.", renamed: "SwiftyJSONError.notExist")
public let ErrorNotExist: Int = 500
@available(*, deprecated, message: "ErrorInvalidJSON is deprecated. Use `SwiftyJSONError.invalidJSON` instead.", renamed: "SwiftyJSONError.invalidJSON")
public let ErrorInvalidJSON: Int = 490
public enum SwiftyJSONError: Int, Swift.Error {
case unsupportedType = 999
case indexOutOfBounds = 900
@@ -146,19 +130,6 @@ public struct JSON {
}
}
/**
Creates a JSON from JSON string
- parameter json: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
@available(*, deprecated, message: "Use instead `init(parseJSON: )`")
public static func parse(_ json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap { try? JSON(data: $0) } ?? JSON(NSNull())
}
/**
Creates a JSON using the object.
@@ -592,14 +563,6 @@ extension JSON: Swift.ExpressibleByArrayLiteral {
}
}
extension JSON: Swift.ExpressibleByNilLiteral {
@available(*, deprecated, message: "use JSON.null instead. Will be removed in future versions")
public init(nilLiteral: ()) {
self.init(NSNull() as Any)
}
}
// MARK: - Raw
extension JSON: Swift.RawRepresentable {
+3 -2
View File
@@ -1,15 +1,16 @@
Pod::Spec.new do |s|
s.name = "SwiftyJSON"
s.version = "4.1.0"
s.version = "4.2.0"
s.summary = "SwiftyJSON makes it easy to deal with JSON data in Swift"
s.homepage = "https://github.com/SwiftyJSON/SwiftyJSON"
s.license = { :type => "MIT" }
s.authors = { "lingoer" => "lingoerer@gmail.com", "tangplin" => "tangplin@gmail.com" }
s.requires_arc = true
s.swift_version = "4.2"
s.osx.deployment_target = "10.9"
s.ios.deployment_target = "8.0"
s.watchos.deployment_target = "2.0"
s.watchos.deployment_target = "3.0"
s.tvos.deployment_target = "9.0"
s.source = { :git => "https://github.com/SwiftyJSON/SwiftyJSON.git", :tag => s.version }
s.source_files = "Source/*.swift"
@@ -471,14 +471,17 @@
TestTargetID = 2E4FEFDA19575BE100351305;
};
7236B4EC1BAC14150020529B = {
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
};
9C7DFC5A1A9102BD005AA3F7 = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
};
9C7DFC641A9102BD005AA3F7 = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 1000;
};
A81D162B1E5743B000C62C5F = {
CreatedOnToolsVersion = 8.2.1;
@@ -486,8 +489,10 @@
};
A8580F731BCF5C5B00DA927B = {
CreatedOnToolsVersion = 7.1;
LastSwiftMigration = 1000;
};
E4D7CCDE1B9465A700EE7221 = {
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
};
};
@@ -936,6 +941,7 @@
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
@@ -961,6 +967,7 @@
PRODUCT_NAME = SwiftyJSON;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
@@ -993,6 +1000,7 @@
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -1019,6 +1027,7 @@
PRODUCT_NAME = "$(PROJECT_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
};
name = Release;
};
@@ -1041,6 +1050,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -1059,6 +1069,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.swiftyjson.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_VERSION = 4.2;
};
name = Release;
};
@@ -1092,6 +1103,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
name = Debug;
@@ -1110,6 +1122,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.tangplin.SwiftyJSON-tvOS-Tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SWIFT_VERSION = 4.2;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
name = Release;
@@ -1136,8 +1149,9 @@
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "watchsimulator watchos";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Debug;
};
@@ -1162,8 +1176,9 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "watchsimulator watchos";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 2.0;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Release;
};
@@ -127,7 +127,7 @@ class PerformanceTests: XCTestCase {
self.measure {
autoreleasepool {
if let dictionary = json.dictionary {
XCTAssertTrue(dictionary.count > 0)
XCTAssertTrue(dictionary.count == 100001)
} else {
XCTFail("dictionary should not be nil")
}
+16 -10
View File
@@ -7,7 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
15349DB820BAE3F70071CAA1 /* NSBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15349DB720BAE3F70071CAA1 /* NSBezierPath.swift */; };
15492103207B53AE00E45BBC /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15492102207B53AE00E45BBC /* Util.swift */; };
15493F6720BE6E0F007B2BA5 /* CGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15493F6620BE6E0F007B2BA5 /* CGImage.swift */; };
155D2C03204F61220087478B /* Credits.rtfd in Resources */ = {isa = PBXBuildFile; fileRef = 155D2C02204F61220087478B /* Credits.rtfd */; };
159F304E2079A111000BD387 /* AppDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159F304D2079A111000BD387 /* AppDelegateTests.swift */; };
159F30522079A147000BD387 /* PPStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159F30512079A147000BD387 /* PPStateTests.swift */; };
@@ -65,7 +67,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
15349DB720BAE3F70071CAA1 /* NSBezierPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBezierPath.swift; sourceTree = "<group>"; };
15492102207B53AE00E45BBC /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
15493F6620BE6E0F007B2BA5 /* CGImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGImage.swift; sourceTree = "<group>"; };
155D2C02204F61220087478B /* Credits.rtfd */ = {isa = PBXFileReference; lastKnownFileType = wrapper.rtfd; path = Credits.rtfd; sourceTree = "<group>"; };
159F304D2079A111000BD387 /* AppDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateTests.swift; sourceTree = "<group>"; };
159F30512079A147000BD387 /* PPStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPStateTests.swift; sourceTree = "<group>"; };
@@ -171,6 +175,7 @@
B3B4C2BF1E25894B009F8E4E /* PixelPicker */ = {
isa = PBXGroup;
children = (
15493F6620BE6E0F007B2BA5 /* CGImage.swift */,
BA74C13420A6F1BE00306B27 /* ShowAndHideCursor.h */,
BA74C13220A6F16C00306B27 /* ShowAndHideCursor.m */,
BA71817320AE2DCC00619700 /* PPMenuShortcutView.h */,
@@ -180,6 +185,7 @@
BA74C14D20ADA33E00306B27 /* PPColor.swift */,
15FC8CC7204E2E2C000B5E1E /* PPState.swift */,
159ED10B207EB2EF005489ED /* OverlayPanel */,
15349DB720BAE3F70071CAA1 /* NSBezierPath.swift */,
BA74C14120AA409D00306B27 /* NSColor.swift */,
15492102207B53AE00E45BBC /* Util.swift */,
B3B4C2C21E25894B009F8E4E /* Assets.xcassets */,
@@ -250,13 +256,13 @@
TargetAttributes = {
15B4E86F207994AF0024FDE4 = {
CreatedOnToolsVersion = 9.3;
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
TestTargetID = B3B4C2BC1E25894B009F8E4E;
};
B3B4C2BC1E25894B009F8E4E = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = UNR5C68RPX;
LastSwiftMigration = 0920;
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
};
};
@@ -333,6 +339,7 @@
buildActionMask = 2147483647;
files = (
BA74C13320A6F16C00306B27 /* ShowAndHideCursor.m in Sources */,
15349DB820BAE3F70071CAA1 /* NSBezierPath.swift in Sources */,
BA71817220AE2DB400619700 /* PPMenuShortcutView.m in Sources */,
B3B4C2C11E25894B009F8E4E /* AppDelegate.swift in Sources */,
BA22232920B04D660001069C /* AppDelegate+Menu.swift in Sources */,
@@ -342,6 +349,7 @@
BA74C14020AA390400306B27 /* PPOverlayPreview.swift in Sources */,
BA74C14420AA609500306B27 /* PPOverlayWrapper.swift in Sources */,
BA74C0F820A692D100306B27 /* PPOverlayController.swift in Sources */,
15493F6720BE6E0F007B2BA5 /* CGImage.swift in Sources */,
BA74C14220AA409D00306B27 /* NSColor.swift in Sources */,
15492103207B53AE00E45BBC /* Util.swift in Sources */,
);
@@ -381,7 +389,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.acheronfail.PixelPickerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PixelPicker.app/Contents/MacOS/PixelPicker";
};
name = Debug;
@@ -409,7 +417,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.acheronfail.PixelPickerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PixelPicker.app/Contents/MacOS/PixelPicker";
};
name = Release;
@@ -538,7 +546,7 @@
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = UNR5C68RPX;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
@@ -551,8 +559,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "PixelPicker/Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -564,7 +571,7 @@
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = UNR5C68RPX;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
@@ -577,8 +584,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "PixelPicker/Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
+2 -2
View File
@@ -236,10 +236,9 @@ extension AppDelegate: NSMenuDelegate {
}
// Build a submenu with each case in the PPColor enum.
// TODO: with Swift 4.2, we shouldn't need to resort to the hacky "iterateEnum" approach.
private func buildColorFormatsMenu() {
let submenu = NSMenu()
for format in iterateEnum(PPColor.self) {
for format in PPColor.allCases {
let formatItem = submenu.addItem(withTitle: format.rawValue, action: #selector(selectFormat(_:)), keyEquivalent: "")
formatItem.representedObject = format
if PPState.shared.chosenFormat == format { formatItem.state = .on }
@@ -282,6 +281,7 @@ extension AppDelegate: NSMenuDelegate {
contextMenu.addItem(withTitle: "Picker Shortcut", action: nil, keyEquivalent: "")
let shortcutView = MASShortcutView()
shortcutView.style = .flat
shortcutView.shortcutValue = PPState.shared.activatingShortcut
shortcutView.shortcutValueChange = { PPState.shared.activatingShortcut = $0?.shortcutValue }
+1 -1
View File
@@ -7,7 +7,7 @@ import MASShortcut
import CleanroomLogger
// The app's menu bar item.
let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(stringLiteral: "icon")))
@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
+61
View File
@@ -0,0 +1,61 @@
//
// CGImage.swift
// PixelPicker
//
import CleanroomLogger
extension CGImage {
// In order to get the color at a given pixel from a CGImage, we need to convert the CGImage's
// data to a bitmap and draw it. We do so via a CGContext, and then manually extract the data at
// the desired pixel.
func colorAt(x: Int, y: Int) -> NSColor {
assert(0 <= x && x < self.width)
assert(0 <= y && y < self.height)
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * Int(self.height)
// Allocate memory for image data. This is the destination in memory where any drawing to
// the bitmap context will be rendered.
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
let bitmapData = malloc(bitmapByteCount)
// Since we manually allocate memeory for the data, we must ensure that the same memory is
// freed after we've used it.
defer { free(bitmapData) }
// Create the bitmap context.
let context = CGContext(
data: bitmapData,
width: self.width,
height: self.height,
bitsPerComponent: 8,
bytesPerRow: bitmapBytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: bitmapInfo.rawValue
)
// Extract the pixel data from the right offset.
if context != nil {
// First, we draw the image data onto the context we created.
let rect = CGRect(x: 0, y: 0, width: self.width, height: self.height)
context!.draw(self, in: rect)
// Then we extract the data at the right spot.
let data = context!.data!
let offset = 4 * (y * width + x)
let a = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0
let r = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0
let g = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0
let b = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0
return NSColor(red: r, green: g, blue: b, alpha: a)
}
// Creating the context failed, so return a default color instead.
// Hopefully, this should never happen.
Log.error?.message("Failed to create CGContext!")
return NSColor.black
}
}
+2 -2
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.0</string>
<string>1.2.4</string>
<key>CFBundleVersion</key>
<string>5</string>
<string>9</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
+22
View File
@@ -0,0 +1,22 @@
//
// NSBezierPath.swift
// PixelPicker
//
// Extend NSBezierPath to allow easy conversion to a CGPath.
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-SW2
extension NSBezierPath {
var cgPath: CGPath {
let path = CGMutablePath()
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< self.elementCount {
switch self.element(at: i, associatedPoints: &points) {
case .moveTo: path.move(to: points[0])
case .lineTo: path.addLine(to: points[0])
case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePath: path.closeSubpath()
}
}
return path
}
}
@@ -90,19 +90,28 @@ class PPOverlayController: NSWindowController {
// When the user clicks, animate the cursor out, save the picked color and copy it.
override func mouseDown(with event: NSEvent) {
hidePicker(animate: true)
let pickedColor = PPPickedColor(color: lastHighlightedColor, format: PPState.shared.chosenFormat)
PPState.shared.addRecentPick(pickedColor)
copyToPasteboard(stringValue: pickedColor.asString)
pickColor()
}
// Close picker immediately when the user presses the escape key.
// Listen to key events.
override func keyDown(with event: NSEvent) {
if event.keyCode == kVK_Escape { hidePicker(animate: false) }
if event.keyCode == kVK_Space {
let format = PPState.shared.chosenFormat
PPState.shared.chosenFormat = event.modifierFlags.contains(.shift) ? format.previous() : format.next()
switch Int(event.keyCode) {
// Close immediately on escape.
case kVK_Escape:
hidePicker(animate: false)
// Act as if the user clicked on space.
case kVK_Space:
hidePicker(animate: true)
pickColor()
// Cycle between formats when the user presses the left or right arrow keys.
case kVK_LeftArrow:
PPState.shared.chosenFormat = PPState.shared.chosenFormat.previous()
updateInfoPanel(lastHighlightedColor, lastHighlightedColor.bestContrastingColor())
case kVK_RightArrow:
PPState.shared.chosenFormat = PPState.shared.chosenFormat.next()
updateInfoPanel(lastHighlightedColor, lastHighlightedColor.bestContrastingColor())
default:
break
}
}
@@ -147,6 +156,12 @@ class PPOverlayController: NSWindowController {
}
}
func pickColor() {
let pickedColor = PPPickedColor(color: lastHighlightedColor, format: PPState.shared.chosenFormat)
PPState.shared.addRecentPick(pickedColor)
copyToPasteboard(stringValue: pickedColor.asString)
}
// Show the picker, save the previously active application, and hide the cursor.
func showPicker() {
if overlayPanel.isVisible || infoPanel.isVisible { return }
@@ -236,6 +251,10 @@ class PPOverlayController: NSWindowController {
// Ensure the overlayPanel is always the key window.
if !overlayPanel.isKeyWindow {
overlayPanel.activate(withSize: panelSize, infoPanel: infoPanel)
// Reset the cursor state to ensure it's hidden (the foremost application changes then
// WindowServer resets our "SetsCursorInBackground" magic).
ShowCursor()
HideCursor()
}
// Center the mouse in the picker window.
@@ -316,58 +335,3 @@ class PPOverlayController: NSWindowController {
return nil
}
}
extension CGImage {
// In order to get the color at a given pixel from a CGImage, we need to convert the CGImage's
// data to a bitmap and draw it. We do so via a CGContext, and then manually extract the data at
// the desired pixel.
func colorAt(x: Int, y: Int) -> NSColor {
assert(0 <= x && x < self.width)
assert(0 <= y && y < self.height)
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * Int(self.height)
// Allocate memory for image data. This is the destination in memory where any drawing to
// the bitmap context will be rendered.
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
let bitmapData = malloc(bitmapByteCount)
// Since we manually allocate memeory for the data, we must ensure that the same memory is
// freed after we've used it.
defer { free(bitmapData) }
// Create the bitmap context.
let context = CGContext(
data: bitmapData,
width: self.width,
height: self.height,
bitsPerComponent: 8,
bytesPerRow: bitmapBytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: bitmapInfo.rawValue
)
// Extract the pixel data from the right offset.
if context != nil {
// First, we draw the image data onto the context we created.
let rect = CGRect(x: 0, y: 0, width: self.width, height: self.height)
context!.draw(self, in: rect)
// Then we extract the data at the right spot.
let data = context!.data!
let offset = 4 * (y * width + x)
let a = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0
let r = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0
let g = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0
let b = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0
return NSColor(red: r, green: g, blue: b, alpha: a)
}
// Creating the context failed, so return a default color instead.
// Hopefully, this should never happen.
Log.error?.message("Failed to create CGContext!")
return NSColor.black
}
}
@@ -20,18 +20,19 @@ class PPOverlayPreview: NSView, CALayerDelegate {
override func awakeFromNib() {
// Make layers contents resize to fill, and disable antialiasing.
wantsLayer = true
layer?.magnificationFilter = kCAFilterNearest
layer?.contentsGravity = kCAGravityResizeAspectFill
layer?.magnificationFilter = .nearest
layer?.contentsGravity = .resizeAspectFill
layer?.delegate = self
// Add the grid shape layers to the view.
grid.strokeColor = NSColor.gray.cgColor
grid.fillColor = nil
layer?.addSublayer(grid)
// Prepare crosshair shape layers.
crosshair.strokeColor = NSColor.black.cgColor
crosshair.fillColor = nil
layer?.addSublayer(crosshair)
grid.strokeColor = NSColor.gray.cgColor
grid.fillColor = nil
layer?.addSublayer(grid)
}
// Update the crosshair with the correct color, position and size.
@@ -71,21 +72,3 @@ class PPOverlayPreview: NSView, CALayerDelegate {
grid.path = path.cgPath
}
}
// Extend NSBezierPath to allow easy conversion to a CGPath.
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-SW2
extension NSBezierPath {
var cgPath: CGPath {
let path = CGMutablePath()
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< self.elementCount {
switch self.element(at: i, associatedPoints: &points) {
case .moveToBezierPathElement: path.move(to: points[0])
case .lineToBezierPathElement: path.addLine(to: points[0])
case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePathBezierPathElement: path.closeSubpath()
}
}
return path
}
}
+4 -5
View File
@@ -39,7 +39,7 @@ struct PPPickedColor {
// This enum is responsible for each of the color formats PixelPicker supports,
// as well as being responsible for formatting them as strings, etc.
enum PPColor: String {
enum PPColor: String, CaseIterable {
case genericHex = "Generic Hex"
case generic8Bit = "Generic 8-Bit"
case genericDecimal = "Generic Decimal"
@@ -161,16 +161,15 @@ enum PPColor: String {
// Returns the PPColor that sits after this one.
func next() -> PPColor {
return next(withArray: iterateEnum(PPColor.self).map({ $0 }))
return next(withArray: PPColor.allCases)
}
// Same as next() but backwards.
func previous() -> PPColor {
return next(withArray: iterateEnum(PPColor.self).reversed())
return next(withArray: PPColor.allCases.reversed())
}
// Finds the next element after this element in the given array.
// This method should only be passed the result of iterateEnum(PPColor.self).
// Finds the next PPColor after this one in the list of PPColors.
private func next(withArray array: [PPColor]) -> PPColor {
var found = false
for x in array {
+19 -1
View File
@@ -6,6 +6,9 @@
#import "PPMenuShortcutView.h"
#import "PixelPicker-Swift.h"
// The trailing end of the cancel button in the MASShortcutView (within the NSMenuItem).
CGFloat cancelBoundary = 15;
// In order to have a MASShortcutView inside our menubar dropdown menu, we need to
// perform some hacks. Basically, we catch all the key and mouse events, and then
// use those to manually control the shortcut view (the MASShortcutView doesn't work
@@ -44,6 +47,21 @@
return self;
}
-(void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Draw a border around the view.
// Currently we don't use any of MASShortcut's custom drawing styles since they don't draw
// nicely with macOS 10.14's new dark interface style.
CGFloat xInset = 20;
CGFloat yInset = 1;
NSRect borderRect = NSMakeRect(self.frame.origin.x + xInset, self.frame.origin.y + yInset, self.frame.size.width - xInset - cancelBoundary, self.frame.size.height - (yInset * 2));
NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:borderRect xRadius:3 yRadius:3];
[[[NSColor scrollBarColor] colorWithAlphaComponent:0.5] set];
[border stroke];
}
// This is called when the NSMenu is opened, so it's a good time to register our
// intercepting event handlers to catch all keyboard events inside the NSMenu.
- (void)viewDidMoveToWindow
@@ -95,7 +113,7 @@
// End of the MASShortcutView.
CGFloat end = start + shortcutView.frame.size.width;
// Start of the cancel button in the MASShortcutView.
CGFloat cancelStart = end - 20;
CGFloat cancelStart = end - cancelBoundary;
if (shortcutView.shortcutValue != nil) {
if (point.x >= start && point.x <= cancelStart) {
+13 -5
View File
@@ -15,10 +15,18 @@ void LogWarning(void);
// We use an undocumented API to hide the cursor even when the application *isn't* active.
// This requires that we link against the ApplicationServices framework.
// See https://stackoverflow.com/a/3939241/5552584
// and https://web.archive.org/web/20150609013355/http://lists.apple.com:80/archives/carbon-dev/2006/Jan/msg00555.html
// and http://doomlaser.com/cursorcerer-hide-your-cursor-at-will/
void CGSSetConnectionProperty(int a, int b, CFStringRef c, CFBooleanRef d);
int _CGSDefaultConnection(void);
// See: https://stackoverflow.com/a/3939241/5552584
// Also: https://github.com/asmagill/hammerspoon_asm.undocumented/blob/master/cursor/CGSConnection.h
// Every application is given a singular connection ID through which it can receieve and manipulate
// values, state, notifications, events, etc. in the Window Server.
typedef int CGSConnectionID;
// Associates a value for the given key on the given connection.
CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value);
// Gets the default connection for this process. `CGSMainConnectionID` is just a more modern name.
CGSConnectionID _CGSDefaultConnection(void);
CGSConnectionID CGSMainConnectionID(void);
#endif /* ShowAndHideCursor_h */
+2 -1
View File
@@ -31,7 +31,8 @@ void HideCursor() {
#endif
CFStringRef propertyString = CFStringCreateWithCString(NULL, "SetsCursorInBackground", kCFStringEncodingUTF8);
CGSSetConnectionProperty(_CGSDefaultConnection(), _CGSDefaultConnection(), propertyString, kCFBooleanTrue);
CGSConnectionID cid = _CGSDefaultConnection();
CGSSetConnectionProperty(cid, cid, propertyString, kCFBooleanTrue);
CFRelease(propertyString);
CGDisplayHideCursor(kCGDirectMainDisplay);
+1 -13
View File
@@ -8,18 +8,6 @@ import CleanroomLogger
let APP_NAME = Bundle.main.infoDictionary![kCFBundleNameKey as String] as! String
let APPLE_INTERFACE_STYLE = "AppleInterfaceStyle"
// This allows us to iterate over the raw values of an enum.
// TODO: use `CaseIterable` when Swift 4.2 comes out
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
var i = 0
return AnyIterator {
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
if next.hashValue != i { return nil }
i += 1
return next
}
}
// Copies the given string to the clipboard.
func copyToPasteboard(stringValue value: String) {
NSPasteboard.general.declareTypes([.string], owner: nil)
@@ -68,7 +56,7 @@ enum Coordinate {
func runAnimation(_ f: (NSAnimationContext) -> Void, done: (() -> Void)?) {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.5
context.timingFunction = .init(name: kCAMediaTimingFunctionEaseInEaseOut)
context.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut)
context.allowsImplicitAnimation = true
f(context)
}, completionHandler: done)
-1
View File
@@ -4,6 +4,5 @@
//
import XCTest
@testable import PixelPicker
class AppDelegateTests: XCTestCase {}
-1
View File
@@ -4,4 +4,3 @@
//
import XCTest
@testable import PixelPicker
-1
View File
@@ -4,6 +4,5 @@
//
import XCTest
@testable import PixelPicker
class PPStateTests: XCTestCase {}
+22 -18
View File
@@ -4,11 +4,13 @@ PixelPicker is like Digital Color Meter, but lives in your menu bar and lets you
### Features
* Pick *any* pixel on your screen with ease
* Different zoom levels
* Multiple color export formats
* Multi-monitor support
* Global keyboard shortcuts
* 📸 Pick *any* pixel on your screen with ease
* 🔎 Different magnification levels
* ✏ Multiple color export formats
* 🖥 Multi-monitor support
* 🎨 Supports different color spaces
* ⌨️ Global keyboard shortcut activation
* 🌄 Customisable preview
* ✨
![demo of pixel-picker](./Resources/demo.png)
@@ -25,10 +27,10 @@ After you've installed PixelPicker, it will live in your menu bar. You have many
* Select the "Pick a Pixel" item in the dropdown menu to activate the picker
* Simply right-click the menu item and the picker will activate
Once you have the picker open, it should be quite straightforward! Move your mouse around the screen and click on the pixel you want.
Once you have the picker open, it should be quite straightforward! Move your mouse around the screen and click (or press <kbd>space</kbd> on the pixel you want.
You can also hold down the <kbd>control (⌃)</kbd> key (this can be changed to another key) while the picker is open, and the picker will enter "Focus Mode". This will zoom the pixel preview even further, and will slow down the picker's movement to make it easier to pick that hard-to-get pixel!
Remember, you can press <kbd>space</kbd> (or <kbd>shift</kbd> + <kbd>space</kbd>) to cycle between color formats while the picker is open.
**ProTip:** Remember, you can use the left or right arrow keys to cycle between color formats while the picker is open.
### Options
@@ -36,24 +38,26 @@ All of PixelPicker's options are in its menubar dropdown. Simply click on the me
PixelPicker provides some neat options:
* **Color Format**
* How should the color be copied when you select it? Examples include CSS Hex: `#aabbcc`, or Java RGB: `new Color(158, 198, 117)`, etc.
* **Focus Modifier**
* Choose which modifier activates focus mode. When focus mode is active, the picker zooms closer in, and your mouse moves slower to help you pick the right pixel.
* **Color Space**
* Computer screens are complex things, and have various different color spaces. You can either leave this setting as its default (it will choose the default color space of your screen) or you can specify a specific color space and PixelPicker will try and use that instead (compatibility depends on your computer's screen/monitor).
* **Show Grid**
* 🔳 **Show Grid**
* Whether or not to show a grid around the pixels in the picker's preview.
* **Float Precision**
* 🎨 **Color Space**
* Computer screens are complex things, and have various color spaces. You can either leave this setting as its default (it will choose the default color space of your screen) or you can specify a specific color space and PixelPicker will try and use that instead (compatibility depends on your computer's screen/monitor).
* ✏️ **Color Format**
* How should the color be copied when you select it? Examples include CSS Hex: `#aabbcc`, or Java RGB: `new Color(158, 198, 117)`, etc.
* 🔍 **Magnification**
- How far in should the preview zoom/how big should each pixel be?
* 🧐 **Focus Modifier**
* Choose which modifier activates focus mode. When focus mode is active, the picker zooms even closer in, and your mouse moves slower to help you pick the right pixel.
* 🔢 **Float Precision**
* This controls how many digits will come after the decimal point when using float-based color formats.
* **Picker Shortcut**
* ⌨️ **Picker Shortcut**
* The global keyboard shortcut to use that will activate Pixel Picker.
* **Launch at Login**
* 🚀 **Launch at Login**
* If this is enabled then PixelPicker will be launched when you log into your computer.
#### Extra Overrides or Hidden Settings
PixelPicker has some experimental overrides that can be toggled via the Terminal (`defaults write ...`, etc). Look under the "Experimental Overrides" section at `PixelPicker` -> `About` for more information.
In some releases PixelPicker might have some experimental overrides that can be toggled via the Terminal (`defaults write ...`, etc). Look under the "Experimental Overrides" section at `PixelPicker` -> `About` for more information.
## Questions/Concerns
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 354 KiB