├── Assets ├── btn_release.svg ├── btn_sketchpacks.svg ├── btn_support.svg └── img_cover.png ├── LICENSE ├── README.md └── Source ├── Assets ├── Assets.xcassets │ ├── Contents.json │ ├── SketchAPITestFile.dataset │ │ ├── Contents.json │ │ └── SketchAPITestFile.sketch │ ├── disclosure-horizontal.imageset │ │ ├── Contents.json │ │ └── disclosure-horizontal.pdf │ ├── disclosure-vertical.imageset │ │ ├── Contents.json │ │ └── disclosure-vertical.pdf │ ├── prokeys-bg.imageset │ │ ├── Contents.json │ │ └── Group 2.pdf │ └── shortcut-menu.imageset │ │ ├── Contents.json │ │ └── shortcut-menu.pdf ├── KeysIcon.icns └── SketchAPITestFile.sketch ├── Controller ├── ActionsMenu.swift ├── DonationButton.swift ├── Licensing Window │ ├── LicensingBGImageView.swift │ ├── LicensingInfoTextFieldCell.swift │ ├── LicensingWindowController.swift │ ├── LicensingWindowController.xib │ └── UI │ │ └── LicensingWindowController.swift ├── Outline View │ ├── Cell │ │ ├── ItemCellContextualMenu.swift │ │ ├── ItemTableCellView.swift │ │ └── Shortcut View │ │ │ ├── MASShortcut+Additions.swift │ │ │ ├── ShortcutButtonCell.swift │ │ │ ├── ShortcutErrorAlert.swift │ │ │ ├── ShortcutErrorHandler.swift │ │ │ ├── ShortcutValidator.swift │ │ │ └── ShortcutView.swift │ ├── OutlineView.swift │ ├── OutlineViewDataSource.swift │ └── OutlineViewDelegate.swift ├── SearchField.swift ├── ViewController.swift └── ViewController.xib ├── Info.plist ├── Keys.swift ├── KeysForSketch.h ├── KeysForSketch.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── exevil.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── exevil.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── KeysForSketch.xcscheme │ └── xcschememanagement.plist ├── KeysForSketch.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── exevil.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ ├── WorkspaceSettings.xcsettings │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── KeysForSketch ├── Assets │ └── Assets.xcassets │ │ ├── disclosure-horizontal.imageset │ │ ├── Contents.json │ │ └── disclosure-horizontal.pdf │ │ └── disclosure-vertical.imageset │ │ ├── Contents.json │ │ └── disclosure-vertical.pdf ├── KeysForSketch.bundle │ └── Contents │ │ ├── Frameworks │ │ ├── MASShortcut.framework │ │ │ ├── MASShortcut │ │ │ ├── Resources │ │ │ │ └── Info.plist │ │ │ └── Versions │ │ │ │ └── A │ │ │ │ └── MASShortcut │ │ ├── libswiftCoreFoundation.dylib │ │ ├── libswiftCoreGraphics.dylib │ │ ├── libswiftCoreImage.dylib │ │ ├── libswiftFoundation.dylib │ │ ├── libswiftObjectiveC.dylib │ │ ├── libswiftQuartzCore.dylib │ │ └── libswiftSwiftOnoneSupport.dylib │ │ └── Resources │ │ └── libswiftRemoteMirror.dylib ├── KeysForSketch.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── KeysForSketch.xcworkspace │ ├── xcshareddata │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── exevil.xcuserdatad │ │ └── WorkspaceSettings.xcsettings ├── Pods │ ├── Pods.xcodeproj │ │ └── xcuserdata │ │ │ └── exevil.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── MASShortcut.xcscheme │ │ │ ├── Pods-KeysForSketch.xcscheme │ │ │ └── xcschememanagement.plist │ └── Target Support Files │ │ ├── MASShortcut │ │ └── ResourceBundle-MASShortcut-Info.plist │ │ ├── Pods-KeysForSketch │ │ ├── Pods-KeysForSketch-acknowledgements.plist │ │ ├── Pods-KeysForSketch-dummy.m │ │ ├── Pods-KeysForSketch-frameworks.sh │ │ ├── Pods-KeysForSketch-resources.sh │ │ ├── Pods-KeysForSketch-umbrella.h │ │ ├── Pods-KeysForSketch.debug.xcconfig │ │ ├── Pods-KeysForSketch.modulemap │ │ └── Pods-KeysForSketch.release.xcconfig │ │ └── Pods-KeysForSketchUnitTests │ │ └── Info.plist ├── Sketch API │ └── Protocols │ │ ├── VDKSketchMSPluginManagerWithActionsProtocol.h │ │ └── VDKSketchMSPreferencesControllerProtocol.h └── html │ ├── Pods_Target Support Files_MASShortcut_MASShortcut-dummy.m.html │ ├── Pods_Target Support Files_Pods-KeysForSketch_Pods-KeysForSketch-dummy.m.html │ ├── Sketch API_Protocols_Updater_VDKSketchSUAppcastItemProtocol.h.html │ ├── Sketch API_Protocols_Updater_VDKSketchSUHostProtocol.h.html │ ├── Sketch API_Protocols_Updater_VDKSketchSUUpdaterDelegate.h.html │ ├── Sketch API_Protocols_Updater_VDKSketchSUUpdaterProtocol.h.html │ ├── Sketch API_Protocols_VDKSketchMSDocumentControllerProtocol.h.html │ ├── Sketch API_Protocols_VDKSketchMSPluginBundleProtocol.h.html │ ├── Sketch API_Protocols_VDKSketchMSPluginManagerWithActionsProtocol.h.html │ └── Sketch API_Protocols_VDKSketchMSPreferencesControllerProtocol.h.html ├── KeysForSketchTests ├── Info.plist └── KeysForSketchTests.swift ├── Menu ├── MenuObserver.swift ├── NSMenu+Additions.swift ├── NSMenuItem+Additions.swift ├── NSMenuItem+Private.h └── ToolsManager.swift ├── Plugin ├── manifest.json └── script.js ├── ProKeys ├── Keys │ ├── ProKeyAspectRatioSwitcher.swift │ └── ProKeyProtocol.swift └── ProKeysManager.swift ├── Sketch API ├── Protocols │ ├── Document │ │ ├── Inspector │ │ │ ├── VDKSketchMSInspectorControllerProtocol.h │ │ │ ├── VDKSketchMSLayerInspectorViewControllerProtocol.h │ │ │ ├── VDKSketchMSNormalInspectorProtocol.h │ │ │ └── VDKSketchMSStandardInspectorViewControllersProtocol.h │ │ ├── VDKSketchMSActionControllerProtocol.h │ │ ├── VDKSketchMSDocumentControllerProtocol.h │ │ ├── VDKSketchMSDocumentDataProtocol.h │ │ └── VDKSketchMSDocumentProtocol.h │ ├── Layers │ │ ├── VDKSketchMSArtboardGroupProtocol.h │ │ ├── VDKSketchMSLayerArrayProtocol.h │ │ ├── VDKSketchMSLayerGroupProtocol.h │ │ ├── VDKSketchMSLayerProtocol.h │ │ └── VDKSketchMSPageProtocol.h │ ├── VDKSketchAppControllerProtocol.h │ ├── VDKSketchMSKeyBindingsProtocol.h │ └── VDKSketchMSPreferencesControllerProtocol.h ├── SketchAPIIntegrationTest.swift ├── SketchAPIProtocolsTest.swift ├── SketchAPITools.swift ├── VDKSketchAPI.h └── VDKSketchAPI.m ├── Support ├── Const.swift ├── FileWatch.swift ├── Helpers.swift ├── JRSwizzle │ ├── VDK_JRSwizzle.h │ └── VDK_JRSwizzle.m ├── KeyCodeTransformer.swift ├── Lorgnette │ ├── lorgnette-structs.h │ ├── lorgnette.c │ └── lorgnette.h ├── MASShortcut.framework │ ├── Headers │ ├── MASShortcut │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Headers │ │ │ ├── MASDictionaryTransformer.h │ │ │ ├── MASHotKey.h │ │ │ ├── MASKeyCodes.h │ │ │ ├── MASKeyMasks.h │ │ │ ├── MASLocalization.h │ │ │ ├── MASShortcut-umbrella.h │ │ │ ├── MASShortcut.h │ │ │ ├── MASShortcutBinder.h │ │ │ ├── MASShortcutMonitor.h │ │ │ ├── MASShortcutValidator.h │ │ │ ├── MASShortcutView+Bindings.h │ │ │ ├── MASShortcutView.h │ │ │ └── Shortcut.h │ │ ├── MASShortcut │ │ ├── Modules │ │ │ └── module.modulemap │ │ └── Resources │ │ │ ├── Info.plist │ │ │ └── MASShortcut.bundle │ │ │ └── Contents │ │ │ ├── Info.plist │ │ │ ├── Resources │ │ │ ├── cs.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── de.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── en.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── es.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── fr.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── it.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── ja.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── ko.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── nl.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── pl.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── ru.lproj │ │ │ │ └── Localizable.strings │ │ │ ├── zh-Hans.lproj │ │ │ │ └── Localizable.strings │ │ │ └── zh-Hant.lproj │ │ │ │ └── Localizable.strings │ │ │ └── _CodeSignature │ │ │ ├── CodeDirectory │ │ │ ├── CodeRequirements │ │ │ ├── CodeRequirements-1 │ │ │ ├── CodeResources │ │ │ └── CodeSignature │ │ └── Current ├── NSAlert+Additions.swift └── UIInjector.swift ├── Update ├── FileObserver.swift └── UpdateCompletionAlert.swift └── VDKBridgingHeader.h /Assets/img_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Assets/img_cover.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vyacheslav Dubovitsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Attention! Plugin Support Has Been Discontinued 2 | Since I don't have much time for further plugin developement and new Sketch releases produce bugs pretty often I decided to discontinue the project support. Sorry. 3 | 4 | Update: All sources are now available in this repo. Enjoy! 5 | 6 | --- 7 | 8 | ![](./Assets/img_cover.png) 9 | 10 |
11 | 12 | 13 | # Keys For Sketch 14 | 15 |
16 | Keys is a full-featured shortcut manager for Sketch. Integrated directly to Preferences window it helps you to customize shortcuts easily. 17 |
18 |
19 | 20 | 1. [Installation](#installation) 21 | 1. [Features](#features) 22 | 1. [Usage](#usage) 23 | 1. [FAQ](#faq) 24 | 1. [Support Project](#support-project) 25 | 26 | ## Installation 27 | Keys installation is way the same as any other plugins. Just download latest release and double-click it. 28 | 29 | [](https://github.com/exevil/Keys-For-Sketch/releases/latest) [](https://sketchpacks.com/exevil/Keys-For-Sketch/install) 30 | 31 | ## Features 32 | * Easily menu shortcuts customization 33 | * Single-character shortcuts customization (like Pencil or Vector) 34 | * Shortcut conflicts resolving 35 | * Any third-party plugins support 36 | * Preferences window integration 37 | * Intuitive lightning-fast UI 38 | * Instant menu search 39 | * Modern Sketch v45 plugin updating system support 40 | 41 | ## Usage 42 | 1. Open Sketch Preferences (⌘+,) 43 | 1. Select `Keys` tab 44 | 1. Choose menu item for shortcut redefinition 45 | 1. Click on its shortcut or on `...` on the right side 46 | 1. Press new short 47 | 1. Resolve conflict if needed 48 | 1. Enjoy 49 | 50 | ## FAQ 51 | #### — There is a Keys-related bug in latest beta version of Sketch. What should I do? 52 | The only you can do is check [the issues list](https://github.com/exevil/Keys-For-Sketch/issues) and report your issue if no-one did it before. Right now Keys supports only the latest stable Sketch version, so you should expect an update right after its release. 53 | 54 | #### — Where can I see the changelog? 55 | On the [Releases](https://github.com/exevil/Keys-For-Sketch/releases) page. 56 | 57 | #### — How to restore default Sketch shortcuts? 58 | Use «Restore Default Shortcuts...» command from plugin menu. *It should remove any user shortcut data includinge one that defined directly in System Preferences.* 59 | 60 | #### — How Keys will affect custom shortcuts I defined earlier in System Preferences? 61 | Since Keys is using default system storage for shortcuts it shouldn't affect previously set shortcuts without additional user actions like «Restore Default Shortcuts...» command from plugin menu. 62 | 63 | #### — Can't see my Keys shortcuts in System Preferences. Is it something wrong? 64 | Since System Preferences caches shortcut values from storage once upon a startup you need to completely relaunch it to get updated shortcut data there. 65 | 66 | #### — What happens with my custom shortcuts if I remove Keys? 67 | Nothing, because all your shortcuts were defined in System Preferences. 68 | 69 | #### — I removed Keys and my custom shortcuts manually from System Preferences, but tools with single-character shortcuts (like Pencil or Vector) are still using previously defined values. How to reset it to defaults too? 70 | Since Sketch manages single character shortcuts by itself, you should delete `keyBindings.plist` from `~/Library/Application Support/com.bohemiancoding.sketch3/` folder and restart Sketch to return default tools shortcuts. 71 | 72 | #### — Some menu items aren't shown in Keys. How to fix? 73 | Internally Sketch may not properly draw some menu items prior to their first appearance in menu, that's why the only way to make these kind of menu items configurable with Keys is to manually access it through the app menu first. 74 | 75 | #### — Keys conflicts with other plugins that using earlier Swift versions. Is there any way to fix it? 76 | Unfortunately, there isn't. In simple terms, there's no capability between different Swift versions right now so it may produce an errors at runtime. 77 | 78 | But it's not really a big deal. Firstly, the Swift team has already announced that ABI capability feature is planned for the next major Swift release and this is the first priority right now. Secondly, plugins can't conflict each other while the latest available Swift version is used for all of them and requesting author for an update may be a good temporary solution for this problem. 79 | 80 | #### — Why I can't remove default shortcut from a menu item? 81 | Since Keys plugin is wrapping a default System Preferences shortcut assignment flow it follows the same rules. So any default shortcut are constant until you either replace it with your own or use the same combination for another menu item. 82 | 83 | #### — Why it's not possible to assign a single-character shortcut to menu item other than specific items under the insert menu? 84 | Since Sketch itself is using an internal predefined list of menu items that allowed a single-character shortcut to be set, Keys can customize single-character shortcuts only for these items. 85 | 86 | #### — My issue isn't listed here. What now? 87 | Please check the [open issues list](https://github.com/exevil/Keys-For-Sketch/issues) and feel free to create a new one if you don't see your problem there. 88 | 89 | ## Support Project 90 | I spent a lot of time to make this project real so any feedback or donation is really matters. It's wonderful to see that things you do are really making people's lives easier. 91 | 92 | [](https://www.paypal.me/exevil/10) 93 | 94 | ###### Please note that if you're sending your donation from Russian Federation only Russian Roubles will be accepted. 95 | -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/SketchAPITestFile.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "SketchAPITestFile.sketch" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/SketchAPITestFile.dataset/SketchAPITestFile.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/Assets.xcassets/SketchAPITestFile.dataset/SketchAPITestFile.sketch -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/disclosure-horizontal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "disclosure-horizontal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/disclosure-horizontal.imageset/disclosure-horizontal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/Assets.xcassets/disclosure-horizontal.imageset/disclosure-horizontal.pdf -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/disclosure-vertical.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "disclosure-vertical.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/disclosure-vertical.imageset/disclosure-vertical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/Assets.xcassets/disclosure-vertical.imageset/disclosure-vertical.pdf -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/prokeys-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group 2.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/prokeys-bg.imageset/Group 2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/Assets.xcassets/prokeys-bg.imageset/Group 2.pdf -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/shortcut-menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shortcut-menu.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Source/Assets/Assets.xcassets/shortcut-menu.imageset/shortcut-menu.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/Assets.xcassets/shortcut-menu.imageset/shortcut-menu.pdf -------------------------------------------------------------------------------- /Source/Assets/KeysIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/KeysIcon.icns -------------------------------------------------------------------------------- /Source/Assets/SketchAPITestFile.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Assets/SketchAPITestFile.sketch -------------------------------------------------------------------------------- /Source/Controller/ActionsMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionsMenu.swift 3 | // 4 | // 5 | // Created by Vyacheslav Dubovitsky on 21/03/2017. 6 | // 7 | // 8 | 9 | class ActionsMenu: NSMenu { 10 | 11 | /// Show warning alert and if its passed reset all shortcuts to default ones. 12 | @IBAction func resetButtonAction(_ sender: Any) { 13 | 14 | /// Action for "Yes" button. 15 | let yesAction: () -> Void = { 16 | // Remove all user shortcut data from System Preferences 17 | CFPreferencesSetAppValue(Const.Preferences.kUserKeyEquivalents, nil, kCFPreferencesCurrentApplication) 18 | CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication) 19 | // Clean local shortcut cache 20 | appKeyEquivalents.pointee = nil 21 | 22 | // Recursively update all Menu Items in main menu to apply default key equivalent values. 23 | Const.Menu.main.iterateAndExecute { $0._recacheUserKeyEquivalentOnlyIfStale(false) } 24 | // Reload Outline View if needed. 25 | ViewController.initialized?.outlineView.reloadData() 26 | } 27 | 28 | // Show confirmation alert 29 | 30 | let alertCompletionHandler = { (pressedButtonNumber: Int) in 31 | switch pressedButtonNumber { 32 | case 0: 33 | yesAction() 34 | default: 35 | break 36 | } 37 | } 38 | NSAlert.showAlert(over: ViewController.initialized?.view.window, 39 | messageText: "Reset All Shortcuts?", 40 | informativeText: "All Sketch user shortcuts including ones that defined manually in System Preferences will be immediately erased and default values will be restored.", 41 | orderedButtonTitles: ["Yes", "No"], 42 | completionHandler: alertCompletionHandler) 43 | } 44 | 45 | // --- 46 | 47 | @IBAction func proRegistrationInfoButtonAction(_ sender: Any) { 48 | LicensingWindowController.shared.showWindow(self) 49 | } 50 | 51 | // --- 52 | 53 | @IBAction func reportIssueButtonAction(_ sender: Any) { 54 | NSWorkspace.shared.open(URL(string: "https://github.com/exevil/Keys-For-Sketch/issues")!) 55 | } 56 | 57 | @IBAction func helpButtonAction(_ sender: Any) { 58 | NSWorkspace.shared.open(URL(string: "https://github.com/exevil/Keys-For-Sketch/blob/master/README.md#faq")!) 59 | } 60 | 61 | @IBAction func supportButtonAction(_ sender: Any) { 62 | NSWorkspace.shared.open(URL(string: "https://www.paypal.me/exevil/10d")!) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Source/Controller/DonationButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DonationButton.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class DonationButton: NSButton { 10 | 11 | override func awakeFromNib() { 12 | // Button Styling 13 | let paragraphStyle: NSParagraphStyle = { 14 | let style = NSMutableParagraphStyle() 15 | style.alignment = alignment 16 | return style 17 | }() 18 | var basicAttributes: [NSAttributedStringKey : Any] = [ 19 | NSAttributedStringKey.font : font!, 20 | NSAttributedStringKey.paragraphStyle : paragraphStyle 21 | ] 22 | 23 | attributedTitle = NSAttributedString(string: title, attributes:{ 24 | basicAttributes[.foregroundColor] = NSColor(calibratedRed: 74/255, green: 144/255, blue: 226/255, alpha: 1) 25 | return basicAttributes 26 | }()) 27 | 28 | attributedAlternateTitle = NSAttributedString(string: title, attributes:{ 29 | basicAttributes[.foregroundColor] = NSColor(calibratedRed: 74/255, green: 144/255, blue: 226/255, alpha: 0.5) 30 | return basicAttributes 31 | }()) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Source/Controller/Licensing Window/LicensingBGImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicensingBGImageView.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 28/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LicensingBGImageView: NSImageView { 12 | 13 | // Pass mouse events to superview to allow moving window by dragging it anywhere 14 | override var mouseDownCanMoveWindow: Bool { 15 | return true 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Source/Controller/Licensing Window/LicensingInfoTextFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicensingInfoTextFieldCell.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 27/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LicensingInfoTextFieldCell: NSTextFieldCell { 12 | 13 | override func awakeFromNib() { 14 | bezelStyle = .roundedBezel 15 | focusRingType = .none 16 | } 17 | 18 | // Center text vert 19 | override func drawingRect(forBounds rect: NSRect) -> NSRect { 20 | let horizontalInset: CGFloat = -6 // For better appearance 21 | let textHeight: CGFloat = 24 // Default text height. 22 | let newRect = NSRect(x: horizontalInset, 23 | y: (rect.size.height - textHeight) / 2, 24 | width: rect.size.width - horizontalInset, 25 | height: textHeight) 26 | return super.drawingRect(forBounds: newRect) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Source/Controller/Licensing Window/LicensingWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicensingWindowController.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 23/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LicensingWindowController: NSWindowController { 12 | 13 | // MARK: Properties 14 | 15 | static let shared = LicensingWindowController(windowNibName: NSNib.Name(rawValue: "LicensingWindowController" 16 | )) 17 | 18 | @IBOutlet weak var emailTextField: NSTextField! 19 | @IBOutlet weak var serialTextField: NSTextField! 20 | @IBOutlet weak var activateButton: NSButton! 21 | 22 | // MARK: Loading 23 | 24 | override func windowDidLoad() { 25 | super.windowDidLoad() 26 | 27 | // Initial setup 28 | window?.backgroundColor = NSColor(calibratedHue: 0, saturation: 0, brightness: 98.0/100.0, alpha: 1) 29 | window?.styleMask.remove(.resizable) 30 | window?.isMovableByWindowBackground = true 31 | 32 | // Hide title bar 33 | window?.titleVisibility = .hidden 34 | window?.titlebarAppearsTransparent = true 35 | window?.styleMask.insert(.fullSizeContentView) 36 | 37 | // Hide excessive buttons 38 | window?.standardWindowButton(.miniaturizeButton)?.isHidden = true 39 | window?.standardWindowButton(.zoomButton)?.isHidden = true 40 | } 41 | 42 | // Actions 43 | } 44 | -------------------------------------------------------------------------------- /Source/Controller/Licensing Window/UI/LicensingWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicensingWindowController.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 23/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LicensingWindowController: NSWindowController { 12 | 13 | // MARK: Properties 14 | 15 | static let shared = LicensingWindowController(windowNibName: NSNib.Name(rawValue: "LicensingWindowController" 16 | )) 17 | 18 | @IBOutlet weak var emailTextField: NSTextField! 19 | @IBOutlet weak var serialTextField: NSTextField! 20 | @IBOutlet weak var activateButton: NSButton! 21 | @IBOutlet weak var bgImageView: NSImageView! 22 | 23 | // MARK: Loading 24 | 25 | override func windowDidLoad() { 26 | super.windowDidLoad() 27 | 28 | // Initial setup 29 | window?.backgroundColor = NSColor(calibratedHue: 0, saturation: 0, brightness: 98.0/100.0, alpha: 1) 30 | window?.styleMask.remove(.resizable) 31 | window?.isMovableByWindowBackground = true 32 | bgImageView.mouseDownCanMoveWindow = true 33 | 34 | // Hide title bar 35 | window?.titleVisibility = .hidden 36 | window?.titlebarAppearsTransparent = true 37 | window?.styleMask.insert(.fullSizeContentView) 38 | 39 | // Hide excessive buttons 40 | window?.standardWindowButton(.miniaturizeButton)?.isHidden = true 41 | window?.standardWindowButton(.zoomButton)?.isHidden = true 42 | } 43 | 44 | // Actions 45 | } 46 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/ItemCellContextualMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyItemContextMenu.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 20/05/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class ItemCellContextualMenu: NSMenu { 10 | 11 | // MARK: Init 12 | 13 | /// Associated KeyItem. 14 | let menuItem: NSMenuItem 15 | 16 | required init(menuItem: NSMenuItem) { 17 | self.menuItem = menuItem 18 | super.init(title: "") 19 | self.autoenablesItems = false 20 | 21 | addItem(withTitle: "Remove", action: #selector(restoreDefaultShortcut), keyEquivalent: "").target = self 22 | } 23 | 24 | // Since we can only init with keyItem, use fatalError on required initializer 25 | required init(coder decoder: NSCoder) { 26 | fatalError("ItemCellContextualMenu can only be initialized with init(keyItem:)") 27 | } 28 | 29 | // MARK: Actions 30 | 31 | /// Restore default shortcut for clicked item. 32 | @objc func restoreDefaultShortcut() { 33 | menuItem.shortcut = nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/ItemTableCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyItemTableCellView.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 12/02/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class ItemTableCellView: NSTableCellView { 10 | 11 | @IBOutlet weak var shortcutView: ShortcutView! 12 | @IBOutlet weak var titleLabel: NSTextField! 13 | @IBOutlet weak var menuButton: NSButton! 14 | 15 | // MARK: Init 16 | 17 | // Should be assigned during cell initialization process 18 | internal var menuItem: NSMenuItem! { 19 | didSet { 20 | shortcutView.menuItem = menuItem 21 | titleLabel.stringValue = menuItem.title 22 | // Hide shortcut view if now shortcut needed 23 | shortcutView.isHidden = !menuItem.needsShortcut 24 | // Hide menu button if there's no custom shortcut to manage 25 | menuButton.isHidden = !menuItem.hasCustomShortcut 26 | } 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | super.init(coder: coder) 31 | 32 | // Draw fake separator 33 | self.layer = { 34 | let layer = CALayer() 35 | let separatorLayer = CAShapeLayer() 36 | separatorLayer.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: Const.Cell.dividerHeight) 37 | separatorLayer.backgroundColor = NSColor(calibratedRed: 217/255, green: 217/255, blue: 217/255, alpha: 1).cgColor 38 | layer.addSublayer(separatorLayer) 39 | return layer 40 | }() 41 | } 42 | 43 | // MARK: Actions 44 | 45 | /// Popup context menu on button press. 46 | @IBAction func contextualMenuButtonAction(_ sender: NSButton) { 47 | let contextualMenu = ItemCellContextualMenu(menuItem: menuItem) 48 | contextualMenu.popUp(positioning: nil, at: NSPoint(x: sender.frame.minX, y: sender.frame.minY), in: self) 49 | } 50 | 51 | // MARK: Other 52 | 53 | /// Blink animation. 54 | func blink(with color: NSColor, forDuration duration: Float) { 55 | let transparentColor = NSColor(calibratedWhite: 1.0, alpha: 0.0).cgColor 56 | let animation = CAKeyframeAnimation(keyPath: "backgroundColor") 57 | animation.values = [color.cgColor, transparentColor] 58 | animation.keyTimes = [0.1, 1.0] 59 | animation.timingFunctions = [ 60 | CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), 61 | CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) 62 | ] 63 | animation.duration = CFTimeInterval(duration) 64 | 65 | layer?.add(animation, forKey: "animation") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/Shortcut View/MASShortcut+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MASShortcut+Additions.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/07/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | extension MASShortcut { 10 | /// A modifier flags string valid to store modifiers data in System Preferences. 11 | var modifierFlagsStringForSystemPreferences: String! { 12 | // Replace modifiers in modifierFlagsString to ones that suitable for System Preferences 13 | return String(modifierFlagsString.map { 14 | switch $0 { 15 | case "⌘": 16 | return "@" 17 | case "⌥": 18 | return "~" 19 | case "⌃": 20 | return "^" 21 | case "⇧": 22 | return "$" 23 | default: 24 | return "\0" // Null character 25 | } 26 | }) 27 | } 28 | 29 | /// A key-code string used in key equivalent matching. This implementation should fix some problems that default `keyCodeStringForKeyEquivalent` has. 30 | var fixedKeyCodeStringForKeyEquivalent: String! { 31 | return KeyCodeTransformer.keyEquivalents(for: keyCode)?[0] ?? "" 32 | } 33 | 34 | /** Init with Key Equivalent string and modifier flags. 35 | — parameters: 36 | keyEquivalent: A string that represents character from ANSI keyboard. 37 | modifierFlags: Used modifier flags. 38 | */ 39 | convenience init?(keyEquivalent: String, modifierFlags: NSEvent.ModifierFlags) { 40 | if let itemKeyCode = KeyCodeTransformer.keyCode(for: keyEquivalent) { 41 | 42 | var modifierFlagsWithShift: UInt = modifierFlags.rawValue 43 | // Since NSMenuItem can't capture Shift key as a Modifier Flag try to determine was Shift used during keyEquivalent setup and if so, add it to modifierFlags 44 | if itemKeyCode.wasShiftKeyUsed { 45 | modifierFlagsWithShift |= NSEvent.ModifierFlags.shift.rawValue 46 | } 47 | 48 | self.init(keyCode: itemKeyCode.value, modifierFlags: modifierFlagsWithShift) 49 | } else { 50 | return nil 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/Shortcut View/ShortcutButtonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutButtonCell.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class ShortcutButtonCell: NSButtonCell { 10 | 11 | override var alignment: NSTextAlignment { 12 | get { 13 | return super.alignment 14 | } 15 | set { 16 | switch newValue { 17 | case .center: 18 | // Make shortcut string aligned left since default implementation of Shortcut aligns it to center in ShortcutView 19 | super.alignment = .left 20 | case .right: 21 | // Disable display of default hinting buttons 22 | self.isEnabled = false 23 | default: 24 | super.alignment = newValue 25 | } 26 | } 27 | } 28 | 29 | override var title: String! { 30 | didSet { 31 | // Change "Record Shortcut" to set shortcut 32 | if title == "Record Shortcut" { 33 | attributedTitle = NSAttributedString(string: "…", attributes:[NSAttributedStringKey.foregroundColor : NSColor.lightGray]) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/Shortcut View/ShortcutErrorAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutErrorAlert.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 05/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class ShortcutErrorAlert: NSAlert { 10 | 11 | var keysShortcutshortcut: MASShortcut? 12 | 13 | var setAnywayButton: NSButton? 14 | var toConflictButton: NSButton? 15 | 16 | convenience init(with shortcut: MASShortcut, explanation: String) { 17 | self.init() 18 | alertStyle = .critical 19 | messageText = "The key combination \(shortcut) cannot be used" 20 | informativeText = explanation 21 | // Add Cancel button and assign it Escape keyEquivalent 22 | addButton(withTitle: "Cancel").keyEquivalent = "\u{1b}" 23 | 24 | // If conflicted shortcut reserved by Menu Item not represented by KeyItem options below are not allowed 25 | if Const.Menu.main.findItem(by: shortcut) != nil { 26 | setAnywayButton = addButton(withTitle: "Set Anyway") 27 | toConflictButton = addButton(withTitle: "To Conflict") 28 | } 29 | 30 | window.initialFirstResponder = toConflictButton 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/Shortcut View/ShortcutErrorHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutErrorHandler.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 25/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ShortcutErrorHandler: NSObject { 12 | 13 | static let shared = ShortcutErrorHandler() 14 | 15 | /// View Controller to display error alerts. 16 | /// - important: Explicitly unwrapped since you can produce an error alert only when controller is successfully initialized. 17 | let viewController: ViewController = ViewController.initialized! 18 | 19 | /// Currently presented Error Sheet. 20 | var presentedErrorSheet: NSAlert? 21 | 22 | /// Begin Error Sheet in viewController. 23 | func beginErrorSheet(with shortcut: MASShortcut, explanation: String, sender: ShortcutView) { 24 | endErrorSheetIfDisplayed() 25 | 26 | let errorAlert = ShortcutErrorAlert(with: shortcut, explanation: explanation) 27 | presentedErrorSheet = errorAlert 28 | errorAlert.beginSheetModal(for: viewController.view.window!, completionHandler: { (response) -> Void in 29 | 30 | // MARK: Cancel button 31 | if response == .alertFirstButtonReturn { 32 | errorAlert.window.sheetParent?.endSheet(errorAlert.window) 33 | } 34 | 35 | if let conflictKeyItem = Const.Menu.main.findItem(by: shortcut) { 36 | // MARK: Set Anyway button 37 | if response == .alertSecondButtonReturn { 38 | sender.menuItem.shortcut = shortcut 39 | conflictKeyItem.shortcut = nil 40 | } 41 | 42 | // MARK: To Conflict Button 43 | if response == .alertThirdButtonReturn { 44 | // If search is used, clear it to access a conflict item even if it's not currently shown in Outline View. 45 | if 46 | let dataSource = self.viewController.outlineView.dataSource as? OutlineViewDataSource, 47 | dataSource.searchPhrase != "" 48 | { 49 | dataSource.searchPhrase = "" 50 | self.viewController.outlineView.reloadData() 51 | } 52 | 53 | // Collect all parents hierarchy of conflicted item 54 | var items = [conflictKeyItem] 55 | if var parent = conflictKeyItem.parent { 56 | items.append(parent) 57 | while parent.parent != nil { 58 | let parentOfParent = parent.parent! 59 | items.append(parentOfParent) 60 | parent = parentOfParent 61 | } 62 | } 63 | 64 | // Reverse array to expand parents in right order 65 | items.reverse() 66 | // Expand items and blink a latest one 67 | for item in items { 68 | if item == items.last { 69 | let itemRow = self.viewController.outlineView.row(forItem: item) 70 | self.viewController.outlineView.scrollRowToVisible(itemRow) 71 | // Blink item cell 72 | if let itemCellView = self.viewController.outlineView.view(atColumn: 0, row: itemRow, makeIfNecessary: false) as? ItemTableCellView { 73 | itemCellView.blink(with: NSColor(calibratedRed: 250/255.0, green: 100/255.0, blue: 93/255.0, alpha: 0.4), forDuration: 0.75) 74 | } 75 | } else { 76 | self.viewController.outlineView.expandItem(item) 77 | } 78 | } 79 | } 80 | } 81 | 82 | // Anyway stop listening for shortcut updates when alert is closed 83 | sender.eventMonitoringIsActive = false 84 | }) 85 | } 86 | 87 | /// End currently shown error sheet. 88 | func endErrorSheetIfDisplayed() { 89 | if let errorSheet = presentedErrorSheet { 90 | errorSheet.window.sheetParent?.endSheet(errorSheet.window) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/Cell/Shortcut View/ShortcutValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutValidator.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 02/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ShortcutValidator : MASShortcutValidator { 12 | 13 | static let sharedInstance = ShortcutValidator() 14 | // Return the same shared instance to override Obj-C superclass singleton method 15 | override static func shared() -> ShortcutValidator { 16 | return ShortcutValidator.sharedInstance 17 | } 18 | 19 | func isShortcutValid(_ shortcut: MASShortcut!, validWithoutModifiers: Bool) -> Bool { 20 | let keyCode = Int(shortcut.keyCode) 21 | let modifiers = shortcut.modifierFlags 22 | 23 | // Allow any function key with any combination of modifiers 24 | let includesFunctionKey: Bool = { 25 | let fKeyCodes = [kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F5, kVK_F6, kVK_F7, kVK_F8, kVK_F9, kVK_F10, kVK_F11, kVK_F12, kVK_F13, kVK_F14, kVK_F15, kVK_F16, kVK_F17, kVK_F18, kVK_F19, kVK_F20] 26 | for fKeyCode in fKeyCodes { 27 | if fKeyCode == keyCode { return true } 28 | } 29 | return false 30 | }() 31 | if includesFunctionKey { return true } 32 | 33 | // Check for modifiers 34 | let hasModifierFlags = modifiers > 0 35 | if !hasModifierFlags && validWithoutModifiers { return true } 36 | 37 | // Allow any hotkey containing Control or Command modifier 38 | let includesCmd = modifiers & NSEvent.ModifierFlags.command.rawValue > 0 39 | let includesCtrl = modifiers & NSEvent.ModifierFlags.control.rawValue > 0 40 | // Allow Option key only in selected cases 41 | let includesCorrectOption: Bool = { 42 | if modifiers & NSEvent.ModifierFlags.control.rawValue > 0 { 43 | // Always allow Option-Space and Option-Escape because they do not have any bind system commands 44 | if keyCode == kVK_Space || keyCode == kVK_Escape { return true } 45 | 46 | // Allow Option modifier with any key even if it will break the system binding 47 | if self.allowAnyShortcutWithOptionModifier { return true } 48 | } 49 | return false 50 | }() 51 | if includesCmd || includesCtrl || includesCorrectOption { 52 | return true 53 | } 54 | 55 | // The hotkey violates system bindings 56 | return false 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/OutlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineView.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 02/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class OutlineView: NSOutlineView { 10 | 11 | override func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView? { 12 | let view = super.makeView(withIdentifier: identifier, owner: owner) 13 | 14 | // Custom disclosure icons 15 | if identifier == NSOutlineView.disclosureButtonIdentifier { 16 | (view as! NSButton).image = image(Const.Image.disclosureVertiacal) 17 | (view as! NSButton).alternateImage = image(Const.Image.disclosureHorizontal) 18 | } 19 | 20 | return view 21 | } 22 | 23 | // Remove default cell separator 24 | override var intercellSpacing: NSSize { 25 | get { 26 | return NSSize(width: 0, height: 0) 27 | } 28 | set {} 29 | } 30 | 31 | // MARK: Double Action 32 | 33 | override var doubleAction: Selector? { 34 | get { 35 | return #selector(doubleActionHandler) 36 | } 37 | set {} 38 | } 39 | 40 | @objc func doubleActionHandler() { 41 | if let item = item(atRow: clickedRow), let view = view(atColumn: 0, row: clickedRow, makeIfNecessary: false) as? ItemTableCellView { 42 | let optionPressed = (NSApp.currentEvent!.modifierFlags.rawValue & NSEvent.ModifierFlags.option.rawValue) == NSEvent.ModifierFlags.option.rawValue 43 | 44 | // Play blink animation 45 | view.blink(with: NSColor.lightGray.withAlphaComponent(0.25), forDuration: 0.2) 46 | 47 | // Expand/Collapse item based on its current state 48 | isItemExpanded(item) ? 49 | animator().collapseItem(item, collapseChildren: optionPressed) : 50 | animator().expandItem(item, expandChildren: optionPressed) 51 | } 52 | } 53 | } 54 | 55 | extension NSOutlineView { 56 | /// Make function that automatically assigns given Key Item. 57 | func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?, menuItem: NSMenuItem) -> ItemTableCellView? { 58 | let view = super.makeView(withIdentifier: identifier, owner: owner) as? ItemTableCellView 59 | view?.menuItem = menuItem 60 | return view 61 | } 62 | 63 | /// A KeyItem at clicked row or nil if no Key Item found. 64 | var clickedMenuItem: NSMenuItem? { 65 | return item(atRow: clickedRow) as? NSMenuItem 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/OutlineViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineViewDataSource.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 24/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class OutlineViewDataSource: NSObject, NSOutlineViewDataSource { 10 | 11 | /// String that represents a search phrase that menu item titles should correspond 12 | var searchPhrase: String = "" 13 | 14 | /// Get the only menu items that need to be displayed by Outline View 15 | func itemsToDisplay(of menu: NSMenu) -> [NSMenuItem] { 16 | // Filter restricted items 17 | var result = menu.items.filter{ $0.shouldBeDisplayedInKeys } 18 | // Additionaly filter items that not containing searchPhrase if needed 19 | if !searchPhrase.isEmpty { 20 | result = result.filter{ $0.title(contains: searchPhrase) } 21 | } 22 | 23 | return result 24 | } 25 | 26 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { 27 | // Get customizable Menu Items count 28 | if let submenu = (item as? NSMenuItem)?.submenu { 29 | return itemsToDisplay(of: submenu).count 30 | } 31 | return itemsToDisplay(of: Const.Menu.main).count 32 | } 33 | 34 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { 35 | // Get customizable Menu Item by index 36 | if let submenu = (item as? NSMenuItem)?.submenu { 37 | return itemsToDisplay(of: submenu)[index] as Any 38 | } 39 | return itemsToDisplay(of: Const.Menu.main)[index] as Any 40 | } 41 | 42 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { 43 | if let submenu = (item as? NSMenuItem)?.submenu { 44 | // Stop menu observer to avoid infinite loop and invoke menuNeedsUpdate method of submenu delegate to let Sketch provide actial menu items data if needed 45 | MenuObserver.shared.stopObserving() 46 | submenu.delegate?.menuNeedsUpdate?(submenu) 47 | MenuObserver.shared.startObserving() 48 | 49 | return itemsToDisplay(of: submenu).count > 0 50 | } 51 | return false 52 | } 53 | } 54 | 55 | fileprivate extension NSMenuItem { 56 | /// Recursively check if menu item and its children titles contains given search phrase. 57 | func title(contains searchPhrase: String) -> Bool { 58 | switch self { 59 | case let val where val.title.localizedCaseInsensitiveContains(searchPhrase): 60 | return true 61 | case let val where val.hasSubmenu: 62 | return val.submenu!.items.contains{ $0.title(contains: searchPhrase) } 63 | default: 64 | return false 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Source/Controller/Outline View/OutlineViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineViewDelegate.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 24/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class OutlineViewDelegate: NSObject, NSOutlineViewDelegate { 10 | 11 | @IBOutlet weak var errorHandler: ShortcutErrorHandler! 12 | 13 | func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { 14 | var height: CGFloat = 0 // Should crash if no value is set 15 | if let menuItem = item as? NSMenuItem { 16 | if menuItem.isSeparatorItem { 17 | height = Const.Cell.separatorHeight 18 | } else { 19 | height = Const.Cell.height 20 | } 21 | } 22 | return height 23 | } 24 | 25 | func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { 26 | var cell: NSTableCellView? 27 | if let menuItem = item as? NSMenuItem { 28 | if menuItem.isSeparatorItem { 29 | cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "separatorCell"), owner: self) as? NSTableCellView 30 | } else { 31 | cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "menuCell"), owner: self, menuItem: menuItem) 32 | } 33 | } 34 | return cell 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Source/Controller/SearchField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchField.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 13/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SearchField: NSSearchField { 12 | 13 | @IBOutlet weak var outlineView: NSOutlineView! 14 | @IBOutlet weak var outlineViewDataSource: OutlineViewDataSource! 15 | 16 | /// Send search phrase to Data source of Outline view and reload its data to show the results 17 | @IBAction func filterResults(_ sender: NSSearchFieldCell) { 18 | outlineViewDataSource.searchPhrase = sender.stringValue 19 | outlineView.reloadData() 20 | 21 | // Expand items when searching or collapse it back on searching ends 22 | if !sender.stringValue.isEmpty { 23 | outlineView.expandItem(nil, expandChildren: true) 24 | } else { 25 | outlineView.collapseItem(nil, collapseChildren: true) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/Controller/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 12/02/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public class ViewController: NSViewController { 10 | 11 | /// - important: Should return instance only after plugin initialization. 12 | static var initialized: ViewController? 13 | 14 | // MARK: Sketch's MSPreferencePane conformation 15 | 16 | @objc static var toolbarIcon = image(Const.Image.keysIcon) 17 | @objc static var title = "Keys" 18 | @objc static var identifier = Const.Preferences.keysIdentifier 19 | @objc static var nibName = NSNib.Name(rawValue: "ViewController") 20 | @objc var preferencesController: AnyObject? 21 | 22 | @objc public convenience init(preferencesController: AnyObject) { 23 | self.init(nibName: type(of: self).nibName, bundle: Keys.bundle) 24 | self.preferencesController = preferencesController 25 | ViewController.initialized = self 26 | } 27 | 28 | // MARK: Variables 29 | 30 | @IBOutlet weak var outlineScrollView: NSScrollView! 31 | @IBOutlet weak var outlineView: OutlineView! 32 | @IBOutlet weak var actionMenu: ActionsMenu! 33 | 34 | // MARK: Actions 35 | 36 | // Main Settings button action 37 | @IBAction func settingsButtonAction(_ sender: NSButton) { 38 | actionMenu.popUp(positioning: nil, at: NSPoint(x: sender.frame.minX, y: sender.frame.minY), in: view) 39 | } 40 | 41 | // MARK: View Preparations 42 | 43 | override public func viewWillAppear() { 44 | // Style Outline View 45 | outlineScrollView.layer = { 46 | let layer = CALayer() 47 | layer.borderWidth = 0.5 48 | layer.borderColor = NSColor.lightGray.cgColor 49 | return layer 50 | }() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.8.9 19 | CFBundleVersion 20 | 1924 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 23 | NSPrincipalClass 24 | KeysForSketch.Keys 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Keys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keys.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 11/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class Keys: NSObject { 10 | 11 | /// Shared instance 12 | @objc static var shared = Keys() 13 | /// `true` if plugin loading process is finished succesfuly. 14 | @objc var isLoaded: Bool = false 15 | 16 | // MARK: Properties 17 | 18 | /// Keys bundle shortcut. 19 | static var bundle: Bundle { 20 | return Bundle(for:Keys.self) 21 | } 22 | 23 | /// URL of `KeysForSketch.sketchplugin` bundle. 24 | static var pluginPath: String { 25 | let frameworkPath = bundle.bundleURL.path 26 | let endPathIndex = frameworkPath.range(of: ".sketchplugin", options: .backwards)!.upperBound 27 | let endPath = String(frameworkPath.prefix(upTo: endPathIndex)) 28 | return endPath 29 | } 30 | 31 | // CFBundleShortVersionString shortcut. 32 | static var shortVersion: String { 33 | return bundle.infoDictionary?["CFBundleShortVersionString"] as! String 34 | } 35 | 36 | // MARK: Start 37 | 38 | /// Plugin loading preparations. 39 | @objc class func start() { 40 | 41 | // Try to test API protocols and inject it to its Sketch classes for easier casting in Swift. If fails, Keys shouldn't run further 42 | do { 43 | try SketchAPIProtocolsTest.testProtocols() 44 | try SketchAPITools.injectAPIProtocols() 45 | } catch { 46 | NSAlert.showKeysErrorAlert(error: error) 47 | return 48 | } 49 | 50 | // Keys should start loading only after Sketch isFinishedLaunching 51 | if NSRunningApplication.current.isFinishedLaunching { 52 | shared.loadKeys() 53 | } else { 54 | NotificationCenter.default.addObserver(shared, 55 | selector: #selector(shared.loadKeys), 56 | name: NSApplication.didFinishLaunchingNotification, 57 | object: NSApp) 58 | } 59 | } 60 | 61 | /// Load plugin. 62 | @objc func loadKeys() { 63 | do { 64 | try UIInjector.injectKeysPanel() 65 | // TODO: Don't forget to execute only after license verification. 66 | // try ProKeysManager.injectProKeys() 67 | } catch { 68 | NSAlert.showKeysErrorAlert(error: error) 69 | } 70 | 71 | 72 | // Invoke Observers 73 | MenuObserver.shared.startObserving() 74 | FileObserver.shared.startObserving() 75 | // TODO: Add license management 76 | 77 | // Update loading status 78 | isLoaded = true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Source/KeysForSketch.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeysForSketch.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 15/07/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VDKSketchAPI.h" 11 | #import "VDK_JRSwizzle.h" 12 | #import "NSMenuItem+Private.h" 13 | #import "lorgnette.h" 14 | #import "lorgnette-structs.h" 15 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcodeproj/project.xcworkspace/xcuserdata/exevil.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch.xcodeproj/project.xcworkspace/xcuserdata/exevil.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Source/KeysForSketch.xcodeproj/xcuserdata/exevil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcodeproj/xcuserdata/exevil.xcuserdatad/xcschemes/KeysForSketch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 91 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcodeproj/xcuserdata/exevil.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | KeysForSketch.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | KeysForSketchTests.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | EAC3F8041F1AC2EC00207699 21 | 22 | primary 23 | 24 | 25 | EAC3F80D1F1AC2ED00207699 26 | 27 | primary 28 | 29 | 30 | EACE68431F20181500518CAF 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/xcuserdata/exevil.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch.xcworkspace/xcuserdata/exevil.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/xcuserdata/exevil.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | EnabledFullIndexStoreVisibility 12 | 13 | IssueFilterStyle 14 | ShowActiveSchemeOnly 15 | LiveSourceIssuesEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Source/KeysForSketch.xcworkspace/xcuserdata/exevil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Assets/Assets.xcassets/disclosure-horizontal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "disclosure-horizontal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Source/KeysForSketch/Assets/Assets.xcassets/disclosure-horizontal.imageset/disclosure-horizontal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/Assets/Assets.xcassets/disclosure-horizontal.imageset/disclosure-horizontal.pdf -------------------------------------------------------------------------------- /Source/KeysForSketch/Assets/Assets.xcassets/disclosure-vertical.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "disclosure-vertical.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Source/KeysForSketch/Assets/Assets.xcassets/disclosure-vertical.imageset/disclosure-vertical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/Assets/Assets.xcassets/disclosure-vertical.imageset/disclosure-vertical.pdf -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/MASShortcut.framework/MASShortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/MASShortcut.framework/MASShortcut -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/MASShortcut.framework/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 16F73 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | MASShortcut 11 | CFBundleIdentifier 12 | org.cocoapods.MASShortcut 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | MASShortcut 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 2.3.6 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 9M174d 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 17A306e 37 | DTSDKName 38 | macosx10.13 39 | DTXcode 40 | 0900 41 | DTXcodeBuild 42 | 9M174d 43 | 44 | 45 | -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/MASShortcut.framework/Versions/A/MASShortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/MASShortcut.framework/Versions/A/MASShortcut -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreFoundation.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreFoundation.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreGraphics.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreGraphics.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreImage.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftCoreImage.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftFoundation.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftFoundation.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftObjectiveC.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftObjectiveC.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftQuartzCore.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftQuartzCore.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftSwiftOnoneSupport.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Frameworks/libswiftSwiftOnoneSupport.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.bundle/Contents/Resources/libswiftRemoteMirror.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/KeysForSketch/KeysForSketch.bundle/Contents/Resources/libswiftRemoteMirror.dylib -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /Source/KeysForSketch/KeysForSketch.xcworkspace/xcuserdata/exevil.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | EnabledFullIndexStoreVisibility 12 | 13 | IssueFilterStyle 14 | ShowActiveSchemeOnly 15 | LiveSourceIssuesEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Pods.xcodeproj/xcuserdata/exevil.xcuserdatad/xcschemes/MASShortcut.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Pods.xcodeproj/xcuserdata/exevil.xcuserdatad/xcschemes/Pods-KeysForSketch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 68 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Pods.xcodeproj/xcuserdata/exevil.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MASShortcut-MASShortcut.xcscheme 8 | 9 | isShown 10 | 11 | 12 | MASShortcut.xcscheme 13 | 14 | isShown 15 | 16 | 17 | Pods-KeysForSketch.xcscheme 18 | 19 | isShown 20 | 21 | 22 | Pods-KeysForSketchUnitTests.xcscheme 23 | 24 | isShown 25 | 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/MASShortcut/ResourceBundle-MASShortcut-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 2.3.6 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2012-2013, Vadim Shpakovski 18 | All rights reserved. 19 | 20 | Redistribution and use in source and binary forms, with or without 21 | modification, are permitted provided that the following conditions are met: 22 | 23 | 1. Redistributions of source code must retain the above copyright notice, this 24 | list of conditions and the following disclaimer. 25 | 2. Redistributions in binary form must reproduce the above copyright notice, 26 | this list of conditions and the following disclaimer in the documentation 27 | and/or other materials provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 30 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 31 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 32 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 33 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 35 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 36 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 37 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 38 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | 40 | License 41 | BSD 2-clause 42 | Title 43 | MASShortcut 44 | Type 45 | PSGroupSpecifier 46 | 47 | 48 | FooterText 49 | Generated by CocoaPods - https://cocoapods.org 50 | Title 51 | 52 | Type 53 | PSGroupSpecifier 54 | 55 | 56 | StringsTable 57 | Acknowledgements 58 | Title 59 | Acknowledgements 60 | 61 | 62 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_KeysForSketch : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_KeysForSketch 5 | @end 6 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | 91 | if [[ "$CONFIGURATION" == "Debug" ]]; then 92 | install_framework "$BUILT_PRODUCTS_DIR/MASShortcut/MASShortcut.framework" 93 | fi 94 | if [[ "$CONFIGURATION" == "Release" ]]; then 95 | install_framework "$BUILT_PRODUCTS_DIR/MASShortcut/MASShortcut.framework" 96 | fi 97 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 98 | wait 99 | fi 100 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_KeysForSketchVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_KeysForSketchVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/MASShortcut" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/MASShortcut/MASShortcut.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "MASShortcut" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_KeysForSketch { 2 | umbrella header "Pods-KeysForSketch-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketch/Pods-KeysForSketch.release.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/MASShortcut" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/MASShortcut/MASShortcut.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "MASShortcut" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Pods/Target Support Files/Pods-KeysForSketchUnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Sketch API/Protocols/VDKSketchMSPluginManagerWithActionsProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSPluginManagerWithActionsProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 22/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSPluginManagerWithActionsProtocol 10 | 11 | + (NSURL *)mainPluginsFolderURL; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/KeysForSketch/Sketch API/Protocols/VDKSketchMSPreferencesControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDPreferencesControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 27/02/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | // Silence clang warnings 10 | #pragma clang diagnostic push 11 | #pragma clang diagnostic ignored "-Wnullability-completeness" 12 | 13 | @protocol VDKSketchMSPreferencesControllerProtocol 14 | 15 | @property (getter=isWindowLoaded, readonly) BOOL windowLoaded; 16 | @property (nullable, strong) NSWindow *window; 17 | @property(nonatomic) __weak NSToolbar *toolbar; 18 | @property(retain, nonatomic, nullable) NSCache *preferencePanes; 19 | @property(copy, nonatomic) NSDictionary *preferencePaneClasses; 20 | @property(copy, nonatomic) NSArray *toolbarItemIdentifiers; 21 | @property(retain, nonatomic) NSViewController *currentPreferencePane; 22 | @property(nonatomic) unsigned long long selectedTabIndex; 23 | 24 | + (instancetype)sharedController; 25 | 26 | - (BOOL)validateToolbarItem:(id)arg1; 27 | - (id)toolbar:(id)arg1 itemForItemIdentifier:(id)arg2 willBeInsertedIntoToolbar:(BOOL)arg3; 28 | - (id)toolbarSelectableItemIdentifiers:(id)arg1; 29 | - (id)toolbarDefaultItemIdentifiers:(id)arg1; 30 | - (id)toolbarAllowedItemIdentifiers:(id)arg1; 31 | - (void)updateWindowFrame; 32 | - (void)switchToPaneWithIdentifier:(id)arg1; 33 | - (void)switchPanes:(id)arg1; 34 | - (void)adjustColorsAction:(id)arg1; 35 | - (void)awakeFromNib; 36 | 37 | - (IBAction)showWindow:(id)sender; 38 | - (void)close; 39 | 40 | // Remaining properties 41 | @property(readonly, copy) NSString *debugDescription; 42 | @property(readonly, copy) NSString *description; 43 | @property(readonly) Class superclass; 44 | 45 | @end 46 | 47 | #pragma clang diagnostic pop 48 | -------------------------------------------------------------------------------- /Source/KeysForSketchTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/KeysForSketchTests/KeysForSketchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeysForSketchTests.swift 3 | // KeysForSketchTests 4 | // 5 | // Created by Vyacheslav Dubovitsky on 08/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class KeysForSketchTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/Menu/MenuObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuObserver.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public class MenuObserver : NSObject { 10 | 11 | @objc public static let shared = MenuObserver() 12 | let notificationCenter = NotificationCenter.default 13 | 14 | @objc public func startObserving() { 15 | notificationCenter.addObserver(self, selector: #selector(received(notification:)), name: NSMenu.didAddItemNotification, object: nil) 16 | notificationCenter.addObserver(self, selector: #selector(received(notification:)), name: NSMenu.didChangeItemNotification, object: nil) 17 | notificationCenter.addObserver(self, selector: #selector(received(notification:)), name: NSMenu.didChangeItemNotification, object: nil) 18 | } 19 | 20 | @objc public func stopObserving() { 21 | notificationCenter.removeObserver(self, name: NSMenu.didAddItemNotification, object: nil) 22 | notificationCenter.removeObserver(self, name: NSMenu.didChangeItemNotification, object: nil) 23 | notificationCenter.removeObserver(self, name: NSMenu.didChangeItemNotification, object: nil) 24 | } 25 | 26 | /// Handle received notifications. 27 | @objc func received(notification: NSNotification) { 28 | let menu = notification.object as? NSMenu 29 | // let itemIndex = notification.userInfo?["NSMenuItemIndex"] as? Int ?? -1 30 | // let menuItem = menu?.item(at: itemIndex) 31 | 32 | if menu?.isChildOf(Const.Menu.main) ?? false { 33 | 34 | // Reload outlineView for superitem if needed. 35 | if ViewController.initialized != nil { 36 | self.cancelPreviousPerformRequiestsAndPerform(#selector(self.reloadOutlineViewItem(for:)), with: menu, afterDelay: 0.5) 37 | } 38 | 39 | // Provide debug info based on notification type 40 | switch notification.name { 41 | case NSMenu.didAddItemNotification: 42 | // inDebug { print("Did add menuItem: '\(menu?.title ?? "") -> \(menuItem?.title ?? "")' (index: \(itemIndex))") } 43 | break 44 | case NSMenu.didChangeItemNotification: 45 | // inDebug { print("Did change menuItem: '\(menu?.title ?? "") -> \(menuItem?.title ?? "")' (index: \(itemIndex))") } 46 | break 47 | case NSMenu.didRemoveItemNotification: 48 | // inDebug { print("Did remove menuItem from Menu '\(menu?.title ?? "")' at index \(itemIndex)") } 49 | break 50 | default: 51 | assertionFailure("Unexpected notification type.") 52 | } 53 | } 54 | } 55 | 56 | /// Reload ViewController's Outline View item for given menu if needed. 57 | @objc func reloadOutlineViewItem(for menu: NSMenu?) { 58 | if let superItem = menu?.superitem { 59 | ViewController.initialized?.outlineView.reloadItem(superItem, reloadChildren: true) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Source/Menu/NSMenu+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenu+Additions.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 05/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public extension NSMenu { 10 | 11 | // MARK: Common 12 | 13 | /// True if NSMenu is a child of Application's mainMenu. 14 | func isChildOf(_ menu: NSMenu) -> Bool { 15 | var currentSupermenu = supermenu 16 | while currentSupermenu != nil { 17 | if currentSupermenu == menu { return true } 18 | currentSupermenu = currentSupermenu?.supermenu 19 | } 20 | return false 21 | } 22 | 23 | /// Enclosing Menu Item. 24 | var superitem: NSMenuItem? { 25 | return supermenu?.items.filter{ $0.submenu == self }[0] 26 | } 27 | 28 | // MARK: Iterate 29 | 30 | /// Iterate over all KeyItems placed deeper in hierarchy and execute given closure for each of it. 31 | func iterateAndExecute(_ closure: (_ menuItem: NSMenuItem) -> Void) { 32 | items.forEach { item in 33 | closure(item) 34 | item.submenu?.iterateAndExecute(closure) 35 | } 36 | } 37 | 38 | /// Find KeyItem placed deeper in hierarchy by its shortcut. 39 | func findItem(by shortcut: MASShortcut) -> NSMenuItem? { 40 | var resulItem: NSMenuItem? = nil 41 | iterateAndExecute { item in 42 | if item.shortcut == shortcut { 43 | resulItem = item 44 | } 45 | } 46 | return resulItem 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/Menu/NSMenuItem+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenuItem+Additions.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/02/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public extension NSMenuItem { 10 | 11 | // MARK: Keys Checks 12 | 13 | /// Decide which Menu Item should be presented to user. 14 | var shouldBeDisplayedInKeys: Bool { 15 | // Alternate menuItems shouldn't have unique shortcut 16 | if isAlternate { return false } 17 | // If menuItem matches any of the manually defined exceptions, return false as well 18 | if Const.MenuItem.customizableShortcutExceptions.contains(where: { $0 == (title, parent?.title ?? "") }) { return false } 19 | 20 | return true 21 | } 22 | 23 | /// Determines should Key Item have a shortcut or not. 24 | var needsShortcut: Bool { 25 | if hasSubmenu || isSeparatorItem { 26 | return false 27 | } 28 | return true 29 | } 30 | 31 | // MARK: Shortcut 32 | 33 | /// Calculate individual key string to store shortcut value in System Preferences. 34 | var defaultsKey: String { 35 | var parentsTitles = [String]() 36 | 37 | var itemToLoop: NSMenuItem? = self 38 | while itemToLoop != nil { 39 | parentsTitles.append(itemToLoop!.title) 40 | itemToLoop = itemToLoop!.parent 41 | } 42 | 43 | return "\u{1B}" + parentsTitles.reversed().joined(separator: "\u{1B}") 44 | } 45 | 46 | /// Tools can have single a character shortcut and special binding after shortcut is set. 47 | var isTool: Bool { 48 | // Сheck if menu item is part of Insert menu 49 | var parentToCheck = parent 50 | while parentToCheck != nil { 51 | if parentToCheck?.title == "Insert" { 52 | return true 53 | } 54 | parentToCheck = parentToCheck?.parent 55 | } 56 | 57 | // TODO: Possibly add target class matching check 58 | 59 | return false 60 | } 61 | 62 | /// Check if menu item has custom shortcut assigned. 63 | var hasCustomShortcut: Bool { 64 | guard let currentShortcuts = CFPreferencesCopyAppValue(Const.Preferences.kUserKeyEquivalents, kCFPreferencesCurrentApplication) as? [String : String] else { 65 | assertionFailure("Can't get App's User Key Equivalents dict.") 66 | return false 67 | } 68 | return currentShortcuts[defaultsKey] != nil 69 | } 70 | 71 | /// Computed property that represents shortcut that currently associated with KeyItem. 72 | @objc var shortcut: MASShortcut? { 73 | get { 74 | return MASShortcut(keyEquivalent: keyEquivalent, modifierFlags: keyEquivalentModifierMask) 75 | } 76 | set { 77 | if needsShortcut { 78 | // Write new user key equivalent value to System Preferences 79 | writeCFPreferences(for: newValue) 80 | 81 | // Clean up AppKit's local user key equivalents cache 82 | appKeyEquivalents.pointee = nil 83 | // Recache updated user key equivalents cache from System Preferences and apply the changes 84 | _recacheUserKeyEquivalentOnlyIfStale(false) 85 | 86 | inDebug { 87 | print("Shortcut set:\n \(CFPreferencesCopyAppValue( "NSUserKeyEquivalents" as CFString, kCFPreferencesCurrentApplication) ?? [:] as CFPropertyList)") 88 | } 89 | 90 | // Final setup 91 | ToolsManager.setToolShortcutIfNeeded(for: self) 92 | ViewController.initialized?.outlineView.reloadItem(self) 93 | } 94 | } 95 | } 96 | 97 | // MARK: Other 98 | 99 | /// An index of the menu item in enclosing menu. 100 | /// - important: Returns `nil` if item has no enclosing menu or haven't been included into it for some reason. 101 | var index: Int? { 102 | return menu?.index(of: self) != -1 ? menu?.index(of: self) : nil 103 | } 104 | 105 | /// Target object type name 106 | var targetTypeName: String! { 107 | if target != nil { 108 | return String(describing: type(of:target!)) 109 | } else { 110 | // TODO: Handle Error 111 | assertionFailure("Can't get menu item target to determine its class.") 112 | return nil 113 | } 114 | } 115 | 116 | /// Save given Shortcut to System Preferences 117 | func writeCFPreferences(for shortcut: MASShortcut?) { 118 | 119 | // Copy current values from System Preferences, add a new value and save it back 120 | 121 | var currentShortcuts = CFPreferencesCopyAppValue(Const.Preferences.kUserKeyEquivalents, kCFPreferencesCurrentApplication) as? [String : String] ?? [String : String]() 122 | 123 | if shortcut != nil { 124 | let sysPrefsShortcutString = "\(shortcut?.modifierFlagsStringForSystemPreferences ?? "")\(shortcut?.fixedKeyCodeStringForKeyEquivalent ?? "")" 125 | currentShortcuts[defaultsKey] = sysPrefsShortcutString 126 | } else { 127 | currentShortcuts[defaultsKey] = nil 128 | } 129 | 130 | CFPreferencesSetAppValue(Const.Preferences.kUserKeyEquivalents, currentShortcuts as CFPropertyList, kCFPreferencesCurrentApplication) 131 | CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication) 132 | 133 | // Register application in `com.apple.universalaccess` preference file if needed to get shortcuts shown in System Preferences 134 | 135 | guard var custommenuApps = CFPreferencesCopyAppValue(Const.Preferences.kCustomMenuApps, Const.Preferences.kUniversalAccess) as? [String] else { 136 | assertionFailure("Can't determine \(Const.Preferences.kCustomMenuApps) array from \(Const.Preferences.kUniversalAccess) preferences file") 137 | return 138 | } 139 | 140 | if !currentShortcuts.isEmpty && !custommenuApps.contains(Bundle.main.bundleIdentifier!) { 141 | custommenuApps += [Bundle.main.bundleIdentifier!] 142 | CFPreferencesSetAppValue(Const.Preferences.kCustomMenuApps, custommenuApps as CFPropertyList, Const.Preferences.kUniversalAccess) 143 | CFPreferencesAppSynchronize(Const.Preferences.kUniversalAccess) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Source/Menu/NSMenuItem+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenuItem+Private.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 27/07/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// A category used to get access to NSMenuItem's private methods. 12 | @interface NSMenuItem (Private) 13 | 14 | /// When NO is passed, this method will recache user key equivalents immediately. 15 | - (void)_recacheUserKeyEquivalentOnlyIfStale:(BOOL)arg1; 16 | @end 17 | -------------------------------------------------------------------------------- /Source/Menu/ToolsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolsManager.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 28/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public class ToolsManager : NSObject { 10 | 11 | /// Sketch KeyBindings ShortcutMap dictionary contains mapping of tools action class names to its key equivalents. 12 | static var sketchShortcutMap: [String : String] { 13 | get { 14 | return VDKSketchAPI.keyBindingsController().shortcutMap as! [String : String] 15 | } 16 | set { 17 | // Update Sketch's KeyBindingsController shortcutMap current value 18 | VDKSketchAPI.keyBindingsController().shortcutMap.removeAllObjects() 19 | VDKSketchAPI.keyBindingsController().shortcutMap.setDictionary(newValue) 20 | 21 | // Update singleKeysShortcuts dict of action controllers of all opened documents which is storing an original KeyBindings shortcutMap value during initialization and won't updating it automatically. 22 | for case let doc as VDKSketchMSDocumentProtocol in NSApp.orderedDocuments { 23 | doc.actionsController.singleKeyShortcuts = newValue 24 | } 25 | 26 | // Store new value in User Key Bindings plist file to load it automatically when Sketch will start next time 27 | NSDictionary(dictionary: newValue).write(toFile: VDKSketchAPI.keyBindingsController().userKeyBindingsPath(), atomically: true) 28 | } 29 | } 30 | 31 | /// Default Sketch tools shortcut mapping. 32 | static var sketchDefaultKeyBindings: [String : String] { 33 | // TODO: Need to test 34 | return NSDictionary(contentsOfFile: VDKSketchAPI.keyBindingsController().defaultKeyBindingsPath()) as! [String : String] 35 | } 36 | 37 | // MARK: Shortcut 38 | 39 | /// Bind Shortcut for given Tool KeyItem via Sketch Key Bindings if needed. 40 | class func setToolShortcutIfNeeded(for menuItem: NSMenuItem) { 41 | if !menuItem.isTool { return } 42 | 43 | inDebug { print("Set tool shortcut for: ", menuItem.title) } 44 | 45 | // If the new shortcut is empty, remove its bindning 46 | guard let menuItemShortcut = menuItem.shortcut else { 47 | restoreDefaultToolShortcut(for: menuItem) 48 | return 49 | } 50 | 51 | // If given shortcut is a multi-character one remove previous single-character shortcut for tool from KeyBindings if exist without setting a new one because now app menu itself can manage the shortcut handling 52 | if menuItemShortcut.modifierFlags != 0 { 53 | restoreDefaultToolShortcut(for: menuItem) 54 | return 55 | } 56 | 57 | // Make sure that key has proper Key Equivalent and associated class 58 | if let keyEquivalents = KeyCodeTransformer.keyEquivalents(for: menuItemShortcut.keyCode) { 59 | // Get target class name and use it as a key to set new value to Sketch shortcutMap 60 | sketchShortcutMap[menuItem.targetTypeName] = keyEquivalents.joined() 61 | } 62 | } 63 | 64 | /// Remove shortcut of given tool KeyItem in Sketch Key Bindings. 65 | class func restoreDefaultToolShortcut(for menuItem: NSMenuItem) { 66 | if let defaultValue = sketchDefaultKeyBindings[menuItem.targetTypeName] { 67 | sketchShortcutMap[menuItem.targetTypeName] = defaultValue 68 | } else { 69 | sketchShortcutMap[menuItem.targetTypeName] = nil 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Source/Plugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "com.vyacheslav-dubovitsky.KeysForSketch", 3 | "name" : "Keys For Sketch", 4 | "author" : "Vyacheslav Dubovitsky", 5 | "homepage" : "https://github.com/exevil/Keys-For-Sketch/releases", 6 | "appcast" : "https://api.sketchpacks.com/v1/plugins/com.vyacheslav-dubovitsky.KeysForSketch/appcast", 7 | "version" : "0.8.9", 8 | "description" : "Advanced shortcut manager for Sketch", 9 | "authorEmail" : "m@dbv.ae", 10 | "commands" : [ 11 | { 12 | "script" : "script.js", 13 | "handlers" : { 14 | "actions" : { 15 | "Startup" : "startKeys" 16 | } 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Source/Plugin/script.js: -------------------------------------------------------------------------------- 1 | // 2 | // KeysForSketch 3 | // 4 | // Created by Vyacheslav Dubovitsky on 12/04/2017. 5 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 6 | // 7 | 8 | // Return principal class used to start an initialization process. 9 | function principalClass() { 10 | return NSClassFromString("KeysForSketch.Keys") 11 | } 12 | 13 | // Load framework and start initialization process if needed. 14 | function startKeys(context) { 15 | if (principalClass() == null || principalClass().isLoaded == false) { 16 | var mocha = Mocha.sharedRuntime() 17 | var resourcesFolder = context.scriptPath.stringByDeletingLastPathComponent().stringByDeletingLastPathComponent() + "/Resources" 18 | if (mocha.loadFrameworkWithName_inDirectory("KeysForSketch", resourcesFolder)) { 19 | principalClass().start() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/ProKeys/Keys/ProKeyAspectRatioSwitcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProKeyAspectRatioSwitcher.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 31/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | /// Aspect ratio (Constrain Proportions) lock switching menu item. 10 | final class ProKeyAspectRatioSwitcher: NSObject, ProKeyProtocol { 11 | 12 | static let shared: ProKeyProtocol = ProKeyAspectRatioSwitcher() 13 | 14 | let menuItems: [NSMenuItem] 15 | let parentMenu: NSMenu? = Const.Menu.main.item(withTitle: "Layer")?.submenu?.item(withTitle: "Transform")?.submenu 16 | let previousMenuItemTitle: String? = "Rotate" 17 | 18 | /// Button for switching aspect ratio lock in Sketch Inspector which our menu should correspond to. 19 | var sketchInspectorButton: NSButton? { 20 | return VDKSketchAPI.currentDocument()?.inspectorController.normalInspector.standardInspectors.layerViewController.lockProportionsButton 21 | } 22 | 23 | override init() { 24 | let actionMenuItem = NSMenuItem(title: "Constrain Proportions", action: #selector(action), keyEquivalent: "") 25 | menuItems = [ 26 | NSMenuItem.separator(), 27 | actionMenuItem, 28 | NSMenuItem.separator() 29 | ] 30 | super.init() 31 | actionMenuItem.target = self 32 | } 33 | 34 | /// Simply click on Sketch Inspector button. 35 | @objc func action() { 36 | if let inspectorButton = sketchInspectorButton { 37 | inspectorButton.performClick(self) 38 | } 39 | } 40 | 41 | override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { 42 | if let inspectorButton = sketchInspectorButton { 43 | menuItem.state = inspectorButton.state 44 | return inspectorButton.isEnabled 45 | } else { 46 | menuItem.state = .off 47 | return false 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/ProKeys/Keys/ProKeyProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProKeyProtocol.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 30/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | protocol ProKeyProtocol { 10 | 11 | /// Shared instance. 12 | static var shared: ProKeyProtocol { get } 13 | 14 | /// Associated menu items that will be injected into Sketch menu. 15 | var menuItems: [NSMenuItem] { get } 16 | 17 | /// Sketch menu where ProKey's menu items should be placed. 18 | /// - note: Since there's no guarantee that given menu exists, value type is set to optional and should be implicitly unwrapped during ProKeys injection process. 19 | /// - important: If no item will be found, error will be thrown. 20 | var parentMenu: NSMenu? { get } 21 | 22 | /// Title of Sketch menu item placed in parent menu after which ProKey menu items should be placed after. 23 | /// - important: Make sure that entered title is correct and refers to existing Sketch menu item. Otherwise, error will be thrown during ProKeys injection process. 24 | var previousMenuItemTitle: String? { get } 25 | 26 | /// Menu item validation. See `NSMenuValidation` for help. 27 | /// - important: Make sure that menuItem's target set to self during initialization to get `NSMenuValidation` work. 28 | func validateMenuItem(_ menuItem: NSMenuItem) -> Bool 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Source/ProKeys/ProKeysManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProKeysManager.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 21/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public class ProKeysManager: NSObject { 10 | 11 | /// Array of ProKeys 12 | static var proKeys: [ProKeyProtocol] = [ProKeyAspectRatioSwitcher.shared] 13 | 14 | /// Insert ProKeys menu items onto its positions. 15 | @objc public class func injectProKeys() throws { 16 | for pkey in proKeys { 17 | 18 | guard let parentMenu = pkey.parentMenu else { throw Error.cantGetParentMenuOf(proKey: pkey) } 19 | 20 | // If there's no specified previous menu item, ProKey's menu items should be inserted at index 0. 21 | var previousItemIndex = -1 22 | // If previous item title was set for ProKey, make sure that corresponding menu item exists or throw the error to avoid menu items misplacement. 23 | if let previousMenuItemTitle = pkey.previousMenuItemTitle { 24 | guard let prevMenuItem = parentMenu.item(withTitle: previousMenuItemTitle) else { throw Error.cantGetPreviousMenuItemOf(proKey: pkey) } 25 | previousItemIndex = parentMenu.index(of: prevMenuItem) 26 | } 27 | 28 | for menuItem in pkey.menuItems { 29 | parentMenu.insertItem(menuItem, at: previousItemIndex + 1) 30 | previousItemIndex += 1 31 | } 32 | } 33 | } 34 | 35 | enum Error: LocalizedError { 36 | 37 | case cantGetPreviousMenuItemOf(proKey: ProKeyProtocol) 38 | case cantGetParentMenuOf(proKey: ProKeyProtocol) 39 | 40 | var errorDescription: String? { 41 | switch self { 42 | case .cantGetPreviousMenuItemOf(let proKey): 43 | return "Can't get previous menu item of ProKey: \"\(type(of:proKey))\"." 44 | case .cantGetParentMenuOf(let proKey): 45 | return "Can't get parent menu of ProKey: \"\(type(of:proKey))\"." 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/Inspector/VDKSketchMSInspectorControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSInspectorControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSNormalInspectorProtocol; 10 | 11 | @protocol VDKSketchMSInspectorControllerProtocol 12 | 13 | @property(nonnull, retain, nonatomic) NSViewController *normalInspector; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/Inspector/VDKSketchMSLayerInspectorViewControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSLayerInspectorViewControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerInspectorViewControllerProtocol 10 | 11 | @property(nullable, nonatomic) __weak NSButton *lockProportionsButton; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/Inspector/VDKSketchMSNormalInspectorProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSNormalInspectorProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | @protocol VDKSketchMSStandardInspectorViewControllersProtocol; 9 | 10 | @protocol VDKSketchMSNormalInspectorProtocol 11 | 12 | @property(nonnull, readonly, nonatomic) NSObject *standardInspectors; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/Inspector/VDKSketchMSStandardInspectorViewControllersProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSStandardInspectorViewControllersProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 17/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerInspectorViewControllerProtocol; 10 | 11 | @protocol VDKSketchMSStandardInspectorViewControllersProtocol 12 | 13 | @property(nonnull, readonly, nonatomic) NSViewController *layerViewController; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/VDKSketchMSActionControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSActionControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 22/06/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSActionControllerProtocol 10 | 11 | @property(nonnull, nonatomic, copy) NSDictionary *singleKeyShortcuts; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/VDKSketchMSDocumentControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSDocumentControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 27/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSDocumentProtocol; 10 | 11 | @protocol VDKSketchMSDocumentControllerProtocol 12 | 13 | + (nonnull instancetype)sharedDocumentController; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/VDKSketchMSDocumentDataProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSDocumentDataProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 03/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSPageProtocol; 10 | 11 | @protocol VDKSketchMSDocumentDataProtocol 12 | 13 | @property(nonnull, readonly, nonatomic) NSArray*> *pages; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Document/VDKSketchMSDocumentProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKMSDocumentProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 20/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSActionControllerProtocol, VDKSketchMSLayerArrayProtocol, VDKSketchMSDocumentDataProtocol, VDKSketchMSInspectorControllerProtocol; 10 | 11 | @protocol VDKSketchMSDocumentProtocol 12 | 13 | @property(nonnull, retain, nonatomic) NSObject *documentData; 14 | @property(nonnull, retain, nonatomic) NSObject *actionsController; 15 | @property(nonnull, retain, nonatomic) NSViewController *inspectorController; 16 | 17 | + (nullable instancetype)currentDocument; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Layers/VDKSketchMSArtboardGroupProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSArtboardGroupProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerGroupProtocol; 10 | 11 | @protocol VDKSketchMSArtboardGroupProtocol 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Layers/VDKSketchMSLayerArrayProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSLayerArrayProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 02/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKMSLayerProtocol; 10 | 11 | @protocol VDKSketchMSLayerArrayProtocol 12 | 13 | @property(nonnull, copy, nonatomic) NSArray*> *layers; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Layers/VDKSketchMSLayerGroupProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSLayerGroupProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerProtocol; 10 | 11 | @protocol VDKSketchMSLayerGroupProtocol 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Layers/VDKSketchMSLayerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSLayerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerProtocol 10 | 11 | @property(nonnull, retain, nonatomic) NSString *nodeName; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/Layers/VDKSketchMSPageProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSPageProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 04/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSLayerGroupProtocol, VDKSketchMSArtboardGroupProtocol; 10 | 11 | @protocol VDKSketchMSPageProtocol 12 | 13 | @property(weak, readonly, nonatomic) NSArray*> *artboards; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/VDKSketchAppControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchAppControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 22/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchAppControllerProtocol 10 | 11 | + (nonnull instancetype)sharedInstance; 12 | - (void)refreshCurrentDocument; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/VDKSketchMSKeyBindingsProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchMSKeyBindingsProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 24/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSKeyBindingsProtocol 10 | 11 | @property(nonnull, retain, nonatomic, readonly) NSMutableDictionary *shortcutMap; 12 | 13 | - (nonnull NSString *)defaultKeyBindingsPath; 14 | - (nonnull NSString *)userKeyBindingsPath; 15 | + (nonnull instancetype)sharedInstance; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/Sketch API/Protocols/VDKSketchMSPreferencesControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDPreferencesControllerProtocol.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 27/02/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @protocol VDKSketchMSPreferencesControllerProtocol 10 | 11 | @property(nullable, nonatomic) __weak NSToolbar *toolbar; 12 | @property(nullable, copy, nonatomic) NSArray *toolbarItemIdentifiers; 13 | @property(nonnull, copy, nonatomic) NSDictionary *preferencePaneClasses; 14 | @property(nonatomic) unsigned long long selectedTabIndex; 15 | 16 | + (nonnull id)sharedController; 17 | - (void)switchToPaneWithIdentifier:(nonnull NSToolbarItemIdentifier)arg1; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Source/Sketch API/SketchAPIIntegrationTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SketchAPIIntegrationTest.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 18/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | /// Runtime Big-Bang Integration test of all 10 | class SketchAPIIntegrationTest: NSObject { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Source/Sketch API/SketchAPIProtocolsTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SketchAPI.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 06/08/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | /// Runtime test Sketch classes for conformance to corresponding Keys API protocols. 10 | public class SketchAPIProtocolsTest: NSObject { 11 | 12 | /// Array of protocols that represents Sketch Classes. 13 | static let protocols: [Protocol] = VDKSketchAPI.protocols 14 | 15 | /// Runtime test Sketch classes for conformance to corresponding Keys API protocols. 16 | @objc public static func testProtocols() throws { 17 | for prt in protocols { 18 | let cls: AnyClass 19 | do { cls = try SketchAPITools.sketchClass(from: prt) } 20 | catch { throw error } 21 | 22 | // Test that class responds to protocol instance methods 23 | var methodsCount: UInt32 = 0; 24 | if let instanceMethods = protocol_copyMethodDescriptionList(prt, true, true, &methodsCount) { 25 | for i in 0.. AnyClass { 52 | let prtName = String(cString: protocol_getName(prt)) 53 | 54 | // Check and remove suffix and prefix of protocol name to define a class name 55 | let prtNamePrefix = "VDKSketch" 56 | let prtNameSuffix = "Protocol" 57 | assert(prtName.hasPrefix(prtNamePrefix) && prtName.hasSuffix(prtNameSuffix), "") 58 | let clsName = String(prtName.dropFirst(prtNamePrefix.count).dropLast(prtNameSuffix.count)) 59 | 60 | // Define and return a class itself 61 | guard let cls = NSClassFromString(clsName) else { 62 | throw Error.cantDetermineClassFrom(protocol: prt) 63 | } 64 | return cls 65 | } 66 | 67 | /// Sketch API errors. 68 | enum Error: LocalizedError { 69 | 70 | case cantDetermineClassFrom(protocol: Protocol) 71 | case cantAdd(protocol: Protocol, toClass: AnyClass) 72 | 73 | var errorDescription: String? { 74 | switch self { 75 | case .cantDetermineClassFrom(let prt): 76 | return "Can't determine class from protocol: \"\(prt)\"." 77 | case .cantAdd(let prt, let cls): 78 | return "Can't add API protocol: \"\(prt)\" to its corresponding class: \"\(cls)\"." 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Source/Sketch API/VDKSketchAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKSketchAPI.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 18/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | #import 9 | 10 | #import "VDKSketchAppControllerProtocol.h" 11 | #import "VDKSketchMSPreferencesControllerProtocol.h" 12 | #import "VDKSketchMSKeyBindingsProtocol.h" 13 | // Document 14 | #import "VDKSketchMSDocumentControllerProtocol.h" 15 | #import "VDKSketchMSDocumentProtocol.h" 16 | #import "VDKSketchMSDocumentDataProtocol.h" 17 | #import "VDKSketchMSActionControllerProtocol.h" 18 | // - Inspector 19 | #import "VDKSketchMSInspectorControllerProtocol.h" 20 | #import "VDKSketchMSNormalInspectorProtocol.h" 21 | #import "VDKSketchMSStandardInspectorViewControllersProtocol.h" 22 | #import "VDKSketchMSLayerInspectorViewControllerProtocol.h" 23 | // Layers 24 | #import "VDKSketchMSLayerArrayProtocol.h" 25 | #import "VDKSketchMSPageProtocol.h" 26 | #import "VDKSketchMSArtboardGroupProtocol.h" 27 | #import "VDKSketchMSLayerGroupProtocol.h" 28 | #import "VDKSketchMSLayerProtocol.h" 29 | 30 | @interface VDKSketchAPI : NSObject 31 | 32 | /// Array of API protocols that represents selected methods from its corresponding Sketch Classes. 33 | @property (nonatomic, readonly, class) NSArray * _Nonnull protocols; 34 | 35 | // MARK: General 36 | 37 | + (NSWindowController *_Nonnull)preferencesController; 38 | + (NSObject *_Nonnull)appController; 39 | + (NSObject *_Nonnull)keyBindingsController; 40 | 41 | // MARK: Document 42 | 43 | + (NSDocumentController *_Nonnull)sharedDocumentController; 44 | + (NSDocument *_Nullable)currentDocument; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Source/Sketch API/VDKSketchAPI.m: -------------------------------------------------------------------------------- 1 | // 2 | // SketchAPI.m 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 18/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | #import "VDKSketchAPI.h" 10 | #import 11 | #import 12 | 13 | @implementation VDKSketchAPI 14 | 15 | + (NSArray *)protocols { 16 | return SketchAPITools.protocols; 17 | } 18 | 19 | // MARK: General 20 | 21 | + (NSWindowController *)preferencesController { 22 | NSError *error; 23 | Class sketchClass = [SketchAPITools 24 | sketchClassFrom:@protocol(VDKSketchMSPreferencesControllerProtocol) 25 | error:&error]; 26 | return [sketchClass sharedController]; 27 | // TODO: Handle Error 28 | } 29 | 30 | + (nonnull NSObject *)appController { 31 | return [NSClassFromString(@"AppController") sharedInstance]; 32 | } 33 | 34 | + (nonnull NSObject *)keyBindingsController { 35 | return [NSClassFromString(@"MSKeyBindings") sharedInstance]; 36 | } 37 | 38 | // MARK: Document 39 | 40 | + (nonnull NSDocumentController *)sharedDocumentController { 41 | return [NSClassFromString(@"MSDocumentController") sharedDocumentController]; 42 | } 43 | 44 | + (nullable NSDocument *)currentDocument { 45 | return [NSClassFromString(@"MSDocument") currentDocument]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/Support/Const.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 19/04/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public struct Const { 10 | /// Image names. 11 | public struct Image { 12 | public static let keysIcon = "KeysIcon.icns" 13 | static let disclosureVertiacal = "disclosure-vertical" 14 | static let disclosureHorizontal = "disclosure-horizontal" 15 | } 16 | 17 | struct Menu { 18 | 19 | /// Sketch's mainMenu. 20 | static let main = NSApplication.shared.mainMenu! 21 | 22 | /// A dictionary where keys is titles of Tool Menu Items and the objects is a Sketch action class names associated with it. 23 | static let toolsClassNames = [ 24 | "Vector" : "MSInsertVectorAction", 25 | "Pencil" : "MSPencilAction", 26 | "Text" : "MSInsertTextLayerAction", 27 | "Artboard" : "MSInsertArtboardAction", 28 | "Slice" : "MSInsertSliceAction", 29 | // Shape Submenu items 30 | "Line" : "MSInsertLineAction", 31 | "Arrow" : "MSInsertArrowAction", 32 | "Rectangle" : "MSRectangleShapeAction", 33 | "Oval" : "MSOvalShapeAction", 34 | "Rounded" : "MSRoundedRectangleShapeAction", 35 | "Star" : "MSStarShapeAction", 36 | "Polygon" : "MSPolygonShapeAction", 37 | "Triangle": "MSTriangleShapeAction" 38 | ] 39 | } 40 | 41 | struct MenuItem { 42 | /// An array of manually defined tuples describing a menuItems that shouldn't be customized by Keys. 43 | static let customizableShortcutExceptions: [(title: String, parentItem: String)] = [ 44 | // Sketch 45 | // Dynamic system menu. Better to manage through System Preferences 46 | ("Services", Const.Menu.main.items[0].title), 47 | // File 48 | // A group of dynamic items 49 | ("New from Template", "File"), 50 | // Hidden by Sketch from the main menu 51 | ("New from Template…", "File"), 52 | // A group of dynamic items 53 | ("Open Recent", "File"), 54 | // A group of dynamic items 55 | ("Revert To", "File"), 56 | // A group of dynamic items 57 | ("Print", "File"), 58 | // Edit 59 | // Since "Start Dictation" has`fn fn` shortcut by default which we cant easily reassign, skip it too. 60 | ("Start Dictation", "Edit"), 61 | // Insert 62 | // Layer 63 | // A group of dynamic items 64 | ("Replace With", "Layer") 65 | // Text 66 | // Arrange 67 | // Share 68 | // Plugins 69 | // View 70 | // Window 71 | // Help 72 | ] 73 | } 74 | 75 | struct Cell { 76 | // OutlineView cells 77 | static let height: CGFloat = 33.0 78 | static let separatorHeight: CGFloat = 8.0 79 | static let dividerHeight: CGFloat = 0.5 80 | } 81 | 82 | struct KeyCodeTransformer { 83 | /// A map of non-printed characters Key Codes to its String representations. Values presented in array to match KeyCodeTransformer mapping dictionary data representation. 84 | static let specialKeyCodesMap : [UInt : [String]] = [ 85 | UInt(kVK_F1) : [String(unicodeInt: NSF1FunctionKey)], 86 | UInt(kVK_F2) : [String(unicodeInt: NSF2FunctionKey)], 87 | UInt(kVK_F3) : [String(unicodeInt: NSF3FunctionKey)], 88 | UInt(kVK_F4) : [String(unicodeInt: NSF4FunctionKey)], 89 | UInt(kVK_F5) : [String(unicodeInt: NSF5FunctionKey)], 90 | UInt(kVK_F6) : [String(unicodeInt: NSF6FunctionKey)], 91 | UInt(kVK_F7) : [String(unicodeInt: NSF7FunctionKey)], 92 | UInt(kVK_F8) : [String(unicodeInt: NSF8FunctionKey)], 93 | UInt(kVK_F9) : [String(unicodeInt: NSF9FunctionKey)], 94 | UInt(kVK_F10) : [String(unicodeInt: NSF10FunctionKey)], 95 | UInt(kVK_F11) : [String(unicodeInt: NSF11FunctionKey)], 96 | UInt(kVK_F12) : [String(unicodeInt: NSF12FunctionKey)], 97 | UInt(kVK_F13) : [String(unicodeInt: NSF13FunctionKey)], 98 | UInt(kVK_F14) : [String(unicodeInt: NSF14FunctionKey)], 99 | UInt(kVK_F15) : [String(unicodeInt: NSF15FunctionKey)], 100 | UInt(kVK_F16) : [String(unicodeInt: NSF16FunctionKey)], 101 | UInt(kVK_F17) : [String(unicodeInt: NSF17FunctionKey)], 102 | UInt(kVK_F18) : [String(unicodeInt: NSF18FunctionKey)], 103 | UInt(kVK_F19) : [String(unicodeInt: NSF19FunctionKey)], 104 | UInt(kVK_F20) : [String(unicodeInt: NSF20FunctionKey)], 105 | UInt(kVK_Space) : ["\u{20}"], 106 | UInt(kVK_Delete) : [String(unicodeInt: NSBackspaceCharacter)], 107 | UInt(kVK_ForwardDelete) : [String(unicodeInt: NSDeleteCharacter)], 108 | UInt(kVK_ANSI_KeypadClear) : [String(unicodeInt: NSClearLineFunctionKey)], 109 | UInt(kVK_LeftArrow) : [String(unicodeInt: NSLeftArrowFunctionKey), "\u{2190}"], 110 | UInt(kVK_RightArrow) : [String(unicodeInt: NSRightArrowFunctionKey), "\u{2192}"], 111 | UInt(kVK_UpArrow) : [String(unicodeInt: NSUpArrowFunctionKey), "\u{2191}"], 112 | UInt(kVK_DownArrow) : [String(unicodeInt: NSDownArrowFunctionKey), "\u{2193}"], 113 | UInt(kVK_End) : [String(unicodeInt: NSEndFunctionKey)], 114 | UInt(kVK_Home) : [String(unicodeInt: NSHomeFunctionKey)], 115 | UInt(kVK_Escape) : ["\u{1b}"], 116 | UInt(kVK_PageDown) : [String(unicodeInt: NSPageDownFunctionKey)], 117 | UInt(kVK_PageUp) : [String(unicodeInt: NSPageUpFunctionKey)], 118 | UInt(kVK_Return) : [String(unicodeInt: NSCarriageReturnCharacter)], 119 | UInt(kVK_ANSI_KeypadEnter) : [String(unicodeInt: NSEnterCharacter)], 120 | UInt(kVK_Tab) : [String(unicodeInt: NSTabCharacter)], 121 | UInt(kVK_Help) : [String(unicodeInt: NSHelpFunctionKey)], 122 | UInt(kVK_Function) : ["\u{F747}"] 123 | ] 124 | } 125 | 126 | struct Preferences { 127 | // Identifier used to present Keys in Preferences window. 128 | static let keysIdentifier = NSToolbarItem.Identifier(rawValue: "keysForSketch") 129 | // Keys used to store settings in Preferences 130 | static let kUserKeyEquivalents = "NSUserKeyEquivalents" as CFString 131 | static let kCustomMenuApps = "com.apple.custommenu.apps" as CFString 132 | static let kUniversalAccess = "com.apple.universalaccess" as CFString 133 | } 134 | 135 | struct Licensing { 136 | /// Key for local storing of Licensing Info in UserDefaults. 137 | static let kLicensingInfoDefaultsDict = "VDKLicensingInfo" 138 | } 139 | } 140 | 141 | fileprivate extension String { 142 | /// Init with unicode int value. 143 | init(unicodeInt: Int) { 144 | self = String(format: "%C", unicodeInt) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Source/Support/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 06/07/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | /// Execute given closure in `#DEBUG` configuration. 10 | func inDebug(_ closure: () -> () ) { 11 | #if DEBUG 12 | closure() 13 | #endif 14 | } 15 | 16 | // MARK: Assertions 17 | 18 | /// Fast `non-nil` assert. 19 | func assertNonNil(_ object: AnyObject?) { 20 | assert(object != nil, "Object is nil.") 21 | } 22 | 23 | /// Fast `respondsToSelector` assert. 24 | func assert(_ object: AnyObject, respondsTo selector: Selector) { 25 | assert(object.responds(to: selector), "Object is not responding to selector.") 26 | } 27 | 28 | /// Get a resource from asset catalog by name 29 | public func image(_ imageName: String) -> NSImage? { 30 | return Bundle(for:Keys.self).image(forResource: NSImage.Name(rawValue: imageName)) 31 | } 32 | 33 | // MARK: Other 34 | 35 | /// Pointer to internal static AppKit dictionary with cached user Key Equivalents. 36 | let appKeyEquivalents: UnsafeMutablePointer = { 37 | let addr = UInt(lorgnette_lookup(mach_task_self_, "sAppKeyEquivalents")) 38 | return UnsafeMutablePointer(bitPattern:addr)! 39 | }() 40 | 41 | -------------------------------------------------------------------------------- /Source/Support/JRSwizzle/VDK_JRSwizzle.h: -------------------------------------------------------------------------------- 1 | // JRSwizzle.h semver:1.1.0 2 | // Copyright (c) 2007-2016 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/mit 4 | // https://github.com/rentzsch/jrswizzle 5 | 6 | #import 7 | 8 | @interface NSObject (VDK_JRSwizzle) 9 | 10 | + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; 11 | + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; 12 | 13 | 14 | /** 15 | ``` 16 | __block NSInvocation *invocation = nil; 17 | invocation = [self jr_swizzleMethod:@selector(initWithCoder:) withBlock:^(id obj, NSCoder *coder) { 18 | NSLog(@"before %@, coder %@", obj, coder); 19 | 20 | [invocation setArgument:&coder atIndex:2]; 21 | [invocation invokeWithTarget:obj]; 22 | 23 | id ret = nil; 24 | [invocation getReturnValue:&ret]; 25 | 26 | NSLog(@"after %@, coder %@", obj, coder); 27 | 28 | return ret; 29 | } error:nil]; 30 | ``` 31 | */ 32 | + (NSInvocation*)jr_swizzleMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error; 33 | 34 | /** 35 | ``` 36 | __block NSInvocation *classInvocation = nil; 37 | classInvocation = [self jr_swizzleClassMethod:@selector(test) withBlock:^() { 38 | NSLog(@"before"); 39 | 40 | [classInvocation invoke]; 41 | 42 | NSLog(@"after"); 43 | } error:nil]; 44 | ``` 45 | */ 46 | + (NSInvocation*)jr_swizzleClassMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/Support/Lorgnette/lorgnette-structs.h: -------------------------------------------------------------------------------- 1 | // 2 | // lorgnette-structs.h 3 | // liblorgnette 4 | // 5 | // Created by Dmitry Rodionov on 9/26/14. 6 | // Copyright (c) 2014 rodionovd. All rights reserved. 7 | // 8 | 9 | #pragma once 10 | 11 | struct dyld_image_info_32 { 12 | uint32_t imageLoadAddress; 13 | uint32_t imageFilePath; 14 | uint32_t imageFileModDate; 15 | }; 16 | 17 | struct load_command_with_segname { 18 | uint32_t cmd; 19 | uint32_t cmdsize; 20 | uint32_t segname; 21 | }; 22 | 23 | struct dyld_image_info_64 { 24 | uint64_t imageLoadAddress; 25 | uint64_t imageFilePath; 26 | uint64_t imageFileModDate; 27 | }; 28 | 29 | struct dyld_all_image_infos_32 { 30 | uint32_t version; 31 | uint32_t infoArrayCount; 32 | uint32_t infoArray; 33 | uint32_t notification; 34 | bool processDetachedFromSharedRegion; 35 | bool libSystemInitialized; 36 | uint32_t dyldImageLoadAddress; 37 | uint32_t jitInfo; 38 | uint32_t dyldVersion; 39 | uint32_t errorMessage; 40 | uint32_t terminationFlags; 41 | uint32_t coreSymbolicationShmPage; 42 | uint32_t systemOrderFlag; 43 | uint32_t uuidArrayCount; 44 | uint32_t uuidArray; 45 | uint32_t dyldAllImageInfosAddress; 46 | uint32_t initialImageCount; 47 | uint32_t errorKind; 48 | uint32_t errorClientOfDylibPath; 49 | uint32_t errorTargetDylibPath; 50 | uint32_t errorSymbol; 51 | uint32_t sharedCacheSlide; 52 | uint8_t sharedCacheUUID[16]; 53 | uint32_t sharedCacheBaseAddress; 54 | uint64_t infoArrayChangeTimestamp; 55 | uint32_t dyldPath; 56 | uint32_t notifyMachPorts[8]; 57 | uint32_t reserved[5]; 58 | uint32_t compact_dyld_image_info_addr; 59 | uint32_t compact_dyld_image_info_size; 60 | }; 61 | 62 | struct dyld_all_image_infos_64 { 63 | uint32_t version; 64 | uint32_t infoArrayCount; 65 | uint64_t infoArray; 66 | uint64_t notification; 67 | bool processDetachedFromSharedRegion; 68 | bool libSystemInitialized; 69 | uint32_t paddingToMakeTheSizeCorrectOn32bitAndDoesntAffect64b; // NOT PART OF DYLD_ALL_IMAGE_INFOS! 70 | uint64_t dyldImageLoadAddress; 71 | uint64_t jitInfo; 72 | uint64_t dyldVersion; 73 | uint64_t errorMessage; 74 | uint64_t terminationFlags; 75 | uint64_t coreSymbolicationShmPage; 76 | uint64_t systemOrderFlag; 77 | uint64_t uuidArrayCount; 78 | uint64_t uuidArray; 79 | uint64_t dyldAllImageInfosAddress; 80 | uint64_t initialImageCount; 81 | uint64_t errorKind; 82 | uint64_t errorClientOfDylibPath; 83 | uint64_t errorTargetDylibPath; 84 | uint64_t errorSymbol; 85 | uint64_t sharedCacheSlide; 86 | uint8_t sharedCacheUUID[16]; 87 | uint64_t sharedCacheBaseAddress; 88 | uint64_t infoArrayChangeTimestamp; 89 | uint64_t dyldPath; 90 | uint32_t notifyMachPorts[8]; 91 | uint64_t reserved[9]; 92 | uint64_t compact_dyld_image_info_addr; 93 | uint64_t compact_dyld_image_info_size; 94 | }; 95 | -------------------------------------------------------------------------------- /Source/Support/Lorgnette/lorgnette.h: -------------------------------------------------------------------------------- 1 | // 2 | // lorgnette.h 3 | // liblorgnette 4 | // 5 | // Created by Dmitry Rodionov on 9/24/14. 6 | // Copyright (c) 2014 rodionovd. All rights reserved. 7 | // 8 | #include 9 | #pragma once 10 | /** 11 | * @abstract 12 | * Locate a symbol inside an arbitrary process' address space. 13 | * 14 | * @note 15 | * This function iterates local symbols first and only then it looks for symbols 16 | * in linked libraries. 17 | * 18 | * @param target 19 | * The target process to inspect. 20 | * @param symbol_name 21 | * The name of the symbol to find. This parameter must not be NULL. 22 | * 23 | * @return 24 | * An address of the given symbol within the given process, or 0 (zero) if this symbol 25 | * could not be found. 26 | * 27 | * @b Examples 28 | * 29 | * Find a @p dlopen symbol address within the current task memory space 30 | * @code 31 | * addr = lorgnette_lookup(mach_task_self(), "dlopen"); 32 | * @endcode 33 | * Find a @p glob_var0 symbol address inside a remote process 34 | * @code 35 | * addr = lorgnette_lookup(some_task, "glob_var0"); 36 | * @endcode 37 | */ 38 | mach_vm_address_t lorgnette_lookup(task_t target, const char *symbol_name); 39 | 40 | /** 41 | * @abstract 42 | * Locate a symbol within a particular image inside an alien process. 43 | * 44 | * @param target 45 | * The target process to inspect. 46 | * @param symbol_name 47 | * The name of the symbol to find. This parameter must not be NULL. 48 | * @param image_name 49 | * The name of the host image of the given symbol. This may be NULL. 50 | * The image name should be either a full file path or just a file base name. 51 | * 52 | * @return 53 | * An address of the given symbol within the given process, or 0 (zero) if this symbol 54 | * could not be found [within the given image, if @p image_name is not NULL]. 55 | * 56 | * @see lorgnette_lookup() 57 | */ 58 | mach_vm_address_t lorgnette_lookup_image(task_t target, const char *symbol_name, const char *image_name); 59 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/MASShortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/MASShortcut -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASDictionaryTransformer.h: -------------------------------------------------------------------------------- 1 | extern NSString *const MASDictionaryTransformerName; 2 | 3 | /** 4 | Converts shortcuts for storage in user defaults. 5 | 6 | User defaults can’t stored custom types directly, they have to 7 | be serialized to `NSData` or some other supported type like an 8 | `NSDictionary`. In Cocoa Bindings, the conversion can be done 9 | using value transformers like this one. 10 | 11 | There’s a built-in transformer (`NSKeyedUnarchiveFromDataTransformerName`) 12 | that converts any `NSCoding` types to `NSData`, but with shortcuts 13 | it makes sense to use a dictionary instead – the defaults look better 14 | when inspected with the `defaults` command-line utility and the 15 | format is compatible with an older sortcut library called Shortcut 16 | Recorder. 17 | */ 18 | @interface MASDictionaryTransformer : NSValueTransformer 19 | @end 20 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASHotKey.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | extern FourCharCode const MASHotKeySignature; 4 | 5 | @interface MASHotKey : NSObject 6 | 7 | @property(readonly) UInt32 carbonID; 8 | @property(copy) dispatch_block_t action; 9 | 10 | + (instancetype) registeredHotKeyWithShortcut: (MASShortcut*) shortcut; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASKeyCodes.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "MASKeyMasks.h" 4 | 5 | // These glyphs are missed in Carbon.h 6 | enum { 7 | kMASShortcutGlyphEject = 0x23CF, 8 | kMASShortcutGlyphClear = 0x2715, 9 | kMASShortcutGlyphDeleteLeft = 0x232B, 10 | kMASShortcutGlyphDeleteRight = 0x2326, 11 | kMASShortcutGlyphLeftArrow = 0x2190, 12 | kMASShortcutGlyphRightArrow = 0x2192, 13 | kMASShortcutGlyphUpArrow = 0x2191, 14 | kMASShortcutGlyphDownArrow = 0x2193, 15 | kMASShortcutGlyphEscape = 0x238B, 16 | kMASShortcutGlyphHelp = 0x003F, 17 | kMASShortcutGlyphPageDown = 0x21DF, 18 | kMASShortcutGlyphPageUp = 0x21DE, 19 | kMASShortcutGlyphTabRight = 0x21E5, 20 | kMASShortcutGlyphReturn = 0x2305, 21 | kMASShortcutGlyphReturnR2L = 0x21A9, 22 | kMASShortcutGlyphPadClear = 0x2327, 23 | kMASShortcutGlyphNorthwestArrow = 0x2196, 24 | kMASShortcutGlyphSoutheastArrow = 0x2198, 25 | }; 26 | 27 | NS_INLINE NSString* NSStringFromMASKeyCode(unsigned short ch) 28 | { 29 | return [NSString stringWithFormat:@"%C", ch]; 30 | } 31 | 32 | NS_INLINE NSUInteger MASPickCocoaModifiers(NSUInteger flags) 33 | { 34 | return (flags & (NSEventModifierFlagControl | NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand)); 35 | } 36 | 37 | NS_INLINE UInt32 MASCarbonModifiersFromCocoaModifiers(NSUInteger cocoaFlags) 38 | { 39 | return 40 | (cocoaFlags & NSEventModifierFlagCommand ? cmdKey : 0) 41 | | (cocoaFlags & NSEventModifierFlagOption ? optionKey : 0) 42 | | (cocoaFlags & NSEventModifierFlagControl ? controlKey : 0) 43 | | (cocoaFlags & NSEventModifierFlagShift ? shiftKey : 0); 44 | } 45 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASKeyMasks.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | // https://github.com/shpakovski/MASShortcut/issues/99 4 | // 5 | // Long story short: NSControlKeyMask and friends were replaced with NSEventModifierFlagControl 6 | // and similar in macOS Sierra. The project builds fine & clean, but including MASShortcut in 7 | // a project with deployment target set to 10.12 results in several deprecation warnings because 8 | // of the control masks. Simply replacing the old symbols with the new ones isn’t an option, 9 | // since it breaks the build on older SDKs – in Travis, for example. 10 | // 11 | // It should be safe to remove this whole thing once the 10.12 SDK is ubiquitous. 12 | 13 | #if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 14 | #define NSEventModifierFlagCommand NSCommandKeyMask 15 | #define NSEventModifierFlagControl NSControlKeyMask 16 | #define NSEventModifierFlagOption NSAlternateKeyMask 17 | #define NSEventModifierFlagShift NSShiftKeyMask 18 | #endif 19 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASLocalization.h: -------------------------------------------------------------------------------- 1 | /** 2 | Reads a localized string from the framework’s bundle. 3 | 4 | Normally you would use NSLocalizedString to read the localized 5 | strings, but that’s just a shortcut for loading the strings from 6 | the main bundle. And once the framework ends up in an app, the 7 | main bundle will be the app’s bundle and won’t contain our strings. 8 | So we introduced this helper function that makes sure to load the 9 | strings from the framework’s bundle. Please avoid using 10 | NSLocalizedString throughout the framework, it wouldn’t work 11 | properly. 12 | */ 13 | NSString *MASLocalizedString(NSString *key, NSString *comment); -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcut-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "MASDictionaryTransformer.h" 14 | #import "MASHotKey.h" 15 | #import "MASKeyCodes.h" 16 | #import "MASKeyMasks.h" 17 | #import "MASLocalization.h" 18 | #import "MASShortcut.h" 19 | #import "MASShortcutBinder.h" 20 | #import "MASShortcutMonitor.h" 21 | #import "MASShortcutValidator.h" 22 | #import "MASShortcutView+Bindings.h" 23 | #import "MASShortcutView.h" 24 | #import "Shortcut.h" 25 | 26 | FOUNDATION_EXPORT double MASShortcutVersionNumber; 27 | FOUNDATION_EXPORT const unsigned char MASShortcutVersionString[]; 28 | 29 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcut.h: -------------------------------------------------------------------------------- 1 | #import "MASKeyCodes.h" 2 | 3 | /** 4 | A model class to hold a key combination. 5 | 6 | This class just represents a combination of keys. It does not care if 7 | the combination is valid or can be used as a hotkey, it doesn’t watch 8 | the input system for the shortcut appearance, nor it does access user 9 | defaults. 10 | */ 11 | @interface MASShortcut : NSObject 12 | 13 | /** 14 | The virtual key code for the keyboard key. 15 | 16 | Hardware independent, same as in `NSEvent`. See `Events.h` in the HIToolbox 17 | framework for a complete list, or Command-click this symbol: `kVK_ANSI_A`. 18 | */ 19 | @property (nonatomic, readonly) NSUInteger keyCode; 20 | 21 | /** 22 | Cocoa keyboard modifier flags. 23 | 24 | Same as in `NSEvent`: `NSCommandKeyMask`, `NSAlternateKeyMask`, etc. 25 | */ 26 | @property (nonatomic, readonly) NSUInteger modifierFlags; 27 | 28 | /** 29 | Same as `keyCode`, just a different type. 30 | */ 31 | @property (nonatomic, readonly) UInt32 carbonKeyCode; 32 | 33 | /** 34 | Carbon modifier flags. 35 | 36 | A bit sum of `cmdKey`, `optionKey`, etc. 37 | */ 38 | @property (nonatomic, readonly) UInt32 carbonFlags; 39 | 40 | /** 41 | A string representing the “key” part of a shortcut, like the `5` in `⌘5`. 42 | 43 | @warning The value may change depending on the active keyboard layout. 44 | For example for the `^2` keyboard shortcut (`kVK_ANSI_2+NSControlKeyMask` 45 | to be precise) the `keyCodeString` is `2` on the US keyboard, but `ě` when 46 | the Czech keyboard layout is active. See the spec for details. 47 | */ 48 | @property (nonatomic, readonly) NSString *keyCodeString; 49 | 50 | /** 51 | A key-code string used in key equivalent matching. 52 | 53 | For precise meaning of “key equivalents” see the `keyEquivalent` 54 | property of `NSMenuItem`. Here the string is used to support shortcut 55 | validation (“is the shortcut already taken in this menu?”) and 56 | for display in `NSMenu`. 57 | 58 | The value of this property may differ from `keyCodeString`. For example 59 | the Russian keyboard has a `Г` (Ge) Cyrillic character in place of the 60 | latin `U` key. This means you can create a `^Г` shortcut, but in menus 61 | that’s always displayed as `^U`. So the `keyCodeString` returns `Г` 62 | and `keyCodeStringForKeyEquivalent` returns `U`. 63 | */ 64 | @property (nonatomic, readonly) NSString *keyCodeStringForKeyEquivalent; 65 | 66 | /** 67 | A string representing the shortcut modifiers, like the `⌘` in `⌘5`. 68 | */ 69 | @property (nonatomic, readonly) NSString *modifierFlagsString; 70 | 71 | - (instancetype)initWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags; 72 | + (instancetype)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags; 73 | 74 | /** 75 | Creates a new shortcut from an `NSEvent` object. 76 | 77 | This is just a convenience initializer that reads the key code and modifiers from an `NSEvent`. 78 | */ 79 | + (instancetype)shortcutWithEvent:(NSEvent *)anEvent; 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcutBinder.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcutMonitor.h" 2 | 3 | /** 4 | Binds actions to user defaults keys. 5 | 6 | If you store shortcuts in user defaults (for example by binding 7 | a `MASShortcutView` to user defaults), you can use this class to 8 | connect an action directly to a user defaults key. If the shortcut 9 | stored under the key changes, the action will get automatically 10 | updated to the new one. 11 | 12 | This class is mostly a wrapper around a `MASShortcutMonitor`. It 13 | watches the changes in user defaults and updates the shortcut monitor 14 | accordingly with the new shortcuts. 15 | */ 16 | @interface MASShortcutBinder : NSObject 17 | 18 | /** 19 | A convenience shared instance. 20 | 21 | You may use it so that you don’t have to manage an instance by hand, 22 | but it’s perfectly fine to allocate and use a separate instance instead. 23 | */ 24 | + (instancetype) sharedBinder; 25 | 26 | /** 27 | The underlying shortcut monitor. 28 | */ 29 | @property(strong) MASShortcutMonitor *shortcutMonitor; 30 | 31 | /** 32 | Binding options customizing the access to user defaults. 33 | 34 | As an example, you can use `NSValueTransformerNameBindingOption` to customize 35 | the storage format used for the shortcuts. By default the shortcuts are converted 36 | from `NSData` (`NSKeyedUnarchiveFromDataTransformerName`). Note that if the 37 | binder is to work with `MASShortcutView`, both object have to use the same storage 38 | format. 39 | */ 40 | @property(copy) NSDictionary *bindingOptions; 41 | 42 | /** 43 | Binds given action to a shortcut stored under the given defaults key. 44 | 45 | In other words, no matter what shortcut you store under the given key, 46 | pressing it will always trigger the given action. 47 | */ 48 | - (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action; 49 | 50 | /** 51 | Disconnect the binding between user defaults and action. 52 | 53 | In other words, the shortcut stored under the given key will no longer trigger an action. 54 | */ 55 | - (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName; 56 | 57 | /** 58 | Register default shortcuts in user defaults. 59 | 60 | This is a convenience frontent to `[NSUserDefaults registerDefaults]`. 61 | The dictionary should contain a map of user defaults’ keys to appropriate 62 | keyboard shortcuts. The shortcuts will be transformed according to 63 | `bindingOptions` and registered using `registerDefaults`. 64 | */ 65 | - (void) registerDefaultShortcuts: (NSDictionary*) defaultShortcuts; 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcutMonitor.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | /** 4 | Executes action when a shortcut is pressed. 5 | 6 | There can only be one instance of this class, otherwise things 7 | will probably not work. (There’s a Carbon event handler inside 8 | and there can only be one Carbon event handler of a given type.) 9 | */ 10 | @interface MASShortcutMonitor : NSObject 11 | 12 | - (instancetype) init __unavailable; 13 | + (instancetype) sharedMonitor; 14 | 15 | /** 16 | Register a shortcut along with an action. 17 | 18 | Attempting to insert an already registered shortcut probably won’t work. 19 | It may burn your house or cut your fingers. You have been warned. 20 | */ 21 | - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action; 22 | - (BOOL) isShortcutRegistered: (MASShortcut*) shortcut; 23 | 24 | - (void) unregisterShortcut: (MASShortcut*) shortcut; 25 | - (void) unregisterAllShortcuts; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcutValidator.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | /** 4 | This class is used by the recording control to tell which shortcuts are acceptable. 5 | 6 | There are two kinds of shortcuts that are not considered acceptable: shortcuts that 7 | are too simple (like single letter keys) and shortcuts that are already used by the 8 | operating system. 9 | */ 10 | @interface MASShortcutValidator : NSObject 11 | 12 | /** 13 | Set to `YES` if you want to accept Option-something shortcuts. 14 | 15 | `NO` by default, since Option-something shortcuts are often used by system, 16 | for example Option-G will type the © sign. This also applies to Option-Shift 17 | shortcuts – in other words, shortcut recorder will not accept shortcuts like 18 | Option-Shift-K by default. (Again, since Option-Shift-K inserts the Apple 19 | logo sign by default.) 20 | */ 21 | @property(assign) BOOL allowAnyShortcutWithOptionModifier; 22 | 23 | + (instancetype) sharedValidator; 24 | 25 | - (BOOL) isShortcutValid: (MASShortcut*) shortcut; 26 | - (BOOL) isShortcut: (MASShortcut*) shortcut alreadyTakenInMenu: (NSMenu*) menu explanation: (NSString**) explanation; 27 | - (BOOL) isShortcutAlreadyTakenBySystem: (MASShortcut*) shortcut explanation: (NSString**) explanation; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcutView+Bindings.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcutView.h" 2 | 3 | /** 4 | A simplified interface to bind the recorder value to user defaults. 5 | 6 | You can bind the `shortcutValue` to user defaults using the standard 7 | `bind:toObject:withKeyPath:options:` call, but since that’s a lot to type 8 | and read, here’s a simpler option. 9 | 10 | Setting the `associatedUserDefaultsKey` binds the view’s shortcut value 11 | to the given user defaults key. You can supply a value transformer to convert 12 | values between user defaults and `MASShortcut`. If you don’t supply 13 | a transformer, the `NSUnarchiveFromDataTransformerName` will be used 14 | automatically. 15 | 16 | Set `associatedUserDefaultsKey` to `nil` to disconnect the binding. 17 | */ 18 | @interface MASShortcutView (Bindings) 19 | 20 | @property(copy) NSString *associatedUserDefaultsKey; 21 | 22 | - (void) setAssociatedUserDefaultsKey: (NSString*) newKey withTransformer: (NSValueTransformer*) transformer; 23 | - (void) setAssociatedUserDefaultsKey: (NSString*) newKey withTransformerName: (NSString*) transformerName; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/MASShortcutView.h: -------------------------------------------------------------------------------- 1 | @class MASShortcut, MASShortcutValidator; 2 | 3 | extern NSString *const MASShortcutBinding; 4 | 5 | typedef enum { 6 | MASShortcutViewStyleDefault = 0, // Height = 19 px 7 | MASShortcutViewStyleTexturedRect, // Height = 25 px 8 | MASShortcutViewStyleRounded, // Height = 43 px 9 | MASShortcutViewStyleFlat 10 | } MASShortcutViewStyle; 11 | 12 | @interface MASShortcutView : NSView 13 | 14 | @property (nonatomic, strong) MASShortcut *shortcutValue; 15 | @property (nonatomic, strong) MASShortcutValidator *shortcutValidator; 16 | @property (nonatomic, getter = isRecording) BOOL recording; 17 | @property (nonatomic, getter = isEnabled) BOOL enabled; 18 | @property (nonatomic, copy) void (^shortcutValueChange)(MASShortcutView *sender); 19 | @property (nonatomic, assign) MASShortcutViewStyle style; 20 | 21 | /// Returns custom class for drawing control. 22 | + (Class)shortcutCellClass; 23 | 24 | - (void)setAcceptsFirstResponder:(BOOL)value; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Headers/Shortcut.h: -------------------------------------------------------------------------------- 1 | #import "MASKeyMasks.h" 2 | #import "MASShortcut.h" 3 | #import "MASShortcutValidator.h" 4 | #import "MASShortcutMonitor.h" 5 | #import "MASShortcutBinder.h" 6 | #import "MASDictionaryTransformer.h" 7 | #import "MASShortcutView.h" 8 | #import "MASShortcutView+Bindings.h" 9 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/MASShortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/Versions/A/MASShortcut -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MASShortcut { 2 | umbrella header "MASShortcut-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 17A405 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | MASShortcut 11 | CFBundleIdentifier 12 | org.cocoapods.MASShortcut 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | MASShortcut 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 2.3.6 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 9A235 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 17A360 37 | DTSDKName 38 | macosx10.13 39 | DTXcode 40 | 0900 41 | DTXcodeBuild 42 | 9A235 43 | 44 | 45 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 17A405 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleIdentifier 10 | org.cocoapods.MASShortcut 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | MASShortcut 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 2.3.6 19 | CFBundleSignature 20 | ???? 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 9A235 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 17A360 35 | DTSDKName 36 | macosx10.13 37 | DTXcode 38 | 0900 39 | DTXcodeBuild 40 | 9A235 41 | 42 | 43 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/cs.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Zrušit"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Kliknutím nahrajete novou zkratku"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Smazat zkratku"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "klávesová zkratka"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Nahrát zkratku"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "zkratka smazána"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "zkratka nastavena"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Mezerník"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "Kombinace %@ se nedá použít"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Tato zkratka se nedá použít, protože už ji obsadil systém.\nKdybyste na ní trvali, většina systémových zkratek se dá přenastavit v Předvolbách systému."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Tato zkratka se nedá použít, protože už je použita pro menu (%@)."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Pokud chcete nahrát novou zkratku, stiskněte toto tlačítko a následně vybranou zkratku. Stisknutím Delete vymažete stávající zkratku."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Stiskněte zkratku"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Stiskněte zkratku"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Vrátit se k původní"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Abbrechen"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Klicken um neuen Kurzbefehl aufzunehmen"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Kurzbefehl Löschen"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "Kurzbefehl"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Kurzbefehl aufnehmen"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Kurzbefehl gelöscht"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Kurzbefehl gesetzt"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Leertaste"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "Die Tastenkombination %@ kann nicht genutzt werden"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Diese Kombination kann nicht genutzt werden, weil sie bereits als systemweiter Kurzbefehl genutzt wird.\nFalls du diese Tastenkombination wirklich benutzen willst, können die meisten Kurzbefehle in den Tastatur Systemeinstellungen geändert werden."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Dieser Kurzbefehl kann nicht genutzt werden, weil er bereits vom Menüpunkt „%@“ genutzt wird."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Drücke diesen Button, um einen neuen Kurzbefehl aufzunehmen. Tippe dann den neuen Kurzbefehl oder drücke Löschen, um den aktuellen Kurzbefehl zu löschen."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Neuen eingeben"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Kurzbefehl eingeben"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Alten nutzen"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Cancel"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Click to record new shortcut"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Delete shortcut"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "keyboard shortcut"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Record Shortcut"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Shortcut cleared"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Shortcut set"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Space"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "The key combination %@ cannot be used"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "This shortcut cannot be used because it is already used by the menu item ‘%@’."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Type New Shortcut"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Type Shortcut"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Use Old Shortcut"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Cancelar"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Haga clic para grabar nuevo atajo"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Borrar atajo"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "atajo de teklado"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Grabar atajo"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Atajo borrado"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Atajo creado"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Espacio"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "La combinación de claves %@ no se puede utilizada"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Esta combinación no se puede utilizar debido a que ya es en us como atajo del sistema.\nSi realmente desea utilizar esta combinación de teclas, la mayoría de los atajos se puede cambiar en el panel de Teclado y Ratón de Preferencias del Sistema."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Este atajo no se puede utilizar debido a que ya es utilizado por el elemento de menú '%@'."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Para grabar un nuevo atajo, haga clic en este botón, a continuar, escriba el nuevo atajo, o pulse borrar para qutar un atajo existente."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Escribir atajo"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Escribir atajo"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Usa atajo anterior"; -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Annuler"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Cliquez pour enregistrer le raccourci"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Supprimer le raccourci"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "raccourci clavier"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Enregistrer le raccourci"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Raccourci supprimé"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Raccourci configuré"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Espace"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "La combinaison %@ ne peut être utilisée"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Cette combinaison de touches ne peut être utilisée parce qu’elle est réservée pour un raccourci du système.\nSi vous désirez l’utiliser, la plupart des raccourcis peuvent être modifiés dans l’onglet Clavier, dans Préférences Système."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Ce raccourci ne peut être utilisé parce qu’il est déjà utilisé par le point de menu «%@»."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Pour enregistrer un nouveau raccourci, cliquez sur ce bouton et tapez le nouveau raccourci, ou bien, tapez sur «Supprimer» pour supprimer le raccourci configuré."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Saisir un raccourci"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Saisir un raccourci"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Revenir au raccourci précédent"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Annulla"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Cliccare per registrare una nuova combinazione"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Cancella scorciatoia"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "Scorciatoia da tastiera"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Registra scorciatoia"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Scorciatoia rimossa"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Scorciatoia impostata"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Spazio"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "Questa combinazione %@ di tasti non può essere usata"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Questa combinazione di tasti non può essere usata perchè è già usata da una scorciatoia da tastiera a livello di Sistema.\nSe volete davvero usare questa combinazione di tasti, la maggior parte delle scorciatoie possono essere cambiate nel pannello Tastiera e Mouse delle Preferenze di Sistema."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Questa combinazione di tasti non può essere usata perchè è già usata dalla voce di menù ‘%@’."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Per registrare una nuova scorciatoia, cliccare su questo pulsante e poi inserire la muova scorciatoia o premere cancella per resettare una scorciatoia esistente."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Digita nuova"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Digita scorciatoia"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Usare vecchia"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "キャンセルする"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "クリックしてショートカットを入力"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "ショートカットを削除"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "キーボードショートカット"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "ショートカットを入力"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "ショートカットが削除されました"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "ショートカットが設定されました"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "スペース"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "%@ はショートカットに設定できません"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "このショートカットは、システム全体で使用されているショートカットのため、設定することができません。\nもしこのショートカットを使用したい場合、「システム環境設定」の「キーボード」、「マウス」のパネルから既に設定されているショートカットを変更してください。"; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "このショートカットは、メニュー操作の「%@」で使われているため、設定できません。"; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "このボタンをクリックし、ショートカットを入力すると、新しいショートカットが設定されます。また、削除ボタンをおすと、ショートカットが削除されます。"; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "ショートカットを入力"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "ショートカットを入力"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "古いショートカットを使用"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "취소"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "클릭해 단축 키를 입력"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "단축키 삭제"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "키보드 단축키"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "좋아"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "단축키 입력"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "단축키 삭제됨"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "단축키 설정됨"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "스페이스 바"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "%@ 단축키로 설정할 수 없습니다"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "이 결합은 시스템 전체에서 사용 때문에 단축키로 설정 할 수 없습니다.\n단축키를 사용하고 싶으면 시스템 환경 설정의 키보드, 마우스 패널에서 이미 설정되어있는 단축키를 변경하십시오."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "이 단축키는 ‘%@’ 메뉴 아이템에 사용되고 있기 때문에 설정할 수 없습니다."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "이 버튼을 클릭하고 단축키를 입력하면 새로운 단축키가 설정됩니다. 또한 삭제 버튼을 누르면 단축키가 삭제됩니다."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "새 단축키 입력"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "단축키 입력"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "오래된 단축키를 사용"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Verbreken"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Druk om een nieuwe sneltoets in te voeren"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Verwijder sneltoets"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "sneltoets"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Sneltoets opnemen"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Sneltoets verwijderd"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Sneltoets zetten"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Spatie"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "De sneltoetsencombinatie kan niet worden gebruikt"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Deze combinatie kan niet worden gebruikt want hij wordt al gebruikt door een systeembreed sneltoets.\nAls je deze combinatie echt wilt gebruiken, kun je de meeste sneltoetsen binnen Toetsenbordinstellingen veranderen."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Deze sneltoets kan niet worden gebruikt want hij wordt al gebruikt door het menu item ‘%@’."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Om nieuwe sneltoets op te nemen, druk op deze knop, en voer een nieuwe sneltoets in, of druk op verwijder om bestaande sneltoets te verwijderen."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Voer Nieuwe Sneltoets in"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Voer Sneltoets in"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Gebruik Oude Sneltoets"; -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Anuluj"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Kliknij, by ustawić nowy skrót"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Usuń skrót"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "skrót klawiszowy"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "OK"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Utwórz skrót"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Skrót usunięty"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Skrót ustawiony"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Spacja"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "Nie można użyć kombinacji klawiszy %@"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Nie można użyć tej kombinacji, ponieważ jest już zajęta przez skrót systemowy.\nMożesz to zmienić w panelu Klawiatura w Preferencjach systemowych."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Ten skrót nie może być użyty, ponieważ w menu ma już przypisaną funkcję ‘%@’."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Aby ustawić nowy skrót, użyj tego przycisku, a następnie wpisz nowy skrót albo naciśnij klawisz delete, by usunąć istniejący skrót"; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Wpisz nowy skrót"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Wpisz skrót"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Użyj starego skrótu"; -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "Отмена"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "Нажмите для записи сочетания клавиш"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "Удалить горячую клавишу"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "сочетание клавиш"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "ОК"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "Ввести сочетание"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "Сочетание клавиш удалено"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "Сочетание клавиш назначено"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "Пробел"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "Нельзя использовать сочетание клавиш %@"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "Нельзя использовать это сочетание клавиш, потому что оно уже используется в системе.\n Если вы хотите использовать это сочетание, измените существующее системное сочетание клавиш через панель Клавиатура в Cистемных настройках."; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "Нельзя использовать это сочетание, потому что оно уже связано с элементом ‘%@’."; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "Чтобы назначить новое сочетание клавиш, нажмите эту кнопку и введите новое сочетание, или нажмите \"Удалить\", чтобы удалить действующее сочетание клавиш."; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "Введите сочетание"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "Введите сочетание"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "Вернуть старое"; -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "取消"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "点击以记录新快捷键"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "删除快捷键"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "键盘快捷键"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "好"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "记录快捷键"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "快捷键已清除"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "快捷键已设置"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "空格键"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "按键组合“%@”无法使用"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "当前按键组合无法使用,因为它已经用作其他系统全局快捷键。\n如果您真的想使用这个按键组合,大部分系统全局快捷键能在“系统偏好设置”里的“键盘”和“鼠标”面板中重设。"; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "当前快捷键无法使用,因为它已用作菜单项“%@”的快捷键。"; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "若要记录新的快捷键,单击此按钮,然后键入新的快捷键,或者按“delete键”删除已经存在的快捷键。"; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "键入新快捷键"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "键入快捷键"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "还原快捷键"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/Resources/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Cancel action button in recording state */ 2 | "Cancel" = "取消"; 3 | 4 | /* Tooltip for non-empty shortcut button */ 5 | "Click to record new shortcut" = "點選以記錄新快捷鍵"; 6 | 7 | /* Tooltip for hint button near the non-empty shortcut */ 8 | "Delete shortcut" = "刪除快捷鍵"; 9 | 10 | /* VoiceOver title */ 11 | "keyboard shortcut" = "鍵盤快捷鍵"; 12 | 13 | /* Alert button when shortcut is already used */ 14 | "OK" = "好"; 15 | 16 | /* Empty shortcut button in normal state */ 17 | "Record Shortcut" = "記錄快捷鍵"; 18 | 19 | /* VoiceOver: Shortcut cleared */ 20 | "Shortcut cleared" = "快捷鍵已清除"; 21 | 22 | /* VoiceOver: Shortcut set */ 23 | "Shortcut set" = "快捷鍵已設定"; 24 | 25 | /* Shortcut glyph name for SPACE key */ 26 | "Space" = "空格鍵"; 27 | 28 | /* Title for alert when shortcut is already used */ 29 | "The key combination %@ cannot be used" = "按鍵組合“%@”無法使用"; 30 | 31 | /* Message for alert when shortcut is already used by the system */ 32 | "This combination cannot be used because it is already used by a system-wide keyboard shortcut.\nIf you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences." = "當前按鍵組合無法使用,因為它已經用作其他系統全局快捷鍵。\n如果您真的想使用這個按鍵組合,大部分系統全局快捷鍵能在“系統偏好設定”裡的“鍵盤”和“滑鼠”面板中重設。"; 33 | 34 | /* Message for alert when shortcut is already used */ 35 | "This shortcut cannot be used because it is already used by the menu item ‘%@’." = "當前快捷鍵無法使用,因為它已用作選單項“%@”的快捷鍵。"; 36 | 37 | /* VoiceOver shortcut help */ 38 | "To record a new shortcut, click this button, and then type the new shortcut, or press delete to clear an existing shortcut." = "若要記錄新的快捷鍵,單擊此按鈕,然後鍵入新的快捷鍵,或者按“delete鍵”刪除已經存在的快捷鍵。"; 39 | 40 | /* Non-empty shortcut button in recording state */ 41 | "Type New Shortcut" = "鍵入新快捷鍵"; 42 | 43 | /* Empty shortcut button in recording state */ 44 | "Type Shortcut" = "鍵入快捷鍵"; 45 | 46 | /* Cancel action button for non-empty shortcut in recording state */ 47 | "Use Old Shortcut" = "還原快捷鍵"; 48 | -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeDirectory: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeDirectory -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeRequirements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeRequirements -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeRequirements-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeRequirements-1 -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeSignature: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exevil/Keys-For-Sketch/dfbc9ac3d67579802516b2a27da39dcec12e7306/Source/Support/MASShortcut.framework/Versions/A/Resources/MASShortcut.bundle/Contents/_CodeSignature/CodeSignature -------------------------------------------------------------------------------- /Source/Support/MASShortcut.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Source/Support/NSAlert+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAlert+additions.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 09/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | extension NSAlert { 10 | /** 11 | Display an alert sheet with given parameters and execute closure associated with pressed button. Buttons will shown on `NSAlert` in the same order in order that provides orderedButtonTitles array 12 | 13 | - parameters: 14 | - window: An NSWindow instance new sheet should be attached to. While nil value is passed method will firstly try to attach alert to NSApplication's mainWindow but if it's nil too alert will be shown separately using `runModal()` method. 15 | - style: 'NSAlert''s alertSytle. 16 | - icon: 'NSAlert''s icon. 17 | - messageText: `NSAlert's` messageText. 18 | - informativeText: `NSAlert's` informativeText. 19 | - orderedButtonTitles: Titles of alert buttons ordered from first to last one. 20 | - completionHandler: Closure with pressed button number as a parameter. Button number should correspond the button order in `orderedButtonTitles` parameter's array. 21 | */ 22 | class func showAlert(over window: NSWindow? = nil, 23 | style: NSAlert.Style = .informational, 24 | icon: NSImage? = image(Const.Image.keysIcon), 25 | messageText: String, 26 | informativeText: String, 27 | orderedButtonTitles: [String], 28 | completionHandler: ((_ pressedButtonNumber: Int) -> Void)? 29 | ) { 30 | 31 | // Make alert 32 | let alert = NSAlert() 33 | alert.alertStyle = style 34 | alert.icon = icon ?? alert.icon 35 | alert.messageText = messageText 36 | alert.informativeText = informativeText 37 | orderedButtonTitles.forEach { 38 | alert.addButton(withTitle: $0) 39 | } 40 | 41 | // Handle completion handler 42 | let alertCompletionHandler: (NSApplication.ModalResponse) -> () = { response in 43 | // Responeded buttons have incremental (by 1) indexes started from 1000 44 | let buttonNumber = response.rawValue - 1000 45 | completionHandler?(buttonNumber) 46 | } 47 | 48 | // Attach to window if possible and needed 49 | if let win = window { 50 | alert.beginSheetModal(for: win, completionHandler: alertCompletionHandler) 51 | } else { 52 | alertCompletionHandler(alert.runModal()) 53 | } 54 | } 55 | 56 | /** 57 | `showAlert` method shorthand for quick presentation of error `localizedDescription` as part of message text. 58 | 59 | - important: Descriptions of other parameters can be found in `showAlert` method. 60 | 61 | - parameters: 62 | - error: Error instance which `localizedDescription` should be used in alert. 63 | - underErrorText: Additional text that should be placed right after an error message. 64 | */ 65 | class func showKeysErrorAlert(over window: NSWindow? = nil, 66 | style: NSAlert.Style = NSAlert.Style.critical, 67 | icon: NSImage? = image(Const.Image.keysIcon), 68 | messageText: String = "Keys For Sketch Error Occurred", 69 | error: Error, 70 | underErrorText: String = "Make sure you running latest versions of Keys and Sketch and contact developer if problem still occurs.", 71 | orderedButtonTitles: [String] = ["OK"], 72 | completionHandler: ((_ pressedButtonNumber: Int) -> Void)? = nil) { 73 | 74 | showAlert(over: window, 75 | style: style, 76 | icon: icon, 77 | messageText: messageText, 78 | informativeText: "\(error.localizedDescription)\n· · ·\n\(underErrorText)", 79 | orderedButtonTitles: orderedButtonTitles, 80 | completionHandler: completionHandler) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Source/Support/UIInjector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIInjector.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 15/09/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | class UIInjector: NSObject { 10 | 11 | /// Shared instance of MSPreferencesController. 12 | @objc static let pc = VDKSketchAPI.preferencesController() 13 | 14 | /// Add Keys Panel to Sketch Preferences. 15 | class func injectKeysPanel() throws { 16 | pc.loadWindow() 17 | 18 | // Make sure that all used properties are exists. 19 | guard let toolbar = pc.toolbar else { 20 | throw Error.prefsControllerPropertyIsNil(propertyKeyPath: #keyPath(pc.toolbar)) 21 | } 22 | guard var toolbarItemIdentifiers = pc.toolbarItemIdentifiers else { 23 | throw Error.prefsControllerPropertyIsNil(propertyKeyPath: #keyPath(pc.toolbarItemIdentifiers)) 24 | } 25 | 26 | // Register and insert Keys toolbar item into Preferences window toolbar. 27 | pc.preferencePaneClasses[Const.Preferences.keysIdentifier] = ViewController.self 28 | 29 | toolbarItemIdentifiers.append(Const.Preferences.keysIdentifier) 30 | pc.toolbarItemIdentifiers = toolbarItemIdentifiers 31 | 32 | toolbar.insertItem(withItemIdentifier: Const.Preferences.keysIdentifier, at: toolbar.items.count) 33 | 34 | // Make sure that last closed tab is selected again 35 | if pc.toolbar?.selectedItemIdentifier == nil { 36 | pc.switchToPane(withIdentifier: toolbarItemIdentifiers[Int(pc.selectedTabIndex)]) 37 | } 38 | 39 | // Hide Preferences window. 40 | pc.close() 41 | } 42 | 43 | enum Error: LocalizedError { 44 | 45 | case prefsControllerPropertyIsNil(propertyKeyPath: String) 46 | 47 | var errorDescription: String? { 48 | switch self { 49 | case .prefsControllerPropertyIsNil(let propertyKeyPath): 50 | return "MSPreferencesController property \"\(propertyKeyPath)\" is unexpectedly nil." 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Source/Update/FileObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileObserver.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 29/06/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | // FileObserver observes changes made in plugin file. If user or Sketch modifying it (on update or reinstall, for instance) an alert asking to Restart Sketch should appear. 10 | public class FileObserver: NSObject { 11 | 12 | @objc public static let shared = FileObserver() 13 | /// https://github.com/soh335/FileWatch 14 | var fileWatch: FileWatch? 15 | 16 | @objc public func startObserving() { 17 | fileWatch = try! FileWatch(paths: [Keys.pluginPath], 18 | createFlag: [.UseCFTypes, .FileEvents], 19 | runLoop: RunLoop.current, 20 | latency: 0.5, 21 | eventHandler: { event in 22 | 23 | inDebug { 24 | print(""" 25 | 26 | \(event.path) 27 | 28 | == pluginURL: \(event.path == Keys.pluginPath) 29 | .ItemCreated: \(event.flag.contains(.ItemCreated)) 30 | .ItemRemoved: \(event.flag.contains(.ItemRemoved)) 31 | .ItemInodeMetaMod: \(event.flag.contains(.ItemInodeMetaMod)) 32 | .ItemRenamed: \(event.flag.contains(.ItemRenamed)) 33 | .ItemModified: \(event.flag.contains(.ItemModified)) 34 | .ItemFinderInfoMod: \(event.flag.contains(.ItemFinderInfoMod)) 35 | .ItemChangeOwner: \(event.flag.contains(.ItemChangeOwner)) 36 | .ItemXattrMod: \(event.flag.contains(.ItemXattrMod)) 37 | 38 | """) 39 | } 40 | 41 | if 42 | event.flag.contains(.ItemModified) // Manual Plugin Installation 43 | || event.path == Keys.pluginPath && event.flag.contains(.ItemRenamed) // Automatic update by Sketch 44 | 45 | { 46 | // Most of changes should appear through latency period but to make sure that all files passed, delay displaying of update alert until all files will be copied. 47 | self.cancelPreviousPerformRequiestsAndPerform(#selector(self.fileChanged), with: nil, afterDelay: 0.75) 48 | } 49 | }) 50 | } 51 | 52 | @objc func fileChanged() { 53 | let alert = UpdateCompletionAlert() 54 | alert.completionHandler(with: alert.runModal()) 55 | } 56 | } 57 | 58 | extension NSObject { 59 | /// Cancel previous perform requests with given arguments for object and register new one 60 | /// - note: Useful when you need to run a selector after a bunch of repetitive actions. Each action should cancel previous perform request and register a new delayed request afterwards. Selector will perform after the last action registers its perform request and its delay time will expire. 61 | func cancelPreviousPerformRequiestsAndPerform(_ aSelector: Selector, with anArgument: Any?, afterDelay delay: TimeInterval) { 62 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: aSelector, object: anArgument) 63 | self.perform(aSelector, with: anArgument, afterDelay: delay) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Source/Update/UpdateCompletionAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateCompleteAlert.swift 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 19/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | public class UpdateCompletionAlert: NSAlert { 10 | 11 | override init() { 12 | super.init() 13 | messageText = "Keys Were Installed Sucessfully" 14 | informativeText = "Please restart Sketch to finish the instalation process." 15 | icon = image(Const.Image.keysIcon) 16 | 17 | addButton(withTitle: "Restart Sketch") 18 | addButton(withTitle: "Later") 19 | } 20 | 21 | /// Default completion handler for alert. 22 | @objc public func completionHandler(with response: NSApplication.ModalResponse) { 23 | if response == .alertFirstButtonReturn { 24 | // Relaunch Sketch 25 | let task = Process() 26 | task.launchPath = "/bin/sh" 27 | task.arguments = ["-c", "sleep \(0.5); open \"\(Bundle.main.bundlePath)\""] 28 | task.launch() 29 | NSApp.terminate(nil) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Source/VDKBridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDKeysBridgingHeader.h 3 | // KeysForSketch 4 | // 5 | // Created by Vyacheslav Dubovitsky on 15/03/2017. 6 | // Copyright © 2017 Vyacheslav Dubovitsky. All rights reserved. 7 | // 8 | 9 | @import MASShortcut; 10 | #import "VDKeys.h" 11 | #import "VDKSketchAPI.h" 12 | #import "VDK_JRSwizzle.h" 13 | #import "NSMenuItem+Private.h" 14 | --------------------------------------------------------------------------------