diff options
| -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.pngBinary files differ new 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. | 
