├── 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 |
40 |
41 |
42 |
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 |
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 |
--------------------------------------------------------------------------------