diff options
| author | Jason Perkins | 2015-02-16 10:09:48 -0500 |
|---|---|---|
| committer | Jason Perkins | 2015-02-16 10:09:48 -0500 |
| commit | 86d5b1ae49105c1b0a789cbdac17dd7ce0da0479 (patch) | |
| tree | 58f9e363bd64e9a667e35b990655093481bbf64e | |
| parent | b564f5296a489d83d0007b8b21185c5b3326dbc8 (diff) | |
| parent | 3ea350cec127d7118ef64f0e84a9ad84fa249a11 (diff) | |
| download | MASShortcut-86d5b1ae49105c1b0a789cbdac17dd7ce0da0479.tar.bz2 | |
Merge branch 'master' into issue-47-accessibility
| -rw-r--r-- | CHANGES | 10 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 21 | ||||
| -rw-r--r-- | Demo/MainMenu.xib | 23 | ||||
| -rw-r--r-- | Demo/screenshot.png | bin | 0 -> 53006 bytes | |||
| -rw-r--r-- | Framework/Info.plist | 4 | ||||
| -rw-r--r-- | Framework/MASDictionaryTransformer.h | 22 | ||||
| -rw-r--r-- | Framework/MASHotKey.m | 2 | ||||
| -rw-r--r-- | Framework/MASHotKeyTests.m | 15 | ||||
| -rw-r--r-- | Framework/MASShortcut.h | 57 | ||||
| -rw-r--r-- | Framework/MASShortcut.m | 4 | ||||
| -rw-r--r-- | Framework/MASShortcutBinder.h | 58 | ||||
| -rw-r--r-- | Framework/MASShortcutMonitor.h | 16 | ||||
| -rw-r--r-- | Framework/MASShortcutMonitor.m | 11 | ||||
| -rw-r--r-- | Framework/MASShortcutMonitorTests.m | 23 | ||||
| -rw-r--r-- | Framework/MASShortcutView+Bindings.h | 20 | ||||
| -rw-r--r-- | Framework/MASShortcutView.m | 18 | ||||
| -rw-r--r-- | MASShortcut.podspec | 6 | ||||
| -rw-r--r-- | MASShortcut.xcodeproj/project.pbxproj | 12 | ||||
| -rw-r--r-- | README.md | 35 | ||||
| -rw-r--r-- | Spec.md | 12 |
20 files changed, 259 insertions, 110 deletions
@@ -1,4 +1,12 @@ -Unreleased yet +2.1.2 2015/1/28 + - Better key equivalent handling for non-ASCII layouts. + [Dmitry Obukhov] + +2.1.1 2015/1/16 + - Another headerdoc fix for CocoaDocs, hopefully the last one. + +2.1.0 2015/1/16 + - Added support for older OS X versions down to 10.6 included. - Headerdoc markup that plays better with CocoaDocs. 2.0.1 2015/1/9 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9f53769 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# How to Release a New Version + +First, update the version numbers. (MASShortcut uses [Semantic Versioning](http://semver.org/), so please read the docs if you’re not sure what the deal is.) The version number is stored in `Framework/Info.plist` and `MASShortcut.podspec` (twice in both files). + +Then update the `CHANGES` file. Add information about the new version (see the previous versions for an example) and add the release date. + +Now commit the changes: + + $ git commit -a -m "Version bump to x.y.z." + +And tag the last commit: + + $ git tag -a x.y.z + +Now push both the commits and tags (`--tags`) to GitHub and push the new podspec to CocoaPods: + + $ pod trunk push MASShortcut.podspec + +This will run sanity checks on the podspec and fail if the spec does not validate. + +That’s it. Go have a beer or a cup of tea to celebrate.
\ No newline at end of file diff --git a/Demo/MainMenu.xib b/Demo/MainMenu.xib index a04814d..a4bf180 100644 --- a/Demo/MainMenu.xib +++ b/Demo/MainMenu.xib @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/> @@ -655,19 +655,22 @@ <rect key="frame" x="0.0" y="0.0" width="393" height="129"/> <autoresizingMask key="autoresizingMask"/> <subviews> - <customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="536" customClass="MASShortcutView"> + <customView id="536" customClass="MASShortcutView"> <rect key="frame" x="142" y="90" width="158" height="19"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> </customView> - <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PG0-jh-Onh"> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="PG0-jh-Onh"> <rect key="frame" x="18" y="92" width="111" height="17"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Custom shortcut:" id="85u-1A-n7H"> <font key="font" metaFont="system"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> </textFieldCell> </textField> - <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zCi-ki-Uuh"> + <button id="zCi-ki-Uuh"> <rect key="frame" x="140" y="63" width="169" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <buttonCell key="cell" type="check" title="Enable custom shortcut" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Y47-p3-sDa"> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <font key="font" metaFont="system"/> @@ -676,16 +679,18 @@ <binding destination="rCO-Ve-DT5" name="value" keyPath="values.customShortcutEnabled" id="VjS-3V-VdA"/> </connections> </button> - <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KnS-ut-phz"> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="KnS-ut-phz"> <rect key="frame" x="18" y="65" width="111" height="17"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Options:" id="cUE-gA-heG"> <font key="font" metaFont="system"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> </textFieldCell> </textField> - <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F4r-KM-wn9"> + <button id="F4r-KM-wn9"> <rect key="frame" x="140" y="43" width="235" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <buttonCell key="cell" type="check" title="Enable hard-coded shortcut (⌘F2)" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="7gv-jN-44g"> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <font key="font" metaFont="system"/> @@ -694,16 +699,18 @@ <binding destination="rCO-Ve-DT5" name="value" keyPath="values.hardcodedShortcutEnabled" id="dlZ-si-HeN"/> </connections> </button> - <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9fB-XS-8pK"> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="9fB-XS-8pK"> <rect key="frame" x="18" y="20" width="111" height="17"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Feedback:" id="Zbz-mV-NDc"> <font key="font" metaFont="system"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> </textFieldCell> </textField> - <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Aso-dH-W8I"> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="Aso-dH-W8I"> <rect key="frame" x="140" y="20" width="211" height="17"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" placeholderString="Press a shortcut to see feedback" id="Ckx-v7-e6x"> <font key="font" metaFont="system"/> <color key="textColor" red="0.37647062540054321" green="0.85098046064376831" blue="0.17647059261798859" alpha="1" colorSpace="deviceRGB"/> diff --git a/Demo/screenshot.png b/Demo/screenshot.png Binary files differnew file mode 100644 index 0000000..926f4ca --- /dev/null +++ b/Demo/screenshot.png diff --git a/Framework/Info.plist b/Framework/Info.plist index 91a62a8..679ef34 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,9 +15,9 @@ <key>CFBundlePackageType</key> <string>FMWK</string> <key>CFBundleShortVersionString</key> - <string>2.0.0</string> + <string>2.1.2</string> <key>CFBundleVersion</key> - <string>2.0.0</string> + <string>2.1.2</string> <key>NSHumanReadableCopyright</key> <string>Copyright © 2014–2015 Vadim Shpakovski. All rights reserved.</string> </dict> diff --git a/Framework/MASDictionaryTransformer.h b/Framework/MASDictionaryTransformer.h index 8f8e084..6e53fd8 100644 --- a/Framework/MASDictionaryTransformer.h +++ b/Framework/MASDictionaryTransformer.h @@ -1,19 +1,19 @@ extern NSString *const MASDictionaryTransformerName; /** - Converts shortcuts for storage in user defaults. + Converts shortcuts for storage in user defaults. - User defaults can’t stored custom types directly, they have to - be serialized to `NSData` or some other supported type like an - `NSDictionary`. In Cocoa Bindings, the conversion can be done - using value transformers like this one. + User defaults can’t stored custom types directly, they have to + be serialized to `NSData` or some other supported type like an + `NSDictionary`. In Cocoa Bindings, the conversion can be done + using value transformers like this one. - There’s a built-in transformer (`NSKeyedUnarchiveFromDataTransformerName`) - that converts any `NSCoding` types to `NSData`, but with shortcuts - it makes sense to use a dictionary instead – the defaults look better - when inspected with the `defaults` command-line utility and the - format is compatible with an older sortcut library called Shortcut - Recorder. + There’s a built-in transformer (`NSKeyedUnarchiveFromDataTransformerName`) + that converts any `NSCoding` types to `NSData`, but with shortcuts + it makes sense to use a dictionary instead – the defaults look better + when inspected with the `defaults` command-line utility and the + format is compatible with an older sortcut library called Shortcut + Recorder. */ @interface MASDictionaryTransformer : NSValueTransformer @end diff --git a/Framework/MASHotKey.m b/Framework/MASHotKey.m index 7886440..c5ab744 100644 --- a/Framework/MASHotKey.m +++ b/Framework/MASHotKey.m @@ -19,7 +19,7 @@ FourCharCode const MASHotKeySignature = 'MASS'; EventHotKeyID hotKeyID = { .signature = MASHotKeySignature, .id = _carbonID }; OSStatus status = RegisterEventHotKey([shortcut carbonKeyCode], [shortcut carbonFlags], - hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &_hotKeyRef); + hotKeyID, GetEventDispatcherTarget(), 0, &_hotKeyRef); if (status != noErr) { return nil; diff --git a/Framework/MASHotKeyTests.m b/Framework/MASHotKeyTests.m new file mode 100644 index 0000000..65361ab --- /dev/null +++ b/Framework/MASHotKeyTests.m @@ -0,0 +1,15 @@ +#import "MASHotKey.h" + +@interface MASHotKeyTests : XCTestCase +@end + +@implementation MASHotKeyTests + +- (void) testBasicFunctionality +{ + MASHotKey *hotKey = [MASHotKey registeredHotKeyWithShortcut: + [MASShortcut shortcutWithKeyCode:kVK_ANSI_H modifierFlags:NSCommandKeyMask|NSAlternateKeyMask]]; + XCTAssertNotNil(hotKey, @"Register a simple Cmd-Alt-H hotkey."); +} + +@end diff --git a/Framework/MASShortcut.h b/Framework/MASShortcut.h index 8ba1b53..8f420e4 100644 --- a/Framework/MASShortcut.h +++ b/Framework/MASShortcut.h @@ -1,59 +1,70 @@ #import "MASKeyCodes.h" /** - A model class to hold a key combination. + A model class to hold a key combination. - This class just represents a combination of keys. It does not care if - the combination is valid or can be used as a hotkey, it doesn’t watch - the input system for the shortcut appearance, nor it does access user - defaults. + This class just represents a combination of keys. It does not care if + the combination is valid or can be used as a hotkey, it doesn’t watch + the input system for the shortcut appearance, nor it does access user + defaults. */ @interface MASShortcut : NSObject <NSSecureCoding, NSCopying> /** - The virtual key code for the keyboard key. + The virtual key code for the keyboard key. - Hardware independent, same as in `NSEvent`. See `Events.h` in the HIToolbox - framework for a complete list, or Command-click this symbol: `kVK_ANSI_A`. + Hardware independent, same as in `NSEvent`. See `Events.h` in the HIToolbox + framework for a complete list, or Command-click this symbol: `kVK_ANSI_A`. */ @property (nonatomic, readonly) NSUInteger keyCode; /** - Cocoa keyboard modifier flags. + Cocoa keyboard modifier flags. - Same as in `NSEvent`: `NSCommandKeyMask`, `NSAlternateKeyMask`, etc. + Same as in `NSEvent`: `NSCommandKeyMask`, `NSAlternateKeyMask`, etc. */ @property (nonatomic, readonly) NSUInteger modifierFlags; /** - Same as `keyCode`, just a different type. + Same as `keyCode`, just a different type. */ @property (nonatomic, readonly) UInt32 carbonKeyCode; /** - Carbon modifier flags. + Carbon modifier flags. - A bit sum of `cmdKey`, `optionKey`, etc. + A bit sum of `cmdKey`, `optionKey`, etc. */ @property (nonatomic, readonly) UInt32 carbonFlags; /** - A string representing the “key” part of a shortcut, like the `5` in `⌘5`. + A string representing the “key” part of a shortcut, like the `5` in `⌘5`. + + @warning The value may change depending on the active keyboard layout. + For example for the `^2` keyboard shortcut (`kVK_ANSI_2+NSControlKeyMask` + to be precise) the `keyCodeString` is `2` on the US keyboard, but `ě` when + the Czech keyboard layout is active. See the spec for details. */ @property (nonatomic, readonly) NSString *keyCodeString; /** - A key-code string used in key equivalent matching. - - For precise meaning of “key equivalents” see the `keyEquivalent` - property of `NSMenuItem`. Here the string is used to support shortcut - validation (“is the shortcut already taken in this menu?”) and - for display in `NSMenu`. + A key-code string used in key equivalent matching. + + For precise meaning of “key equivalents” see the `keyEquivalent` + property of `NSMenuItem`. Here the string is used to support shortcut + validation (“is the shortcut already taken in this menu?”) and + for display in `NSMenu`. + + The value of this property may differ from `keyCodeString`. For example + the Russian keyboard has a `Г` (Ge) Cyrillic character in place of the + latin `U` key. This means you can create a `^Г` shortcut, but in menus + that’s always displayed as `^U`. So the `keyCodeString` returns `Г` + and `keyCodeStringForKeyEquivalent` returns `U`. */ @property (nonatomic, readonly) NSString *keyCodeStringForKeyEquivalent; /** - A string representing the shortcut modifiers, like the `⌘` in `⌘5`. + A string representing the shortcut modifiers, like the `⌘` in `⌘5`. */ @property (nonatomic, readonly) NSString *modifierFlagsString; @@ -61,9 +72,9 @@ + (instancetype)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags; /** - Creates a new shortcut from an `NSEvent` object. + Creates a new shortcut from an `NSEvent` object. - This is just a convenience initializer that reads the key code and modifiers from an `NSEvent`. + This is just a convenience initializer that reads the key code and modifiers from an `NSEvent`. */ + (instancetype)shortcutWithEvent:(NSEvent *)anEvent; diff --git a/Framework/MASShortcut.m b/Framework/MASShortcut.m index e6fa63d..ef3385d 100644 --- a/Framework/MASShortcut.m +++ b/Framework/MASShortcut.m @@ -139,10 +139,10 @@ static NSString *const MASShortcutModifierFlags = @"ModifierFlags"; case 115: return NSStringFromMASKeyCode(kMASShortcutGlyphNorthwestArrow); } - // Everything else should be printable so look it up in the current keyboard + // Everything else should be printable so look it up in the current ASCII capable keyboard layout OSStatus error = noErr; NSString *keystroke = nil; - TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource(); + TISInputSourceRef inputSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (inputSource) { CFDataRef layoutDataRef = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); if (layoutDataRef) { diff --git a/Framework/MASShortcutBinder.h b/Framework/MASShortcutBinder.h index 1e65e0d..e7406de 100644 --- a/Framework/MASShortcutBinder.h +++ b/Framework/MASShortcutBinder.h @@ -1,66 +1,66 @@ #import "MASShortcutMonitor.h" /** - Binds actions to user defaults keys. + Binds actions to user defaults keys. - If you store shortcuts in user defaults (for example by binding - a `MASShortcutView` to user defaults), you can use this class to - connect an action directly to a user defaults key. If the shortcut - stored under the key changes, the action will get automatically - updated to the new one. + If you store shortcuts in user defaults (for example by binding + a `MASShortcutView` to user defaults), you can use this class to + connect an action directly to a user defaults key. If the shortcut + stored under the key changes, the action will get automatically + updated to the new one. - This class is mostly a wrapper around a `MASShortcutMonitor`. It - watches the changes in user defaults and updates the shortcut monitor - accordingly with the new shortcuts. + This class is mostly a wrapper around a `MASShortcutMonitor`. It + watches the changes in user defaults and updates the shortcut monitor + accordingly with the new shortcuts. */ @interface MASShortcutBinder : NSObject /** - A convenience shared instance. + A convenience shared instance. - You may use it so that you don’t have to manage an instance by hand, - but it’s perfectly fine to allocate and use a separate instance instead. + You may use it so that you don’t have to manage an instance by hand, + but it’s perfectly fine to allocate and use a separate instance instead. */ + (instancetype) sharedBinder; /** - The underlying shortcut monitor. + The underlying shortcut monitor. */ @property(strong) MASShortcutMonitor *shortcutMonitor; /** - Binding options customizing the access to user defaults. + Binding options customizing the access to user defaults. - As an example, you can use `NSValueTransformerNameBindingOption` to customize - the storage format used for the shortcuts. By default the shortcuts are converted - from `NSData` (`NSKeyedUnarchiveFromDataTransformerName`). Note that if the - binder is to work with `MASShortcutView`, both object have to use the same storage - format. + As an example, you can use `NSValueTransformerNameBindingOption` to customize + the storage format used for the shortcuts. By default the shortcuts are converted + from `NSData` (`NSKeyedUnarchiveFromDataTransformerName`). Note that if the + binder is to work with `MASShortcutView`, both object have to use the same storage + format. */ @property(copy) NSDictionary *bindingOptions; /** - Binds given action to a shortcut stored under the given defaults key. + Binds given action to a shortcut stored under the given defaults key. - In other words, no matter what shortcut you store under the given key, - pressing it will always trigger the given action. + In other words, no matter what shortcut you store under the given key, + pressing it will always trigger the given action. */ - (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action; /** - Disconnect the binding between user defaults and action. + Disconnect the binding between user defaults and action. - In other words, the shortcut stored under the given key will no longer trigger an action. + In other words, the shortcut stored under the given key will no longer trigger an action. */ - (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName; /** - Register default shortcuts in user defaults. + Register default shortcuts in user defaults. - This is a convenience frontent to `[NSUserDefaults registerDefaults]`. - The dictionary should contain a map of user defaults’ keys to appropriate - keyboard shortcuts. The shortcuts will be transformed according to - `bindingOptions` and registered using `registerDefaults`. + This is a convenience frontent to `[NSUserDefaults registerDefaults]`. + The dictionary should contain a map of user defaults’ keys to appropriate + keyboard shortcuts. The shortcuts will be transformed according to + `bindingOptions` and registered using `registerDefaults`. */ - (void) registerDefaultShortcuts: (NSDictionary*) defaultShortcuts; diff --git a/Framework/MASShortcutMonitor.h b/Framework/MASShortcutMonitor.h index 69affc1..dc3d458 100644 --- a/Framework/MASShortcutMonitor.h +++ b/Framework/MASShortcutMonitor.h @@ -1,11 +1,11 @@ #import "MASShortcut.h" /** - Executes action when a shortcut is pressed. + Executes action when a shortcut is pressed. - There can only be one instance of this class, otherwise things - will probably not work. (There’s a Carbon event handler inside - and there can only be one Carbon event handler of a given type.) + There can only be one instance of this class, otherwise things + will probably not work. (There’s a Carbon event handler inside + and there can only be one Carbon event handler of a given type.) */ @interface MASShortcutMonitor : NSObject @@ -13,12 +13,12 @@ + (instancetype) sharedMonitor; /** - Register a shortcut along with an action. + Register a shortcut along with an action. - Attempting to insert an already registered shortcut probably won’t work. - It may burn your house or cut your fingers. You have been warned. + Attempting to insert an already registered shortcut probably won’t work. + It may burn your house or cut your fingers. You have been warned. */ -- (void) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action; +- (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action; - (BOOL) isShortcutRegistered: (MASShortcut*) shortcut; - (void) unregisterShortcut: (MASShortcut*) shortcut; diff --git a/Framework/MASShortcutMonitor.m b/Framework/MASShortcutMonitor.m index 099f4b1..fce8022 100644 --- a/Framework/MASShortcutMonitor.m +++ b/Framework/MASShortcutMonitor.m @@ -45,11 +45,16 @@ static OSStatus MASCarbonEventCallback(EventHandlerCallRef, EventRef, void*); #pragma mark Registration -- (void) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action +- (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action { MASHotKey *hotKey = [MASHotKey registeredHotKeyWithShortcut:shortcut]; - [hotKey setAction:action]; - [_hotKeys setObject:hotKey forKey:shortcut]; + if (hotKey) { + [hotKey setAction:action]; + [_hotKeys setObject:hotKey forKey:shortcut]; + return YES; + } else { + return NO; + } } - (void) unregisterShortcut: (MASShortcut*) shortcut diff --git a/Framework/MASShortcutMonitorTests.m b/Framework/MASShortcutMonitorTests.m new file mode 100644 index 0000000..ccdcaef --- /dev/null +++ b/Framework/MASShortcutMonitorTests.m @@ -0,0 +1,23 @@ +#import "MASShortcutMonitor.h" + +@interface MASShortcutMonitorTests : XCTestCase +@end + +@implementation MASShortcutMonitorTests + +- (void) testMonitorCreation +{ + XCTAssertNotNil([MASShortcutMonitor sharedMonitor], @"Create a shared shortcut monitor."); +} + +- (void) testShortcutRegistration +{ + MASShortcutMonitor *monitor = [MASShortcutMonitor sharedMonitor]; + MASShortcut *shortcut = [MASShortcut shortcutWithKeyCode:kVK_ANSI_H modifierFlags:NSCommandKeyMask|NSAlternateKeyMask]; + XCTAssertTrue([monitor registerShortcut:shortcut withAction:NULL], @"Register a shortcut."); + XCTAssertTrue([monitor isShortcutRegistered:shortcut], @"Remember a previously registered shortcut."); + [monitor unregisterShortcut:shortcut]; + XCTAssertFalse([monitor isShortcutRegistered:shortcut], @"Forget shortcut after unregistering."); +} + +@end diff --git a/Framework/MASShortcutView+Bindings.h b/Framework/MASShortcutView+Bindings.h index b0148e7..01b2246 100644 --- a/Framework/MASShortcutView+Bindings.h +++ b/Framework/MASShortcutView+Bindings.h @@ -1,19 +1,19 @@ #import "MASShortcutView.h" /** - @brief A simplified interface to bind the recorder value to user defaults. + A simplified interface to bind the recorder value to user defaults. - You can bind the @p shortcutValue to user defaults using the standard - @p bind:toObject:withKeyPath:options: call, but since that’s a lot to type - and read, here’s a simpler option. + You can bind the `shortcutValue` to user defaults using the standard + `bind:toObject:withKeyPath:options:` call, but since that’s a lot to type + and read, here’s a simpler option. - Setting the @p associatedUserDefaultsKey binds the view’s shortcut value - to the given user defaults key. You can supply a value transformer to convert - values between user defaults and @p MASShortcut. If you don’t supply - a transformer, the @p NSUnarchiveFromDataTransformerName will be used - automatically. + Setting the `associatedUserDefaultsKey` binds the view’s shortcut value + to the given user defaults key. You can supply a value transformer to convert + values between user defaults and `MASShortcut`. If you don’t supply + a transformer, the `NSUnarchiveFromDataTransformerName` will be used + automatically. - Set @p associatedUserDefaultsKey to @p nil to disconnect the binding. + Set `associatedUserDefaultsKey` to `nil` to disconnect the binding. */ @interface MASShortcutView (Bindings) diff --git a/Framework/MASShortcutView.m b/Framework/MASShortcutView.m index dfeeac6..4132ce2 100644 --- a/Framework/MASShortcutView.m +++ b/Framework/MASShortcutView.m @@ -13,6 +13,7 @@ NSString *const MASShortcutBinding = @"shortcutValue"; @property (nonatomic, getter = isHinting) BOOL hinting; @property (nonatomic, copy) NSString *shortcutPlaceholder; +@property (nonatomic, assign) BOOL showsDeleteButton; @end @@ -57,6 +58,7 @@ NSString *const MASShortcutBinding = @"shortcutValue"; _shortcutCell.font = [[NSFontManager sharedFontManager] convertFont:_shortcutCell.font toSize:BUTTON_FONT_SIZE]; _shortcutValidator = [MASShortcutValidator sharedValidator]; _enabled = YES; + _showsDeleteButton = YES; [self resetShortcutCellStyle]; } @@ -198,9 +200,15 @@ NSString *const MASShortcutBinding = @"shortcutValue"; - (void)drawRect:(CGRect)dirtyRect { if (self.shortcutValue) { - [self drawInRect:self.bounds withTitle:NSStringFromMASKeyCode(self.recording ? kMASShortcutGlyphEscape : kMASShortcutGlyphDeleteLeft) - alignment:NSRightTextAlignment state:NSOffState]; - + NSString *buttonTitle; + if (self.recording) { + buttonTitle = NSStringFromMASKeyCode(kMASShortcutGlyphEscape); + } else if (self.showsDeleteButton) { + buttonTitle = NSStringFromMASKeyCode(kMASShortcutGlyphClear); + } + if (buttonTitle != nil) { + [self drawInRect:self.bounds withTitle:buttonTitle alignment:NSRightTextAlignment state:NSOffState]; + } CGRect shortcutRect; [self getShortcutRect:&shortcutRect hintRect:NULL]; NSString *title = (self.recording @@ -379,7 +387,7 @@ void *kUserDataHint = &kUserDataHint; static id eventMonitor = nil; if (shouldActivate) { - __weak MASShortcutView *weakSelf = self; + __unsafe_unretained MASShortcutView *weakSelf = self; NSEventMask eventMask = (NSKeyDownMask | NSFlagsChangedMask); eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) { @@ -460,7 +468,7 @@ void *kUserDataHint = &kUserDataHint; static id observer = nil; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; if (shouldActivate) { - __weak MASShortcutView *weakSelf = self; + __unsafe_unretained MASShortcutView *weakSelf = self; observer = [notificationCenter addObserverForName:NSWindowDidResignKeyNotification object:self.window queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { weakSelf.recording = NO; diff --git a/MASShortcut.podspec b/MASShortcut.podspec index 8cf824f..fb89e5a 100644 --- a/MASShortcut.podspec +++ b/MASShortcut.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'MASShortcut' - s.version = '2.0.1' + s.version = '2.1.2' s.summary = 'Modern framework for managing global keyboard shortcuts compatible with Mac App Store' s.homepage = 'https://github.com/shpakovski/MASShortcut' s.license = 'BSD 2-clause' @@ -8,8 +8,8 @@ Pod::Spec.new do |s| 'Tomáš Znamenáček' => 'tomas.znamenacek@gmail.com' } s.platform = :osx - s.osx.deployment_target = "10.7" - s.source = { :git => 'https://github.com/shpakovski/MASShortcut.git', :tag => '2.0.1' } + s.osx.deployment_target = "10.6" + s.source = { :git => 'https://github.com/shpakovski/MASShortcut.git', :tag => '2.1.2' } s.source_files = 'Framework/*.{h,m}' s.exclude_files = 'Framework/*Tests.m' s.osx.frameworks = 'Carbon', 'AppKit' diff --git a/MASShortcut.xcodeproj/project.pbxproj b/MASShortcut.xcodeproj/project.pbxproj index 2ab08a8..ea5125c 100644 --- a/MASShortcut.xcodeproj/project.pbxproj +++ b/MASShortcut.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 0D39DCA21A668A4400639145 /* MASHotKeyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D39DCA11A668A4400639145 /* MASHotKeyTests.m */; }; + 0D39DCA41A668E5500639145 /* MASShortcutMonitorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D39DCA31A668E5500639145 /* MASShortcutMonitorTests.m */; }; 0D827CD71990D4420010B8EF /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D827CD61990D4420010B8EF /* Cocoa.framework */; }; 0D827D251990D55E0010B8EF /* MASShortcut.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D827D1B1990D55E0010B8EF /* MASShortcut.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0D827D261990D55E0010B8EF /* MASShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827D1C1990D55E0010B8EF /* MASShortcut.m */; }; @@ -64,6 +66,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0D39DCA11A668A4400639145 /* MASHotKeyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASHotKeyTests.m; path = Framework/MASHotKeyTests.m; sourceTree = "<group>"; }; + 0D39DCA31A668E5500639145 /* MASShortcutMonitorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MASShortcutMonitorTests.m; path = Framework/MASShortcutMonitorTests.m; sourceTree = "<group>"; }; 0D827CD31990D4420010B8EF /* MASShortcut.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MASShortcut.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0D827CD61990D4420010B8EF /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 0D827CD91990D4420010B8EF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -231,8 +235,10 @@ children = ( 0DC2F17419922798003A0131 /* MASHotKey.h */, 0DC2F17519922798003A0131 /* MASHotKey.m */, + 0D39DCA11A668A4400639145 /* MASHotKeyTests.m */, 0D827DA319912D240010B8EF /* MASShortcutMonitor.h */, 0D827DA419912D240010B8EF /* MASShortcutMonitor.m */, + 0D39DCA31A668E5500639145 /* MASShortcutMonitorTests.m */, ); name = Monitoring; sourceTree = "<group>"; @@ -419,6 +425,8 @@ 0DC2F190199372B4003A0131 /* MASDictionaryTransformerTests.m in Sources */, 0D827D9419910B740010B8EF /* MASShortcutTests.m in Sources */, 0DC2F18919925F8F003A0131 /* MASShortcutBinderTests.m in Sources */, + 0D39DCA21A668A4400639145 /* MASHotKeyTests.m in Sources */, + 0D39DCA41A668E5500639145 /* MASShortcutMonitorTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -516,6 +524,7 @@ GCC_PREFIX_HEADER = Framework/Prefix.pch; INFOPLIST_FILE = Framework/Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.6; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -533,6 +542,7 @@ GCC_PREFIX_HEADER = Framework/Prefix.pch; INFOPLIST_FILE = Framework/Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.6; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -551,6 +561,7 @@ "$(inherited)", ); INFOPLIST_FILE = Demo/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 10.6; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -564,6 +575,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Demo/Prefix.pch; INFOPLIST_FILE = Demo/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 10.6; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -4,7 +4,27 @@ Some time ago Cocoa developers used a brilliant framework [ShortcutRecorder](http://wafflesoftware.net/shortcut/) for managing keyboard shortcuts in application preferences. However, it became incompatible with the new plugin architecture of Xcode 4. -The MASShortcut project introduces a modern API and user interface for recording, storing and using system-wide keyboard shortcuts. All code is compatible with recent Xcode & OS X versions and the sandboxed environment. +The MASShortcut project introduces a modern API and user interface for recording, storing and using system-wide keyboard shortcuts. + + + +Features: + +* Record and display keyboard shortcuts +* Watch for shortcuts and execute actions, system-wide +* A nice, [documented API](http://cocoadocs.org/docsets/MASShortcut/) +* Can be configured to be compatible with Shortcut Recorder +* Can be installed both through CocoaPods and as a Git submodule +* Mac App Store friendly +* Works on OS X 10.6 and up +* Hacking-friendly codebase covered with tests + +Important features currently missing: + +* Localisation +* Accessibility + +Pull requests welcome :) # Installation @@ -16,6 +36,8 @@ If you want to stick to the 1.x branch, you can use the version smart match oper pod 'MASShortcut', '~> 1' +Or can use Git submodules and link against the MASShortcut framework. + # Usage I hope, it is really easy: @@ -95,9 +117,16 @@ _observableKeyPath = [@"values." stringByAppendingString:kPreferenceGlobalShortc context:kGlobalShortcutContext]; ``` -# Non-ARC Version +# Using in Swift projects + + 1. Install as a Pod using the latest CocoaPods with Swift support. + 2. Create a bridging header file [using the instructions here](http://swiftalicio.us/2014/11/using-cocoapods-from-swift/) + 3. Your bridging header file should contain the following [two](https://github.com/shpakovski/MASShortcut/issues/36) imports: -If you like retain/release, please check out these forks: [heardrwt/MASShortcut](https://github.com/heardrwt/MASShortcut) and [chendo/MASShortcut](https://github.com/chendo/MASShortcut). However, the preferred way is to enable the `-fobjc-arc` in Xcode source options. +```objective-c +#import <Cocoa/Cocoa.h> +#import <MASShortcut/Shortcut.h> +``` # Copyright @@ -12,4 +12,14 @@ Please stay high-level when writing the spec, do not document particular classes * If the shortcut is Cmd-W or Cmd-Q, the recording must be cancelled and the keypress passed through to the system, closing the window or quitting the app. * If a shortcut is already taken by system and is enabled, it must be rejected. (Examples: Cmd-S, Cmd-N. TBD: What exactly does it mean that the shortcut is “enabled”?) * TBD: Option-key handling. -* All other shortcuts must be accepted. (Examples: Ctrl-Esc, Cmd-Delete, F16.)
\ No newline at end of file +* All other shortcuts must be accepted. (Examples: Ctrl-Esc, Cmd-Delete, F16.) + +# Formatting Shortcuts + +On different keyboard layouts (such as US and Czech), a single shortcut (a combination of physical keys) may be formatted into different strings. + +For example, the default system shortcut for toggling directly to Space #2 is Control–2. But when you switch to the Czech keyboard layout, the physical key with the `2` label now inserts the `ě` character. Thus, on most keyboard layouts the shortcut for toggling to Space #2 is called `^2`, but on the Czech layout it’s called `^ě`. (I stress that this is the same combination of hardware keys and the same `MASShortcut` instance.) + +This is reflected by the system: When you open the System Preferences → Keyboard → Shortcuts pane, the shortcuts displayed depend on the currently selected keyboard layout (try switching between the US and Czech keyboard layouts and reopening the preference pane). + +This means that the identity of a shortcut is given by its key code and modifiers (such as `kVK_ANSI_2` and `NSControlKeyMask`), not the `keyCodeString` returned by the `MASShortcut` class. This string may change depending on the current keyboard layout: `^2` with the US keyboard active, but `^ě` with the Czech keyboard active. |
