├── tyke ├── Assets.xcassets │ ├── Contents.json │ ├── dabutton.imageset │ │ ├── asdf.png │ │ ├── asdf-1.png │ │ ├── asdf-2.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── tyke16.png │ │ ├── tyke32.png │ │ ├── tyke64.png │ │ ├── tyke1024.png │ │ ├── tyke128.png │ │ ├── tyke256-1.png │ │ ├── tyke256.png │ │ ├── tyke32-1.png │ │ ├── tyke512-1.png │ │ ├── tyke512.png │ │ └── Contents.json ├── NoBloopView.swift ├── Info.plist ├── EditorViewController.swift ├── EditorViewController.xib ├── Base.lproj │ └── Main.storyboard ├── AppDelegate.swift ├── PreferencesViewController.swift └── PreferencesViewController.xib ├── tyke.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcuserdata │ └── andre.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── tyke.xcscheme ├── xcshareddata │ └── xcschemes │ │ └── tyke.xcscheme └── project.pbxproj ├── HotKey ├── HotKey.swift ├── NSEventModifierFlags+HotKey.swift ├── KeyCombo.swift ├── HotKeysController.swift ├── KeyCombo+System.swift └── Key.swift └── .gitignore /tyke/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /tyke/Assets.xcassets/dabutton.imageset/asdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/dabutton.imageset/asdf.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke16.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke32.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke64.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/dabutton.imageset/asdf-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/dabutton.imageset/asdf-1.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/dabutton.imageset/asdf-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/dabutton.imageset/asdf-2.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke1024.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke128.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke256-1.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke256.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke32-1.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke512-1.png -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/tyke512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torrez/tyke/HEAD/tyke/Assets.xcassets/AppIcon.appiconset/tyke512.png -------------------------------------------------------------------------------- /tyke.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tyke/NoBloopView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoBloopView.swift 3 | // tyke 4 | // 5 | // Created by Andre Torrez on 9/21/17. 6 | // Copyright © 2017 Andre Torrez. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class NoBloopView: NSView { 12 | override func performKeyEquivalent(with event: NSEvent) -> Bool { 13 | return true 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tyke/Assets.xcassets/dabutton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asdf.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "asdf-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "asdf-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /tyke.xcodeproj/xcuserdata/andre.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | tyke.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 652006081F463E1700D9FD2F 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tyke/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSUIElement 6 | 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2017 Andre Torrez. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /tyke/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "tyke16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "tyke32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "tyke32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "tyke64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "tyke128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "tyke256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "tyke256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "tyke512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "tyke512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "tyke1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /HotKey/HotKey.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Carbon 3 | 4 | public final class HotKey { 5 | 6 | // MARK: - Types 7 | 8 | public typealias Handler = () -> Void 9 | 10 | // MARK: - Properties 11 | 12 | let identifier = UUID() 13 | 14 | public let keyCombo: KeyCombo 15 | public var keyDownHandler: Handler? 16 | public var keyUpHandler: Handler? 17 | public var isPaused = false { 18 | didSet { 19 | if isPaused { 20 | HotKeysController.unregister(self) 21 | } else { 22 | HotKeysController.register(self) 23 | } 24 | } 25 | } 26 | 27 | // MARK: - Initializers 28 | 29 | public init(keyCombo: KeyCombo, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) { 30 | self.keyCombo = keyCombo 31 | self.keyDownHandler = keyDownHandler 32 | self.keyUpHandler = keyUpHandler 33 | 34 | HotKeysController.register(self) 35 | } 36 | 37 | public convenience init(carbonKeyCode: UInt32, carbonModifiers: UInt32, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) { 38 | let keyCombo = KeyCombo(carbonKeyCode: carbonKeyCode, carbonModifiers: carbonModifiers) 39 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler) 40 | } 41 | 42 | public convenience init(key: Key, modifiers: NSEvent.ModifierFlags, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) { 43 | let keyCombo = KeyCombo(key: key, modifiers: modifiers) 44 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler) 45 | } 46 | 47 | deinit { 48 | HotKeysController.unregister(self) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HotKey/NSEventModifierFlags+HotKey.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Carbon 3 | 4 | extension NSEvent.ModifierFlags { 5 | public var carbonFlags: UInt32 { 6 | var carbonFlags: UInt32 = 0 7 | 8 | if contains(.command) { 9 | carbonFlags |= UInt32(cmdKey) 10 | } 11 | 12 | if contains(.option) { 13 | carbonFlags |= UInt32(optionKey) 14 | } 15 | 16 | if contains(.control) { 17 | carbonFlags |= UInt32(controlKey) 18 | } 19 | 20 | if contains(.shift) { 21 | carbonFlags |= UInt32(shiftKey) 22 | } 23 | 24 | return carbonFlags 25 | } 26 | 27 | public init(carbonFlags: UInt32) { 28 | self.init() 29 | 30 | if carbonFlags & UInt32(cmdKey) == UInt32(cmdKey) { 31 | insert(.command) 32 | } 33 | 34 | if carbonFlags & UInt32(optionKey) == UInt32(optionKey) { 35 | insert(.option) 36 | } 37 | 38 | if carbonFlags & UInt32(controlKey) == UInt32(controlKey) { 39 | insert(.control) 40 | } 41 | 42 | if carbonFlags & UInt32(shiftKey) == UInt32(shiftKey) { 43 | insert(.shift) 44 | } 45 | } 46 | } 47 | 48 | extension NSEvent.ModifierFlags: CustomStringConvertible { 49 | public var description: String { 50 | var output = "" 51 | 52 | if contains(.control) { 53 | output += Key.control.description 54 | } 55 | 56 | if contains(.option) { 57 | output += Key.option.description 58 | } 59 | 60 | if contains(.shift) { 61 | output += Key.shift.description 62 | } 63 | 64 | if contains(.command) { 65 | output += Key.command.description 66 | } 67 | 68 | return output 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /HotKey/KeyCombo.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | public struct KeyCombo: Equatable { 4 | 5 | // MARK: - Properties 6 | 7 | public var carbonKeyCode: UInt32 8 | public var carbonModifiers: UInt32 9 | 10 | public var key: Key? { 11 | get { 12 | return Key(carbonKeyCode: carbonKeyCode) 13 | } 14 | 15 | set { 16 | carbonKeyCode = newValue?.carbonKeyCode ?? 0 17 | } 18 | } 19 | 20 | public var modifiers: NSEvent.ModifierFlags { 21 | get { 22 | return NSEvent.ModifierFlags(carbonFlags: carbonModifiers) 23 | } 24 | 25 | set { 26 | carbonModifiers = modifiers.carbonFlags 27 | } 28 | } 29 | 30 | public var isValid: Bool { 31 | return carbonKeyCode >= 0 32 | } 33 | 34 | // MARK: - Initializers 35 | 36 | public init(carbonKeyCode: UInt32, carbonModifiers: UInt32 = 0) { 37 | self.carbonKeyCode = carbonKeyCode 38 | self.carbonModifiers = carbonModifiers 39 | } 40 | 41 | public init(key: Key, modifiers: NSEvent.ModifierFlags = []) { 42 | self.carbonKeyCode = key.carbonKeyCode 43 | self.carbonModifiers = modifiers.carbonFlags 44 | } 45 | 46 | // MARK: - Converting Keys 47 | 48 | public static func carbonKeyCodeToString(_ carbonKeyCode: UInt32) -> String? { 49 | return nil 50 | } 51 | } 52 | 53 | extension KeyCombo { 54 | public var dictionary: [String: Any] { 55 | return [ 56 | "keyCode": Int(carbonKeyCode), 57 | "modifiers": Int(carbonModifiers) 58 | ] 59 | } 60 | 61 | public init?(dictionary: [String: Any]) { 62 | guard let keyCode = dictionary["keyCode"] as? Int, 63 | let modifiers = dictionary["modifiers"] as? Int 64 | else { 65 | return nil 66 | } 67 | 68 | self.init(carbonKeyCode: UInt32(keyCode), carbonModifiers: UInt32(modifiers)) 69 | } 70 | } 71 | 72 | extension KeyCombo: CustomStringConvertible { 73 | public var description: String { 74 | var output = modifiers.description 75 | 76 | if let keyDescription = key?.description { 77 | output += keyDescription 78 | } 79 | 80 | return output 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tyke/EditorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorViewController.swift 3 | // tyke 4 | // 5 | // Created by Andre Torrez on 8/17/17. 6 | // Copyright © 2017 Andre Torrez. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class EditorViewController: NSViewController, NSTextViewDelegate { 12 | 13 | @IBOutlet var editor: NSTextView! 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do view setup here. 17 | 18 | self.editor.font = NSFont(name:"Menlo", size:16) 19 | self.editor.textContainerInset = NSSize(width: 9.0, height: 9.0) 20 | self.editor.delegate = self 21 | 22 | 23 | let nc = NotificationCenter.default 24 | nc.addObserver(forName:Notification.Name(rawValue:"ToggleSmartQuotes"), 25 | object:nil, queue:nil, 26 | using:didToggleSmartQuotes) 27 | 28 | // Should smart quotes be on or off? 29 | if (UserDefaults.standard.bool(forKey:"use_smart_quotes")){ 30 | self.editor.isAutomaticQuoteSubstitutionEnabled = true 31 | }else{ 32 | self.editor.isAutomaticQuoteSubstitutionEnabled = false 33 | } 34 | 35 | addObserver(self, forKeyPath: #keyPath(editor.isAutomaticQuoteSubstitutionEnabled), options: [.old, .new], context: nil) 36 | } 37 | 38 | // Observe if smart quotes were turned off. 39 | 40 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 41 | if keyPath == #keyPath(editor.isAutomaticQuoteSubstitutionEnabled) { 42 | 43 | //Post notification that it was turned on 44 | if (self.editor.isAutomaticQuoteSubstitutionEnabled){ 45 | let nc = NotificationCenter.default 46 | nc.post(name:Notification.Name(rawValue:"SmartQuotesWasTurnedOn"), 47 | object: nil, 48 | userInfo: nil) 49 | }else{ 50 | let nc = NotificationCenter.default 51 | nc.post(name:Notification.Name(rawValue:"SmartQuotesWasTurnedOff"), 52 | object: nil, 53 | userInfo: nil) 54 | 55 | } 56 | } 57 | } 58 | 59 | func didToggleSmartQuotes(notification:Notification) -> Void{ 60 | self.editor.toggleAutomaticQuoteSubstitution(nil) 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /tyke.xcodeproj/xcshareddata/xcschemes/tyke.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/xcode,swift 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## User settings 11 | xcuserdata/ 12 | 13 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 14 | *.xcscmblueprint 15 | *.xccheckout 16 | 17 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 18 | build/ 19 | DerivedData/ 20 | *.moved-aside 21 | *.pbxuser 22 | !default.pbxuser 23 | *.mode1v3 24 | !default.mode1v3 25 | *.mode2v3 26 | !default.mode2v3 27 | *.perspectivev3 28 | !default.perspectivev3 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | 33 | ## App packaging 34 | *.ipa 35 | *.dSYM.zip 36 | *.dSYM 37 | 38 | ## Playgrounds 39 | timeline.xctimeline 40 | playground.xcworkspace 41 | 42 | # Swift Package Manager 43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 44 | # Packages/ 45 | # Package.pins 46 | # Package.resolved 47 | # *.xcodeproj 48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 49 | # hence it is not needed unless you have added a package configuration file to your project 50 | # .swiftpm 51 | 52 | .build/ 53 | 54 | # CocoaPods 55 | # We recommend against adding the Pods directory to your .gitignore. However 56 | # you should judge for yourself, the pros and cons are mentioned at: 57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 58 | # Pods/ 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 64 | # Carthage/Checkouts 65 | 66 | Carthage/Build/ 67 | 68 | # Accio dependency management 69 | Dependencies/ 70 | .accio/ 71 | 72 | # fastlane 73 | # It is recommended to not store the screenshots in the git repo. 74 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 75 | # For more information about the recommended setup visit: 76 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 77 | 78 | fastlane/report.xml 79 | fastlane/Preview.html 80 | fastlane/screenshots/**/*.png 81 | fastlane/test_output 82 | 83 | # Code Injection 84 | # After new code Injection tools there's a generated folder /iOSInjectionProject 85 | # https://github.com/johnno1962/injectionforxcode 86 | 87 | iOSInjectionProject/ 88 | 89 | ### Xcode ### 90 | # Xcode 91 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 92 | 93 | 94 | 95 | 96 | ## Gcc Patch 97 | /*.gcno 98 | 99 | ### Xcode Patch ### 100 | *.xcodeproj/* 101 | !*.xcodeproj/project.pbxproj 102 | !*.xcodeproj/xcshareddata/ 103 | !*.xcworkspace/contents.xcworkspacedata 104 | **/xcshareddata/WorkspaceSettings.xcsettings 105 | 106 | # End of https://www.toptal.com/developers/gitignore/api/xcode,swift 107 | 108 | .DS_Store 109 | -------------------------------------------------------------------------------- /tyke.xcodeproj/xcuserdata/andre.xcuserdatad/xcschemes/tyke.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tyke/EditorViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tyke/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /HotKey/HotKeysController.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | 3 | final class HotKeysController { 4 | 5 | // MARK: - Types 6 | 7 | final class HotKeyBox { 8 | let identifier: UUID 9 | weak var hotKey: HotKey? 10 | let carbonHotKeyID: UInt32 11 | var carbonEventHotKey: EventHotKeyRef? 12 | 13 | init(hotKey: HotKey, carbonHotKeyID: UInt32) { 14 | self.identifier = hotKey.identifier 15 | self.hotKey = hotKey 16 | self.carbonHotKeyID = carbonHotKeyID 17 | } 18 | } 19 | 20 | // MARK: - Properties 21 | 22 | static var hotKeys = [UInt32: HotKeyBox]() 23 | static private var hotKeysCount: UInt32 = 0 24 | 25 | static let eventHotKeySignature: UInt32 = { 26 | let string = "SSHk" 27 | var result: FourCharCode = 0 28 | for char in string.utf16 { 29 | result = (result << 8) + FourCharCode(char) 30 | } 31 | return result 32 | }() 33 | 34 | private static let eventSpec = [ 35 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)), 36 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased)) 37 | ] 38 | 39 | private static var eventHandler: EventHandlerRef? 40 | 41 | // MARK: - Registration 42 | 43 | static func register(_ hotKey: HotKey) { 44 | // Don't register an already registered HotKey 45 | if hotKeys.values.first(where: { $0.identifier == hotKey.identifier }) != nil { 46 | return 47 | } 48 | 49 | // Increment the count which will become out next ID 50 | hotKeysCount += 1 51 | 52 | // Create a box for our metadata and weak HotKey 53 | let box = HotKeyBox(hotKey: hotKey, carbonHotKeyID: hotKeysCount) 54 | hotKeys[box.carbonHotKeyID] = box 55 | 56 | // Register with the system 57 | var eventHotKey: EventHotKeyRef? 58 | let hotKeyID = EventHotKeyID(signature: eventHotKeySignature, id: box.carbonHotKeyID) 59 | let registerError = RegisterEventHotKey( 60 | hotKey.keyCombo.carbonKeyCode, 61 | hotKey.keyCombo.carbonModifiers, 62 | hotKeyID, 63 | GetEventDispatcherTarget(), 64 | 0, 65 | &eventHotKey 66 | ) 67 | 68 | // Ensure registration worked 69 | guard registerError == noErr, eventHotKey != nil else { 70 | return 71 | } 72 | 73 | // Store the event so we can unregister it later 74 | box.carbonEventHotKey = eventHotKey 75 | 76 | // Setup the event handler if needed 77 | updateEventHandler() 78 | } 79 | 80 | static func unregister(_ hotKey: HotKey) { 81 | // Find the box 82 | guard let box = self.box(for: hotKey) else { 83 | return 84 | } 85 | 86 | // Unregister the hot key 87 | UnregisterEventHotKey(box.carbonEventHotKey) 88 | 89 | // Destroy the box 90 | box.hotKey = nil 91 | hotKeys.removeValue(forKey: box.carbonHotKeyID) 92 | } 93 | 94 | 95 | // MARK: - Events 96 | 97 | static func handleCarbonEvent(_ event: EventRef?) -> OSStatus { 98 | // Ensure we have an event 99 | guard let event = event else { 100 | return OSStatus(eventNotHandledErr) 101 | } 102 | 103 | // Get the hot key ID from the event 104 | var hotKeyID = EventHotKeyID() 105 | let error = GetEventParameter( 106 | event, 107 | UInt32(kEventParamDirectObject), 108 | UInt32(typeEventHotKeyID), 109 | nil, 110 | MemoryLayout.size, 111 | nil, 112 | &hotKeyID 113 | ) 114 | 115 | if error != noErr { 116 | return error 117 | } 118 | 119 | // Ensure we have a HotKey registered for this ID 120 | guard hotKeyID.signature == eventHotKeySignature, 121 | let hotKey = self.hotKey(for: hotKeyID.id) 122 | else { 123 | return OSStatus(eventNotHandledErr) 124 | } 125 | 126 | // Call the handler 127 | switch GetEventKind(event) { 128 | case UInt32(kEventHotKeyPressed): 129 | if !hotKey.isPaused, let handler = hotKey.keyDownHandler { 130 | handler() 131 | return noErr 132 | } 133 | case UInt32(kEventHotKeyReleased): 134 | if !hotKey.isPaused, let handler = hotKey.keyUpHandler { 135 | handler() 136 | return noErr 137 | } 138 | default: 139 | break 140 | } 141 | 142 | return OSStatus(eventNotHandledErr) 143 | } 144 | 145 | private static func updateEventHandler() { 146 | if hotKeysCount == 0 || eventHandler != nil { 147 | return 148 | } 149 | 150 | // Register for key down and key up 151 | let eventSpec = [ 152 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)), 153 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased)) 154 | ] 155 | 156 | // Install the handler 157 | InstallEventHandler(GetEventDispatcherTarget(), hotKeyEventHandler, 2, eventSpec, nil, &eventHandler) 158 | } 159 | 160 | 161 | // MARK: - Querying 162 | 163 | private static func hotKey(for carbonHotKeyID: UInt32) -> HotKey? { 164 | if let hotKey = hotKeys[carbonHotKeyID]?.hotKey { 165 | return hotKey 166 | } 167 | 168 | hotKeys.removeValue(forKey: carbonHotKeyID) 169 | return nil 170 | } 171 | 172 | private static func box(for hotKey: HotKey) -> HotKeyBox? { 173 | for box in hotKeys.values { 174 | if box.identifier == hotKey.identifier { 175 | return box 176 | } 177 | } 178 | 179 | return nil 180 | } 181 | } 182 | 183 | private func hotKeyEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus { 184 | return HotKeysController.handleCarbonEvent(event) 185 | } 186 | -------------------------------------------------------------------------------- /HotKey/KeyCombo+System.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Carbon 3 | 4 | extension KeyCombo { 5 | /// All system key combos 6 | /// 7 | /// - returns: array of key combos 8 | public static func systemKeyCombos() -> [KeyCombo] { 9 | var unmanagedGlobalHotkeys: Unmanaged? 10 | guard CopySymbolicHotKeys(&unmanagedGlobalHotkeys) == noErr, 11 | let globalHotkeys = unmanagedGlobalHotkeys?.takeRetainedValue() else 12 | { 13 | assertionFailure("Unable to get system-wide hotkeys") 14 | return [] 15 | } 16 | 17 | return (0.. [KeyCombo] { 37 | guard let menu = NSApp.mainMenu else { 38 | return [] 39 | } 40 | 41 | return keyCombos(in: menu) 42 | } 43 | 44 | /// Recursively find key combos in a menu 45 | /// 46 | /// - parameter menu: menu to search 47 | /// 48 | /// - returns: array of key combos 49 | public static func keyCombos(in menu: NSMenu) -> [KeyCombo] { 50 | var keyCombos = [KeyCombo]() 51 | 52 | for item in menu.items { 53 | if let key = Key(string: item.keyEquivalent) { 54 | keyCombos.append(KeyCombo(key: key, modifiers: item.keyEquivalentModifierMask)) 55 | } 56 | 57 | if let submenu = item.submenu { 58 | keyCombos += self.keyCombos(in: submenu) 59 | } 60 | } 61 | 62 | return keyCombos 63 | } 64 | 65 | /// Standard application key combos 66 | /// 67 | /// - returns: array of key combos 68 | public static func standardKeyCombos() -> [KeyCombo] { 69 | return [ 70 | // Application 71 | KeyCombo(key: .comma, modifiers: .command), 72 | KeyCombo(key: .h, modifiers: .command), 73 | KeyCombo(key: .h, modifiers: [.command, .option]), 74 | KeyCombo(key: .q, modifiers: .command), 75 | 76 | // File 77 | KeyCombo(key: .n, modifiers: .command), 78 | KeyCombo(key: .o, modifiers: .command), 79 | KeyCombo(key: .w, modifiers: .command), 80 | KeyCombo(key: .s, modifiers: .command), 81 | KeyCombo(key: .s, modifiers: [.command, .shift]), 82 | KeyCombo(key: .r, modifiers: .command), 83 | KeyCombo(key: .p, modifiers: [.command, .shift]), 84 | KeyCombo(key: .p, modifiers: .command), 85 | 86 | // Edit 87 | KeyCombo(key: .z, modifiers: .command), 88 | KeyCombo(key: .z, modifiers: [.command, .shift]), 89 | KeyCombo(key: .x, modifiers: .command), 90 | KeyCombo(key: .c, modifiers: .command), 91 | KeyCombo(key: .v, modifiers: .command), 92 | KeyCombo(key: .v, modifiers: [.command, .option, .shift]), 93 | KeyCombo(key: .a, modifiers: .command), 94 | KeyCombo(key: .f, modifiers: .command), 95 | KeyCombo(key: .f, modifiers: [.command, .option]), 96 | KeyCombo(key: .g, modifiers: .command), 97 | KeyCombo(key: .g, modifiers: [.command, .shift]), 98 | KeyCombo(key: .e, modifiers: .command), 99 | KeyCombo(key: .j, modifiers: .command), 100 | KeyCombo(key: .semicolon, modifiers: [.command, .shift]), 101 | KeyCombo(key: .semicolon, modifiers: .command), 102 | 103 | // Format 104 | KeyCombo(key: .t, modifiers: .command), 105 | KeyCombo(key: .b, modifiers: .command), 106 | KeyCombo(key: .i, modifiers: .command), 107 | KeyCombo(key: .u, modifiers: .command), 108 | KeyCombo(key: .equal, modifiers: [.command, .shift]), 109 | KeyCombo(key: .minus, modifiers: .command), 110 | KeyCombo(key: .c, modifiers: [.command, .shift]), 111 | KeyCombo(key: .c, modifiers: [.command, .option]), 112 | KeyCombo(key: .v, modifiers: [.command, .option]), 113 | KeyCombo(key: .leftBracket, modifiers: [.command, .shift]), 114 | KeyCombo(key: .backslash, modifiers: [.command, .shift]), 115 | KeyCombo(key: .rightBracket, modifiers: [.command, .shift]), 116 | KeyCombo(key: .c, modifiers: [.command, .control]), 117 | KeyCombo(key: .v, modifiers: [.command, .control]), 118 | 119 | // View 120 | KeyCombo(key: .t, modifiers: [.command, .option]), 121 | KeyCombo(key: .s, modifiers: [.command, .control]), 122 | KeyCombo(key: .f, modifiers: [.command, .control]), 123 | 124 | // Window 125 | KeyCombo(key: .m, modifiers: .command), 126 | 127 | // Help 128 | KeyCombo(key: .slash, modifiers: [.command, .shift]) 129 | ] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tyke/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // tyke 4 | // 5 | // Created by Andre Torrez on 8/17/17. 6 | // Copyright © 2017 Andre Torrez. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | let popover = NSPopover() 15 | let statusItem = NSStatusBar.system.statusItem(withLength: -2) 16 | 17 | var evc: EditorViewController! 18 | var pvc: PreferencesViewController! 19 | var pasteboard = NSPasteboard.general 20 | var smart_quote_menu_item: NSMenuItem! 21 | var showHotKey: HotKey? 22 | var clipboardHotKey: HotKey? 23 | 24 | func applicationDidFinishLaunching(_ aNotification: Notification) { 25 | 26 | popover.behavior = NSPopover.Behavior.transient 27 | popover.animates = false 28 | 29 | if let button = statusItem.button { 30 | button.image = NSImage(named: "dabutton") 31 | button.image?.size = NSSize(width: 20, height: 18) 32 | button.image?.isTemplate = true 33 | button.action = #selector(self.statusItemClicked(sender:)) 34 | button.sendAction(on: [.leftMouseUp, .rightMouseUp]) 35 | } 36 | 37 | evc = EditorViewController(nibName: "EditorViewController", bundle: nil) 38 | popover.contentViewController = evc 39 | 40 | pvc = PreferencesViewController(nibName: "PreferencesViewController", bundle:nil) 41 | 42 | //This is bad and you should feel bad 43 | togglePopover(nil) 44 | togglePopover(nil) 45 | 46 | //HANDLE right click https://github.com/craigfrancis/datetime/blob/master/xcode/DateTime/AppDelegate.swift 47 | 48 | //Handle when smart quotes is turned on or off in the view 49 | let nc = NotificationCenter.default 50 | nc.addObserver(forName:Notification.Name(rawValue:"SmartQuotesWasTurnedOn"), object:nil, queue:nil, using:smartQuotesWasTurnedOn) 51 | nc.addObserver(forName:Notification.Name(rawValue:"SmartQuotesWasTurnedOff"), object:nil, queue:nil, using:smartQuotesWasTurnedOff) 52 | 53 | setupHotKeys() 54 | handleHotKeys() 55 | } 56 | 57 | @objc func statusItemClicked(sender: NSStatusBarButton!){ 58 | 59 | let event:NSEvent! = NSApp.currentEvent! 60 | if (event.type == NSEvent.EventType.rightMouseUp) { 61 | closePopover(sender: nil) 62 | 63 | statusItem.highlightMode = true // Highlight badge: Stop the highlight flicker (see async call below). 64 | statusItem.button?.isHighlighted = true 65 | 66 | let contextMenu = NSMenu() 67 | smart_quote_menu_item = NSMenuItem(title: "Smart Quotes", action: #selector(self.toggleSmartQuotes(sender:)), keyEquivalent: "") 68 | 69 | if (UserDefaults.standard.bool(forKey:"use_smart_quotes")) { smart_quote_menu_item.state = NSControl.StateValue.on } 70 | else { smart_quote_menu_item.state = NSControl.StateValue.off } 71 | 72 | contextMenu.addItem(smart_quote_menu_item) 73 | contextMenu.addItem(NSMenuItem(title: "Hot Keys", action: #selector(self.showPreferences(sender:)), keyEquivalent: "")) 74 | contextMenu.addItem(NSMenuItem.separator()) 75 | contextMenu.addItem(NSMenuItem(title: "Quit", action: #selector(self.quit(sender:)), keyEquivalent: "")) 76 | 77 | statusItem.menu = contextMenu 78 | statusItem.popUpMenu(contextMenu) 79 | statusItem.menu = nil // Otherwise clicks won't be processed again 80 | } 81 | else { togglePopover(sender) } 82 | } 83 | 84 | func applicationWillTerminate(_ aNotification: Notification) { 85 | // Insert code here to tear down your application 86 | } 87 | 88 | func showPopover(sender: AnyObject?) { 89 | 90 | if let button = statusItem.button { 91 | NSApplication.shared.activate(ignoringOtherApps: true) 92 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) 93 | } 94 | } 95 | 96 | func closePopover(sender: AnyObject?) { 97 | 98 | popover.performClose(sender) 99 | } 100 | 101 | func togglePopover(_ sender: AnyObject?) { 102 | 103 | if popover.isShown { closePopover(sender:sender) } 104 | else { showPopover(sender:sender) } 105 | } 106 | 107 | @objc func quit(sender: AnyObject?) { 108 | 109 | NSApplication.shared.terminate(nil) 110 | } 111 | 112 | @objc func toggleSmartQuotes(sender: AnyObject?) { 113 | 114 | let nc = NotificationCenter.default 115 | nc.post(name:Notification.Name(rawValue:"ToggleSmartQuotes"), 116 | object: nil, 117 | userInfo: ["message":"Hello there!", "date":Date()]) 118 | } 119 | 120 | @objc func showPreferences(sender: AnyObject?) { 121 | 122 | pvc.presentAsModalWindow(pvc) 123 | } 124 | 125 | func smartQuotesWasTurnedOn(notification:Notification) -> Void { 126 | 127 | if let sqmi = smart_quote_menu_item { 128 | sqmi.state = NSControl.StateValue.on 129 | } 130 | 131 | UserDefaults.standard.set(true, forKey: "use_smart_quotes") 132 | } 133 | 134 | func smartQuotesWasTurnedOff(notification:Notification) -> Void { 135 | 136 | if let sqmi = smart_quote_menu_item { 137 | sqmi.state = NSControl.StateValue.off 138 | } 139 | UserDefaults.standard.set(false, forKey: "use_smart_quotes") 140 | } 141 | 142 | func setupHotKeys() { 143 | // Set show and Clipboard hotkeys to initial values 144 | 145 | showHotKey = HotKey(keyCombo: KeyCombo(key: .s, modifiers: [.command, .option])) 146 | clipboardHotKey = HotKey(keyCombo: KeyCombo(key: .c, modifiers: [.command, .option])) 147 | } 148 | 149 | func handleHotKeys() { 150 | 151 | self.showHotKey?.keyDownHandler = { self.togglePopover(nil) } 152 | 153 | self.clipboardHotKey?.keyDownHandler = { 154 | let textToCopy: [NSString] = NSArray.init(object: self.evc.editor.textStorage!.string) as! [NSString] 155 | self.pasteboard.clearContents() 156 | self.pasteboard.writeObjects(textToCopy) 157 | } 158 | } 159 | } 160 | 161 | -------------------------------------------------------------------------------- /tyke/PreferencesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.swift 3 | // tyke 4 | // 5 | // Created by Andre Torrez on 9/20/17. 6 | // Copyright © 2017 Andre Torrez. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PreferencesViewController: NSViewController { 12 | 13 | @IBOutlet var btnShowHotKey:NSButton! 14 | @IBOutlet var btnClipHotKey:NSButton! 15 | 16 | let appDelegate = NSApp.delegate as! AppDelegate 17 | 18 | let commandSymbol:String = "\u{2318}" // ⌘ 19 | let optionSymbol:String = "\u{2325}" // ⌥ 20 | let controlSymbol:String = "\u{2303}" // ⌃ 21 | let shiftSymbol:String = "\u{21E7}" // ⇧ 22 | let separator:String = " + " 23 | 24 | var isSettingHotKey: Bool = false 25 | var showDisplayString:String = "" 26 | var clipDisplayString:String = "" 27 | var activeButton: NSButton! 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | self.title = "Hot Keys" 33 | 34 | let showHotKeyCode: UInt32 = (HotKeysController.hotKeys[1]?.hotKey?.keyCombo.carbonKeyCode)! 35 | let showHotKeyModifiers: UInt32 = (HotKeysController.hotKeys[1]?.hotKey?.keyCombo.carbonModifiers)! 36 | let clipHotKeyCode: UInt32 = (HotKeysController.hotKeys[2]?.hotKey?.keyCombo.carbonKeyCode)! 37 | let clipHotKeyModifiers: UInt32 = (HotKeysController.hotKeys[2]?.hotKey?.keyCombo.carbonModifiers)! 38 | 39 | showDisplayString = createHotKeyDisplayString(key: showHotKeyCode, modifiers: showHotKeyModifiers) 40 | clipDisplayString = createHotKeyDisplayString(key: clipHotKeyCode, modifiers: clipHotKeyModifiers) 41 | 42 | NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { 43 | self.flagsChanged(with: $0) 44 | return $0 45 | } 46 | NSEvent.addLocalMonitorForEvents(matching: .keyDown) { 47 | self.keyDown(with: $0) 48 | return $0 49 | } 50 | 51 | // TODO: Eventually I want to bring the HotKey code in from GitHub repo using the Swift Package Manager 52 | 53 | // Pull strings from user defaults to override above 54 | setupButtonStrings() 55 | } 56 | 57 | 58 | @IBAction func btnClipHotKey(_ sender: NSButton) { 59 | 60 | self.activeButton = sender 61 | self.activeButton.title = "" 62 | 63 | self.isSettingHotKey = true 64 | } 65 | 66 | @IBAction func btnShowHotKey(_ sender: NSButton) { 67 | 68 | self.activeButton = sender 69 | self.activeButton.title = "" 70 | self.isSettingHotKey = true 71 | } 72 | 73 | //override func flagsChanged(with event: NSEvent) { 74 | //} 75 | 76 | func setupButtonStrings() { 77 | 78 | btnShowHotKey.title = showDisplayString 79 | btnClipHotKey.title = clipDisplayString 80 | } 81 | 82 | func makeButtonString(event: NSEvent) -> String { 83 | 84 | let buttonText: String = "" 85 | return buttonText 86 | } 87 | 88 | override func keyDown(with event: NSEvent) { 89 | 90 | if (!self.isSettingHotKey) { return } 91 | 92 | var newButtonString:String = "" 93 | 94 | if (event.keyCode == 53) { // 53 = 0x35 = kVK_Escape 95 | self.isSettingHotKey = false 96 | self.setupButtonStrings() 97 | return 98 | } 99 | 100 | let x = event.modifierFlags.intersection(.deviceIndependentFlagsMask) 101 | if (!x.contains(.command)) && (!x.contains(.option)) && (!x.contains(.control)) && (!x.contains(.shift)) { 102 | NSSound(named: "Funk")?.play() 103 | return 104 | } 105 | 106 | if x.contains(.command) { newButtonString += commandSymbol + separator } 107 | if x.contains(.option) { newButtonString += optionSymbol + separator } 108 | if x.contains(.control) { newButtonString += controlSymbol + separator } 109 | if x.contains(.shift) { newButtonString += shiftSymbol + separator } 110 | 111 | if let character:String = event.charactersIgnoringModifiers { newButtonString += character } 112 | 113 | if (self.activeButton == btnShowHotKey) { 114 | btnShowHotKey.title = newButtonString 115 | if let newHotKey:Key = Key(string:event.charactersIgnoringModifiers!) { 116 | self.appDelegate.showHotKey = HotKey(key: newHotKey, modifiers: event.modifierFlags) 117 | 118 | // Can I somehow reference the code in AppDelegate instead of have to repeat it below? 119 | self.appDelegate.showHotKey?.keyDownHandler = { self.appDelegate.togglePopover(nil) } 120 | } 121 | } 122 | else if (self.activeButton == btnClipHotKey) { 123 | btnClipHotKey.title = newButtonString 124 | if let newHotKey:Key = Key(string:event.charactersIgnoringModifiers!) { 125 | self.appDelegate.clipboardHotKey = HotKey(key: newHotKey, modifiers: event.modifierFlags) 126 | 127 | // Can I somehow reference the code in AppDelegate instead of have to repeat it below? 128 | self.appDelegate.clipboardHotKey?.keyDownHandler = { 129 | let textToCopy: [NSString] = NSArray.init(object: self.appDelegate.evc.editor.textStorage!.string) as! [NSString] 130 | self.appDelegate.pasteboard.clearContents() 131 | self.appDelegate.pasteboard.writeObjects(textToCopy) 132 | } 133 | } 134 | } 135 | 136 | /* 137 | //Set up this hot key! 138 | if let newHotKey:Key = Key(string:event.charactersIgnoringModifiers!){ 139 | 140 | let hotKey = HotKey(key: newHotKey, modifiers: event.modifierFlags) 141 | hotKey.keyDownHandler = { 142 | print("do a popup") 143 | } 144 | } 145 | 146 | //Store hot key in preferences 147 | */ 148 | isSettingHotKey = false 149 | } 150 | 151 | func createHotKeyDisplayString(key: UInt32, modifiers: UInt32) -> String { 152 | 153 | var displayString: String = "" 154 | 155 | let usesCommand:Bool = (modifiers & 256) != 0 156 | let usesOption:Bool = (modifiers & 2048) != 0 157 | let usesControl:Bool = (modifiers & 4096) != 0 158 | let usesShift:Bool = (modifiers & 512) != 0 159 | 160 | if (usesCommand) { displayString += commandSymbol + separator } 161 | if (usesOption) { displayString += optionSymbol + separator } 162 | if (usesControl) { displayString += controlSymbol + separator } 163 | if (usesShift) { displayString += shiftSymbol + separator } 164 | 165 | displayString += String(describing: Key(carbonKeyCode: key)!) 166 | 167 | return displayString 168 | } 169 | 170 | } 171 | 172 | 173 | -------------------------------------------------------------------------------- /tyke/PreferencesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /tyke.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6520060D1F463E1700D9FD2F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6520060C1F463E1700D9FD2F /* AppDelegate.swift */; }; 11 | 652006111F463E1700D9FD2F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 652006101F463E1700D9FD2F /* Assets.xcassets */; }; 12 | 652006141F463E1700D9FD2F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 652006121F463E1700D9FD2F /* Main.storyboard */; }; 13 | 6520061D1F46537200D9FD2F /* EditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6520061B1F46537200D9FD2F /* EditorViewController.swift */; }; 14 | 6520061E1F46537200D9FD2F /* EditorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6520061C1F46537200D9FD2F /* EditorViewController.xib */; }; 15 | 6540A37A1F74445500797667 /* NoBloopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6540A3791F74445500797667 /* NoBloopView.swift */; }; 16 | 65D87A0A1F7380090069B2F1 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D87A081F7380090069B2F1 /* PreferencesViewController.swift */; }; 17 | 65D87A0B1F7380090069B2F1 /* PreferencesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 65D87A091F7380090069B2F1 /* PreferencesViewController.xib */; }; 18 | 6F8212F62690760700C3272C /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F02690760700C3272C /* KeyCombo.swift */; }; 19 | 6F8212F72690760700C3272C /* KeyCombo+System.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F12690760700C3272C /* KeyCombo+System.swift */; }; 20 | 6F8212F82690760700C3272C /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F22690760700C3272C /* Key.swift */; }; 21 | 6F8212F92690760700C3272C /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F32690760700C3272C /* HotKeysController.swift */; }; 22 | 6F8212FA2690760700C3272C /* NSEventModifierFlags+HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F42690760700C3272C /* NSEventModifierFlags+HotKey.swift */; }; 23 | 6F8212FB2690760700C3272C /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8212F52690760700C3272C /* HotKey.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 652006091F463E1700D9FD2F /* tyke.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tyke.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 6520060C1F463E1700D9FD2F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | 652006101F463E1700D9FD2F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 652006131F463E1700D9FD2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 652006151F463E1700D9FD2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 6520061B1F46537200D9FD2F /* EditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorViewController.swift; sourceTree = ""; }; 33 | 6520061C1F46537200D9FD2F /* EditorViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EditorViewController.xib; sourceTree = ""; }; 34 | 6540A3791F74445500797667 /* NoBloopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoBloopView.swift; sourceTree = ""; }; 35 | 65D87A081F7380090069B2F1 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; 36 | 65D87A091F7380090069B2F1 /* PreferencesViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PreferencesViewController.xib; sourceTree = ""; }; 37 | 6F8212F02690760700C3272C /* KeyCombo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyCombo.swift; sourceTree = ""; }; 38 | 6F8212F12690760700C3272C /* KeyCombo+System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeyCombo+System.swift"; sourceTree = ""; }; 39 | 6F8212F22690760700C3272C /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; }; 40 | 6F8212F32690760700C3272C /* HotKeysController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKeysController.swift; sourceTree = ""; }; 41 | 6F8212F42690760700C3272C /* NSEventModifierFlags+HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEventModifierFlags+HotKey.swift"; sourceTree = ""; }; 42 | 6F8212F52690760700C3272C /* HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKey.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 652006061F463E1700D9FD2F /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 652006001F463E1700D9FD2F = { 57 | isa = PBXGroup; 58 | children = ( 59 | 6520060B1F463E1700D9FD2F /* tyke */, 60 | 6F8212C02690710A00C3272C /* HotKey */, 61 | 6520060A1F463E1700D9FD2F /* Products */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | 6520060A1F463E1700D9FD2F /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 652006091F463E1700D9FD2F /* tyke.app */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | 6520060B1F463E1700D9FD2F /* tyke */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 6520060C1F463E1700D9FD2F /* AppDelegate.swift */, 77 | 6520061B1F46537200D9FD2F /* EditorViewController.swift */, 78 | 6520061C1F46537200D9FD2F /* EditorViewController.xib */, 79 | 65D87A081F7380090069B2F1 /* PreferencesViewController.swift */, 80 | 65D87A091F7380090069B2F1 /* PreferencesViewController.xib */, 81 | 6540A3791F74445500797667 /* NoBloopView.swift */, 82 | 652006101F463E1700D9FD2F /* Assets.xcassets */, 83 | 652006121F463E1700D9FD2F /* Main.storyboard */, 84 | 652006151F463E1700D9FD2F /* Info.plist */, 85 | ); 86 | path = tyke; 87 | sourceTree = ""; 88 | }; 89 | 6F8212C02690710A00C3272C /* HotKey */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 6F8212F52690760700C3272C /* HotKey.swift */, 93 | 6F8212F32690760700C3272C /* HotKeysController.swift */, 94 | 6F8212F22690760700C3272C /* Key.swift */, 95 | 6F8212F02690760700C3272C /* KeyCombo.swift */, 96 | 6F8212F12690760700C3272C /* KeyCombo+System.swift */, 97 | 6F8212F42690760700C3272C /* NSEventModifierFlags+HotKey.swift */, 98 | ); 99 | path = HotKey; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | 652006081F463E1700D9FD2F /* tyke */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = 652006181F463E1700D9FD2F /* Build configuration list for PBXNativeTarget "tyke" */; 108 | buildPhases = ( 109 | 652006051F463E1700D9FD2F /* Sources */, 110 | 652006061F463E1700D9FD2F /* Frameworks */, 111 | 652006071F463E1700D9FD2F /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = tyke; 118 | productName = tyke; 119 | productReference = 652006091F463E1700D9FD2F /* tyke.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 652006011F463E1700D9FD2F /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastSwiftUpdateCheck = 0830; 129 | LastUpgradeCheck = 0830; 130 | ORGANIZATIONNAME = "Andre Torrez"; 131 | TargetAttributes = { 132 | 652006081F463E1700D9FD2F = { 133 | CreatedOnToolsVersion = 8.3.3; 134 | ProvisioningStyle = Automatic; 135 | }; 136 | }; 137 | }; 138 | buildConfigurationList = 652006041F463E1700D9FD2F /* Build configuration list for PBXProject "tyke" */; 139 | compatibilityVersion = "Xcode 3.2"; 140 | developmentRegion = en; 141 | hasScannedForEncodings = 0; 142 | knownRegions = ( 143 | en, 144 | Base, 145 | ); 146 | mainGroup = 652006001F463E1700D9FD2F; 147 | productRefGroup = 6520060A1F463E1700D9FD2F /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | 652006081F463E1700D9FD2F /* tyke */, 152 | ); 153 | }; 154 | /* End PBXProject section */ 155 | 156 | /* Begin PBXResourcesBuildPhase section */ 157 | 652006071F463E1700D9FD2F /* Resources */ = { 158 | isa = PBXResourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 6520061E1F46537200D9FD2F /* EditorViewController.xib in Resources */, 162 | 652006111F463E1700D9FD2F /* Assets.xcassets in Resources */, 163 | 652006141F463E1700D9FD2F /* Main.storyboard in Resources */, 164 | 65D87A0B1F7380090069B2F1 /* PreferencesViewController.xib in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | 652006051F463E1700D9FD2F /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 6F8212F82690760700C3272C /* Key.swift in Sources */, 176 | 6520060D1F463E1700D9FD2F /* AppDelegate.swift in Sources */, 177 | 6F8212FA2690760700C3272C /* NSEventModifierFlags+HotKey.swift in Sources */, 178 | 6F8212FB2690760700C3272C /* HotKey.swift in Sources */, 179 | 6F8212F72690760700C3272C /* KeyCombo+System.swift in Sources */, 180 | 6F8212F62690760700C3272C /* KeyCombo.swift in Sources */, 181 | 65D87A0A1F7380090069B2F1 /* PreferencesViewController.swift in Sources */, 182 | 6F8212F92690760700C3272C /* HotKeysController.swift in Sources */, 183 | 6520061D1F46537200D9FD2F /* EditorViewController.swift in Sources */, 184 | 6540A37A1F74445500797667 /* NoBloopView.swift in Sources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXSourcesBuildPhase section */ 189 | 190 | /* Begin PBXVariantGroup section */ 191 | 652006121F463E1700D9FD2F /* Main.storyboard */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | 652006131F463E1700D9FD2F /* Base */, 195 | ); 196 | name = Main.storyboard; 197 | sourceTree = ""; 198 | }; 199 | /* End PBXVariantGroup section */ 200 | 201 | /* Begin XCBuildConfiguration section */ 202 | 652006161F463E1700D9FD2F /* Debug */ = { 203 | isa = XCBuildConfiguration; 204 | buildSettings = { 205 | ALWAYS_SEARCH_USER_PATHS = NO; 206 | CLANG_ANALYZER_NONNULL = YES; 207 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 209 | CLANG_CXX_LIBRARY = "libc++"; 210 | CLANG_ENABLE_MODULES = YES; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_CONSTANT_CONVERSION = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 222 | CLANG_WARN_UNREACHABLE_CODE = YES; 223 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 224 | CODE_SIGN_IDENTITY = "-"; 225 | COPY_PHASE_STRIP = NO; 226 | DEBUG_INFORMATION_FORMAT = dwarf; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | ENABLE_TESTABILITY = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_OPTIMIZATION_LEVEL = 0; 233 | GCC_PREPROCESSOR_DEFINITIONS = ( 234 | "DEBUG=1", 235 | "$(inherited)", 236 | ); 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | MACOSX_DEPLOYMENT_TARGET = 10.11; 244 | MTL_ENABLE_DEBUG_INFO = YES; 245 | ONLY_ACTIVE_ARCH = YES; 246 | SDKROOT = macosx; 247 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 248 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 249 | }; 250 | name = Debug; 251 | }; 252 | 652006171F463E1700D9FD2F /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_ANALYZER_NONNULL = YES; 257 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | CODE_SIGN_IDENTITY = "-"; 275 | COPY_PHASE_STRIP = NO; 276 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 277 | ENABLE_NS_ASSERTIONS = NO; 278 | ENABLE_STRICT_OBJC_MSGSEND = YES; 279 | GCC_C_LANGUAGE_STANDARD = gnu99; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | MACOSX_DEPLOYMENT_TARGET = 10.11; 288 | MTL_ENABLE_DEBUG_INFO = NO; 289 | SDKROOT = macosx; 290 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 291 | }; 292 | name = Release; 293 | }; 294 | 652006191F463E1700D9FD2F /* Debug */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | COMBINE_HIDPI_IMAGES = YES; 299 | INFOPLIST_FILE = tyke/Info.plist; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 301 | PRODUCT_BUNDLE_IDENTIFIER = org.torrez.tyke; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_VERSION = 5.0; 304 | }; 305 | name = Debug; 306 | }; 307 | 6520061A1F463E1700D9FD2F /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | COMBINE_HIDPI_IMAGES = YES; 312 | INFOPLIST_FILE = tyke/Info.plist; 313 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 314 | PRODUCT_BUNDLE_IDENTIFIER = org.torrez.tyke; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | SWIFT_VERSION = 5.0; 317 | }; 318 | name = Release; 319 | }; 320 | /* End XCBuildConfiguration section */ 321 | 322 | /* Begin XCConfigurationList section */ 323 | 652006041F463E1700D9FD2F /* Build configuration list for PBXProject "tyke" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | 652006161F463E1700D9FD2F /* Debug */, 327 | 652006171F463E1700D9FD2F /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | 652006181F463E1700D9FD2F /* Build configuration list for PBXNativeTarget "tyke" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | 652006191F463E1700D9FD2F /* Debug */, 336 | 6520061A1F463E1700D9FD2F /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | /* End XCConfigurationList section */ 342 | }; 343 | rootObject = 652006011F463E1700D9FD2F /* Project object */; 344 | } 345 | -------------------------------------------------------------------------------- /HotKey/Key.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | 3 | public enum Key { 4 | 5 | // MARK: - Letters 6 | 7 | case a 8 | case b 9 | case c 10 | case d 11 | case e 12 | case f 13 | case g 14 | case h 15 | case i 16 | case j 17 | case k 18 | case l 19 | case m 20 | case n 21 | case o 22 | case p 23 | case q 24 | case r 25 | case s 26 | case t 27 | case u 28 | case v 29 | case w 30 | case x 31 | case y 32 | case z 33 | 34 | // MARK: - Numbers 35 | 36 | case zero 37 | case one 38 | case two 39 | case three 40 | case four 41 | case five 42 | case six 43 | case seven 44 | case eight 45 | case nine 46 | 47 | // MARK: - Symbols 48 | 49 | case period 50 | case quote 51 | case rightBracket 52 | case semicolon 53 | case slash 54 | case backslash 55 | case comma 56 | case equal 57 | case grave // Backtick 58 | case leftBracket 59 | case minus 60 | 61 | // MARK: - Whitespace 62 | 63 | case space 64 | case tab 65 | case `return` 66 | 67 | // MARK: - Modifiers 68 | 69 | case command 70 | case rightCommand 71 | case option 72 | case rightOption 73 | case control 74 | case rightControl 75 | case shift 76 | case rightShift 77 | case function 78 | case capsLock 79 | 80 | // MARK: - Navigation 81 | 82 | case pageUp 83 | case pageDown 84 | case home 85 | case end 86 | case upArrow 87 | case rightArrow 88 | case downArrow 89 | case leftArrow 90 | 91 | // MARK: - Functions 92 | 93 | case f1 94 | case f2 95 | case f3 96 | case f4 97 | case f5 98 | case f6 99 | case f7 100 | case f8 101 | case f9 102 | case f10 103 | case f11 104 | case f12 105 | case f13 106 | case f14 107 | case f15 108 | case f16 109 | case f17 110 | case f18 111 | case f19 112 | case f20 113 | 114 | // MARK: - Keypad 115 | 116 | case keypad0 117 | case keypad1 118 | case keypad2 119 | case keypad3 120 | case keypad4 121 | case keypad5 122 | case keypad6 123 | case keypad7 124 | case keypad8 125 | case keypad9 126 | case keypadClear 127 | case keypadDecimal 128 | case keypadDivide 129 | case keypadEnter 130 | case keypadEquals 131 | case keypadMinus 132 | case keypadMultiply 133 | case keypadPlus 134 | 135 | // MARK: - Misc 136 | 137 | case escape 138 | case delete 139 | case forwardDelete 140 | case help 141 | case volumeUp 142 | case volumeDown 143 | case mute 144 | 145 | // MARK: - Initializers 146 | 147 | public init?(string: String) { 148 | switch string.lowercased() { 149 | case "a": self = .a 150 | case "s": self = .s 151 | case "d": self = .d 152 | case "f": self = .f 153 | case "h": self = .h 154 | case "g": self = .g 155 | case "z": self = .z 156 | case "x": self = .x 157 | case "c": self = .c 158 | case "v": self = .v 159 | case "b": self = .b 160 | case "q": self = .q 161 | case "w": self = .w 162 | case "e": self = .e 163 | case "r": self = .r 164 | case "y": self = .y 165 | case "t": self = .t 166 | case "one", "1": self = .one 167 | case "two", "2": self = .two 168 | case "three", "3": self = .three 169 | case "four", "4": self = .four 170 | case "six", "6": self = .six 171 | case "five", "5": self = .five 172 | case "equal", "=": self = .equal 173 | case "nine", "9": self = .nine 174 | case "seven", "7": self = .seven 175 | case "minus", "-": self = .minus 176 | case "eight", "8": self = .eight 177 | case "zero", "0": self = .zero 178 | case "rightBracket", "]": self = .rightBracket 179 | case "o": self = .o 180 | case "u": self = .u 181 | case "leftBracket", "[": self = .leftBracket 182 | case "i": self = .i 183 | case "p": self = .p 184 | case "l": self = .l 185 | case "j": self = .j 186 | case "quote", "\"": self = .quote 187 | case "k": self = .k 188 | case "semicolon", ";": self = .semicolon 189 | case "backslash", "\\": self = .backslash 190 | case "comma", ",": self = .comma 191 | case "slash", "/": self = .slash 192 | case "n": self = .n 193 | case "m": self = .m 194 | case "period", ".": self = .period 195 | case "grave", "`", "ˋ", "`": self = .grave 196 | case "keypaddecimal": self = .keypadDecimal 197 | case "keypadmultiply": self = .keypadMultiply 198 | case "keypadplus": self = .keypadPlus 199 | case "keypadclear", "⌧": self = .keypadClear 200 | case "keypaddivide": self = .keypadDivide 201 | case "keypadenter": self = .keypadEnter 202 | case "keypadminus": self = .keypadMinus 203 | case "keypadequals": self = .keypadEquals 204 | case "keypad0": self = .keypad0 205 | case "keypad1": self = .keypad1 206 | case "keypad2": self = .keypad2 207 | case "keypad3": self = .keypad3 208 | case "keypad4": self = .keypad4 209 | case "keypad5": self = .keypad5 210 | case "keypad6": self = .keypad6 211 | case "keypad7": self = .keypad7 212 | case "keypad8": self = .keypad8 213 | case "keypad9": self = .keypad9 214 | case "return", "\r", "↩︎", "⏎", "⮐": self = .return 215 | case "tab", "\t", "⇥": self = .tab 216 | case "space", " ", "␣": self = .space 217 | case "delete", "⌫": self = .delete 218 | case "escape", "⎋": self = .escape 219 | case "command", "⌘", "": self = .command 220 | case "shift", "⇧": self = .shift 221 | case "capslock", "⇪": self = .capsLock 222 | case "option", "⌥": self = .option 223 | case "control", "⌃": self = .control 224 | case "rightcommand": self = .rightCommand 225 | case "rightshift": self = .rightShift 226 | case "rightoption": self = .rightOption 227 | case "rightcontrol": self = .rightControl 228 | case "function", "fn": self = .function 229 | case "f17", "F17": self = .f17 230 | case "volumeup", "🔊": self = .volumeUp 231 | case "volumedown", "🔉": self = .volumeDown 232 | case "mute", "🔇": self = .mute 233 | case "f18", "F18": self = .f18 234 | case "f19", "F19": self = .f19 235 | case "f20", "F20": self = .f20 236 | case "f5", "F5": self = .f5 237 | case "f6", "F6": self = .f6 238 | case "f7", "F7": self = .f7 239 | case "f3", "F3": self = .f3 240 | case "f8", "F8": self = .f8 241 | case "f9", "F9": self = .f9 242 | case "f11", "F11": self = .f11 243 | case "f13", "F13": self = .f13 244 | case "f16", "F16": self = .f16 245 | case "f14", "F14": self = .f14 246 | case "f10", "F10": self = .f10 247 | case "f12", "F12": self = .f12 248 | case "f15", "F15": self = .f15 249 | case "help", "?⃝": self = .help 250 | case "home", "↖": self = .home 251 | case "pageup", "⇞": self = .pageUp 252 | case "forwarddelete", "⌦": self = .forwardDelete 253 | case "f4", "F4": self = .f4 254 | case "end", "↘": self = .end 255 | case "f2", "F2": self = .f2 256 | case "pagedown", "⇟": self = .pageDown 257 | case "f1", "F1": self = .f1 258 | case "leftarrow", "←": self = .leftArrow 259 | case "rightarrow", "→": self = .rightArrow 260 | case "downarrow", "↓": self = .downArrow 261 | case "uparrow", "↑": self = .upArrow 262 | default: return nil 263 | } 264 | } 265 | 266 | public init?(carbonKeyCode: UInt32) { 267 | switch carbonKeyCode { 268 | case UInt32(kVK_ANSI_A): self = .a 269 | case UInt32(kVK_ANSI_S): self = .s 270 | case UInt32(kVK_ANSI_D): self = .d 271 | case UInt32(kVK_ANSI_F): self = .f 272 | case UInt32(kVK_ANSI_H): self = .h 273 | case UInt32(kVK_ANSI_G): self = .g 274 | case UInt32(kVK_ANSI_Z): self = .z 275 | case UInt32(kVK_ANSI_X): self = .x 276 | case UInt32(kVK_ANSI_C): self = .c 277 | case UInt32(kVK_ANSI_V): self = .v 278 | case UInt32(kVK_ANSI_B): self = .b 279 | case UInt32(kVK_ANSI_Q): self = .q 280 | case UInt32(kVK_ANSI_W): self = .w 281 | case UInt32(kVK_ANSI_E): self = .e 282 | case UInt32(kVK_ANSI_R): self = .r 283 | case UInt32(kVK_ANSI_Y): self = .y 284 | case UInt32(kVK_ANSI_T): self = .t 285 | case UInt32(kVK_ANSI_1): self = .one 286 | case UInt32(kVK_ANSI_2): self = .two 287 | case UInt32(kVK_ANSI_3): self = .three 288 | case UInt32(kVK_ANSI_4): self = .four 289 | case UInt32(kVK_ANSI_6): self = .six 290 | case UInt32(kVK_ANSI_5): self = .five 291 | case UInt32(kVK_ANSI_Equal): self = .equal 292 | case UInt32(kVK_ANSI_9): self = .nine 293 | case UInt32(kVK_ANSI_7): self = .seven 294 | case UInt32(kVK_ANSI_Minus): self = .minus 295 | case UInt32(kVK_ANSI_8): self = .eight 296 | case UInt32(kVK_ANSI_0): self = .zero 297 | case UInt32(kVK_ANSI_RightBracket): self = .rightBracket 298 | case UInt32(kVK_ANSI_O): self = .o 299 | case UInt32(kVK_ANSI_U): self = .u 300 | case UInt32(kVK_ANSI_LeftBracket): self = .leftBracket 301 | case UInt32(kVK_ANSI_I): self = .i 302 | case UInt32(kVK_ANSI_P): self = .p 303 | case UInt32(kVK_ANSI_L): self = .l 304 | case UInt32(kVK_ANSI_J): self = .j 305 | case UInt32(kVK_ANSI_Quote): self = .quote 306 | case UInt32(kVK_ANSI_K): self = .k 307 | case UInt32(kVK_ANSI_Semicolon): self = .semicolon 308 | case UInt32(kVK_ANSI_Backslash): self = .backslash 309 | case UInt32(kVK_ANSI_Comma): self = .comma 310 | case UInt32(kVK_ANSI_Slash): self = .slash 311 | case UInt32(kVK_ANSI_N): self = .n 312 | case UInt32(kVK_ANSI_M): self = .m 313 | case UInt32(kVK_ANSI_Period): self = .period 314 | case UInt32(kVK_ANSI_Grave): self = .grave 315 | case UInt32(kVK_ANSI_KeypadDecimal): self = .keypadDecimal 316 | case UInt32(kVK_ANSI_KeypadMultiply): self = .keypadMultiply 317 | case UInt32(kVK_ANSI_KeypadPlus): self = .keypadPlus 318 | case UInt32(kVK_ANSI_KeypadClear): self = .keypadClear 319 | case UInt32(kVK_ANSI_KeypadDivide): self = .keypadDivide 320 | case UInt32(kVK_ANSI_KeypadEnter): self = .keypadEnter 321 | case UInt32(kVK_ANSI_KeypadMinus): self = .keypadMinus 322 | case UInt32(kVK_ANSI_KeypadEquals): self = .keypadEquals 323 | case UInt32(kVK_ANSI_Keypad0): self = .keypad0 324 | case UInt32(kVK_ANSI_Keypad1): self = .keypad1 325 | case UInt32(kVK_ANSI_Keypad2): self = .keypad2 326 | case UInt32(kVK_ANSI_Keypad3): self = .keypad3 327 | case UInt32(kVK_ANSI_Keypad4): self = .keypad4 328 | case UInt32(kVK_ANSI_Keypad5): self = .keypad5 329 | case UInt32(kVK_ANSI_Keypad6): self = .keypad6 330 | case UInt32(kVK_ANSI_Keypad7): self = .keypad7 331 | case UInt32(kVK_ANSI_Keypad8): self = .keypad8 332 | case UInt32(kVK_ANSI_Keypad9): self = .keypad9 333 | case UInt32(kVK_Return): self = .`return` 334 | case UInt32(kVK_Tab): self = .tab 335 | case UInt32(kVK_Space): self = .space 336 | case UInt32(kVK_Delete): self = .delete 337 | case UInt32(kVK_Escape): self = .escape 338 | case UInt32(kVK_Command): self = .command 339 | case UInt32(kVK_Shift): self = .shift 340 | case UInt32(kVK_CapsLock): self = .capsLock 341 | case UInt32(kVK_Option): self = .option 342 | case UInt32(kVK_Control): self = .control 343 | case UInt32(kVK_RightCommand): self = .rightCommand 344 | case UInt32(kVK_RightShift): self = .rightShift 345 | case UInt32(kVK_RightOption): self = .rightOption 346 | case UInt32(kVK_RightControl): self = .rightControl 347 | case UInt32(kVK_Function): self = .function 348 | case UInt32(kVK_F17): self = .f17 349 | case UInt32(kVK_VolumeUp): self = .volumeUp 350 | case UInt32(kVK_VolumeDown): self = .volumeDown 351 | case UInt32(kVK_Mute): self = .mute 352 | case UInt32(kVK_F18): self = .f18 353 | case UInt32(kVK_F19): self = .f19 354 | case UInt32(kVK_F20): self = .f20 355 | case UInt32(kVK_F5): self = .f5 356 | case UInt32(kVK_F6): self = .f6 357 | case UInt32(kVK_F7): self = .f7 358 | case UInt32(kVK_F3): self = .f3 359 | case UInt32(kVK_F8): self = .f8 360 | case UInt32(kVK_F9): self = .f9 361 | case UInt32(kVK_F11): self = .f11 362 | case UInt32(kVK_F13): self = .f13 363 | case UInt32(kVK_F16): self = .f16 364 | case UInt32(kVK_F14): self = .f14 365 | case UInt32(kVK_F10): self = .f10 366 | case UInt32(kVK_F12): self = .f12 367 | case UInt32(kVK_F15): self = .f15 368 | case UInt32(kVK_Help): self = .help 369 | case UInt32(kVK_Home): self = .home 370 | case UInt32(kVK_PageUp): self = .pageUp 371 | case UInt32(kVK_ForwardDelete): self = .forwardDelete 372 | case UInt32(kVK_F4): self = .f4 373 | case UInt32(kVK_End): self = .end 374 | case UInt32(kVK_F2): self = .f2 375 | case UInt32(kVK_PageDown): self = .pageDown 376 | case UInt32(kVK_F1): self = .f1 377 | case UInt32(kVK_LeftArrow): self = .leftArrow 378 | case UInt32(kVK_RightArrow): self = .rightArrow 379 | case UInt32(kVK_DownArrow): self = .downArrow 380 | case UInt32(kVK_UpArrow): self = .upArrow 381 | default: return nil 382 | } 383 | } 384 | 385 | public var carbonKeyCode: UInt32 { 386 | switch self { 387 | case .a: return UInt32(kVK_ANSI_A) 388 | case .s: return UInt32(kVK_ANSI_S) 389 | case .d: return UInt32(kVK_ANSI_D) 390 | case .f: return UInt32(kVK_ANSI_F) 391 | case .h: return UInt32(kVK_ANSI_H) 392 | case .g: return UInt32(kVK_ANSI_G) 393 | case .z: return UInt32(kVK_ANSI_Z) 394 | case .x: return UInt32(kVK_ANSI_X) 395 | case .c: return UInt32(kVK_ANSI_C) 396 | case .v: return UInt32(kVK_ANSI_V) 397 | case .b: return UInt32(kVK_ANSI_B) 398 | case .q: return UInt32(kVK_ANSI_Q) 399 | case .w: return UInt32(kVK_ANSI_W) 400 | case .e: return UInt32(kVK_ANSI_E) 401 | case .r: return UInt32(kVK_ANSI_R) 402 | case .y: return UInt32(kVK_ANSI_Y) 403 | case .t: return UInt32(kVK_ANSI_T) 404 | case .one: return UInt32(kVK_ANSI_1) 405 | case .two: return UInt32(kVK_ANSI_2) 406 | case .three: return UInt32(kVK_ANSI_3) 407 | case .four: return UInt32(kVK_ANSI_4) 408 | case .six: return UInt32(kVK_ANSI_6) 409 | case .five: return UInt32(kVK_ANSI_5) 410 | case .equal: return UInt32(kVK_ANSI_Equal) 411 | case .nine: return UInt32(kVK_ANSI_9) 412 | case .seven: return UInt32(kVK_ANSI_7) 413 | case .minus: return UInt32(kVK_ANSI_Minus) 414 | case .eight: return UInt32(kVK_ANSI_8) 415 | case .zero: return UInt32(kVK_ANSI_0) 416 | case .rightBracket: return UInt32(kVK_ANSI_RightBracket) 417 | case .o: return UInt32(kVK_ANSI_O) 418 | case .u: return UInt32(kVK_ANSI_U) 419 | case .leftBracket: return UInt32(kVK_ANSI_LeftBracket) 420 | case .i: return UInt32(kVK_ANSI_I) 421 | case .p: return UInt32(kVK_ANSI_P) 422 | case .l: return UInt32(kVK_ANSI_L) 423 | case .j: return UInt32(kVK_ANSI_J) 424 | case .quote: return UInt32(kVK_ANSI_Quote) 425 | case .k: return UInt32(kVK_ANSI_K) 426 | case .semicolon: return UInt32(kVK_ANSI_Semicolon) 427 | case .backslash: return UInt32(kVK_ANSI_Backslash) 428 | case .comma: return UInt32(kVK_ANSI_Comma) 429 | case .slash: return UInt32(kVK_ANSI_Slash) 430 | case .n: return UInt32(kVK_ANSI_N) 431 | case .m: return UInt32(kVK_ANSI_M) 432 | case .period: return UInt32(kVK_ANSI_Period) 433 | case .grave: return UInt32(kVK_ANSI_Grave) 434 | case .keypadDecimal: return UInt32(kVK_ANSI_KeypadDecimal) 435 | case .keypadMultiply: return UInt32(kVK_ANSI_KeypadMultiply) 436 | case .keypadPlus: return UInt32(kVK_ANSI_KeypadPlus) 437 | case .keypadClear: return UInt32(kVK_ANSI_KeypadClear) 438 | case .keypadDivide: return UInt32(kVK_ANSI_KeypadDivide) 439 | case .keypadEnter: return UInt32(kVK_ANSI_KeypadEnter) 440 | case .keypadMinus: return UInt32(kVK_ANSI_KeypadMinus) 441 | case .keypadEquals: return UInt32(kVK_ANSI_KeypadEquals) 442 | case .keypad0: return UInt32(kVK_ANSI_Keypad0) 443 | case .keypad1: return UInt32(kVK_ANSI_Keypad1) 444 | case .keypad2: return UInt32(kVK_ANSI_Keypad2) 445 | case .keypad3: return UInt32(kVK_ANSI_Keypad3) 446 | case .keypad4: return UInt32(kVK_ANSI_Keypad4) 447 | case .keypad5: return UInt32(kVK_ANSI_Keypad5) 448 | case .keypad6: return UInt32(kVK_ANSI_Keypad6) 449 | case .keypad7: return UInt32(kVK_ANSI_Keypad7) 450 | case .keypad8: return UInt32(kVK_ANSI_Keypad8) 451 | case .keypad9: return UInt32(kVK_ANSI_Keypad9) 452 | case .`return`: return UInt32(kVK_Return) 453 | case .tab: return UInt32(kVK_Tab) 454 | case .space: return UInt32(kVK_Space) 455 | case .delete: return UInt32(kVK_Delete) 456 | case .escape: return UInt32(kVK_Escape) 457 | case .command: return UInt32(kVK_Command) 458 | case .shift: return UInt32(kVK_Shift) 459 | case .capsLock: return UInt32(kVK_CapsLock) 460 | case .option: return UInt32(kVK_Option) 461 | case .control: return UInt32(kVK_Control) 462 | case .rightCommand: return UInt32(kVK_RightCommand) 463 | case .rightShift: return UInt32(kVK_RightShift) 464 | case .rightOption: return UInt32(kVK_RightOption) 465 | case .rightControl: return UInt32(kVK_RightControl) 466 | case .function: return UInt32(kVK_Function) 467 | case .f17: return UInt32(kVK_F17) 468 | case .volumeUp: return UInt32(kVK_VolumeUp) 469 | case .volumeDown: return UInt32(kVK_VolumeDown) 470 | case .mute: return UInt32(kVK_Mute) 471 | case .f18: return UInt32(kVK_F18) 472 | case .f19: return UInt32(kVK_F19) 473 | case .f20: return UInt32(kVK_F20) 474 | case .f5: return UInt32(kVK_F5) 475 | case .f6: return UInt32(kVK_F6) 476 | case .f7: return UInt32(kVK_F7) 477 | case .f3: return UInt32(kVK_F3) 478 | case .f8: return UInt32(kVK_F8) 479 | case .f9: return UInt32(kVK_F9) 480 | case .f11: return UInt32(kVK_F11) 481 | case .f13: return UInt32(kVK_F13) 482 | case .f16: return UInt32(kVK_F16) 483 | case .f14: return UInt32(kVK_F14) 484 | case .f10: return UInt32(kVK_F10) 485 | case .f12: return UInt32(kVK_F12) 486 | case .f15: return UInt32(kVK_F15) 487 | case .help: return UInt32(kVK_Help) 488 | case .home: return UInt32(kVK_Home) 489 | case .pageUp: return UInt32(kVK_PageUp) 490 | case .forwardDelete: return UInt32(kVK_ForwardDelete) 491 | case .f4: return UInt32(kVK_F4) 492 | case .end: return UInt32(kVK_End) 493 | case .f2: return UInt32(kVK_F2) 494 | case .pageDown: return UInt32(kVK_PageDown) 495 | case .f1: return UInt32(kVK_F1) 496 | case .leftArrow: return UInt32(kVK_LeftArrow) 497 | case .rightArrow: return UInt32(kVK_RightArrow) 498 | case .downArrow: return UInt32(kVK_DownArrow) 499 | case .upArrow: return UInt32(kVK_UpArrow) 500 | } 501 | } 502 | } 503 | 504 | extension Key: CustomStringConvertible { 505 | public var description: String { 506 | switch self { 507 | case .a: return "A" 508 | case .s: return "S" 509 | case .d: return "D" 510 | case .f: return "F" 511 | case .h: return "H" 512 | case .g: return "G" 513 | case .z: return "Z" 514 | case .x: return "X" 515 | case .c: return "C" 516 | case .v: return "V" 517 | case .b: return "B" 518 | case .q: return "Q" 519 | case .w: return "W" 520 | case .e: return "E" 521 | case .r: return "R" 522 | case .y: return "Y" 523 | case .t: return "T" 524 | case .one, .keypad1: return "1" 525 | case .two, .keypad2: return "2" 526 | case .three, .keypad3: return "3" 527 | case .four, .keypad4: return "4" 528 | case .six, .keypad6: return "6" 529 | case .five, .keypad5: return "5" 530 | case .equal: return "=" 531 | case .nine, .keypad9: return "9" 532 | case .seven, .keypad7: return "7" 533 | case .minus: return "-" 534 | case .eight, .keypad8: return "8" 535 | case .zero, .keypad0: return "0" 536 | case .rightBracket: return "]" 537 | case .o: return "O" 538 | case .u: return "U" 539 | case .leftBracket: return "[" 540 | case .i: return "I" 541 | case .p: return "P" 542 | case .l: return "L" 543 | case .j: return "J" 544 | case .quote: return "\"" 545 | case .k: return "K" 546 | case .semicolon: return ";" 547 | case .backslash: return "\\" 548 | case .comma: return "," 549 | case .slash: return "/" 550 | case .n: return "N" 551 | case .m: return "M" 552 | case .period: return "." 553 | case .grave: return "`" 554 | case .keypadDecimal: return "." 555 | case .keypadMultiply: return "𝗑" 556 | case .keypadPlus: return "+" 557 | case .keypadClear: return "⌧" 558 | case .keypadDivide: return "/" 559 | case .keypadEnter: return "↩︎" 560 | case .keypadMinus: return "-" 561 | case .keypadEquals: return "=" 562 | case .`return`: return "↩︎" 563 | case .tab: return "⇥" 564 | case .space: return "␣" 565 | case .delete: return "⌫" 566 | case .escape: return "⎋" 567 | case .command, .rightCommand: return "⌘" 568 | case .shift, .rightShift: return "⇧" 569 | case .capsLock: return "⇪" 570 | case .option, .rightOption: return "⌥" 571 | case .control, .rightControl: return "⌃" 572 | case .function: return "fn" 573 | case .f17: return "F17" 574 | case .volumeUp: return "🔊" 575 | case .volumeDown: return "🔉" 576 | case .mute: return "🔇" 577 | case .f18: return "F18" 578 | case .f19: return "F19" 579 | case .f20: return "F20" 580 | case .f5: return "F5" 581 | case .f6: return "F6" 582 | case .f7: return "F7" 583 | case .f3: return "F3" 584 | case .f8: return "F8" 585 | case .f9: return "F9" 586 | case .f11: return "F11" 587 | case .f13: return "F13" 588 | case .f16: return "F16" 589 | case .f14: return "F14" 590 | case .f10: return "F10" 591 | case .f12: return "F12" 592 | case .f15: return "F15" 593 | case .help: return "?⃝" 594 | case .home: return "↖" 595 | case .pageUp: return "⇞" 596 | case .forwardDelete: return "⌦" 597 | case .f4: return "F4" 598 | case .end: return "↘" 599 | case .f2: return "F2" 600 | case .pageDown: return "⇟" 601 | case .f1: return "F1" 602 | case .leftArrow: return "←" 603 | case .rightArrow: return "→" 604 | case .downArrow: return "↓" 605 | case .upArrow: return "↑" 606 | } 607 | } 608 | } 609 | --------------------------------------------------------------------------------