├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── Example
├── Sources
│ ├── AppDelegate.swift
│ └── ViewController.swift
└── Support
│ ├── Base.lproj
│ └── Main.storyboard
│ └── Info.plist
├── HotKey.podspec
├── HotKey.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ ├── Example.xcscheme
│ └── HotKey.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── HotKey
│ ├── HotKey.swift
│ ├── HotKeysController.swift
│ ├── Key.swift
│ ├── KeyCombo+System.swift
│ ├── KeyCombo.swift
│ └── NSEventModifierFlags+HotKey.swift
├── Support
├── HotKey.h
├── Info.plist
└── Tests-Info.plist
└── Tests
└── HotKeyTests
├── KeyComboTests.swift
└── ModifierFlagsTests.swift
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on: [push]
3 | jobs:
4 | test:
5 | name: Test
6 | runs-on: macOS-15
7 | timeout-minutes: 5
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v4
11 | - name: Run tests
12 | run: swift test
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | *.zip
6 |
--------------------------------------------------------------------------------
/Example/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 |
3 | @NSApplicationMain final class AppDelegate: NSObject, NSApplicationDelegate {}
4 |
--------------------------------------------------------------------------------
/Example/Sources/ViewController.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 | import HotKey
3 | import Carbon
4 |
5 | final class ViewController: NSViewController {
6 |
7 | // MARK: - Properties
8 |
9 | @IBOutlet var pressedLabel: NSTextField!
10 |
11 | private var hotKey: HotKey? {
12 | didSet {
13 | guard let hotKey = hotKey else {
14 | pressedLabel.stringValue = "Unregistered"
15 | return
16 | }
17 |
18 | pressedLabel.stringValue = "Registered"
19 |
20 | hotKey.keyDownHandler = { [weak self] in
21 | self?.pressedLabel.stringValue = "Pressed at \(Date())"
22 | }
23 | }
24 | }
25 |
26 |
27 | // MARK: - NSViewController
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 | register(self)
32 | }
33 |
34 |
35 | // MARK: - Actions
36 |
37 | @IBAction func unregister(_ sender: Any?) {
38 | hotKey = nil
39 | }
40 |
41 | @IBAction func register(_ sender: Any?) {
42 | hotKey = HotKey(keyCombo: KeyCombo(key: .r, modifiers: [.command, .option]))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Example/Support/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
714 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
--------------------------------------------------------------------------------
/Example/Support/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2017 Sam Soffes. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/HotKey.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'HotKey'
3 | spec.version = '0.2.1'
4 | spec.authors = {'Sam Soffes' => 'sam@soff.es'}
5 | spec.homepage = 'https://github.com/soffes/HotKey'
6 | spec.summary = 'Simple global shortcuts in macOS.'
7 | spec.source = {:git => 'https://github.com/soffes/HotKey.git', :tag => "v#{spec.version}"}
8 | spec.license = { :type => 'MIT', :file => 'LICENSE' }
9 |
10 | spec.osx.deployment_target = '10.13'
11 | spec.swift_version = '5.0'
12 |
13 | spec.frameworks = 'AppKit', 'Carbon'
14 | spec.source_files = 'Sources/HotKey/**/*.{h,m,swift}'
15 | end
16 |
--------------------------------------------------------------------------------
/HotKey.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 53;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 21739E66233E992B00F3A8EF /* KeyCombo+System.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21739E65233E992B00F3A8EF /* KeyCombo+System.swift */; };
11 | 21887E401F2209DE00A05579 /* HotKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 21887E361F2209DE00A05579 /* HotKey.framework */; };
12 | 21887E8B1F22156900A05579 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21887E841F22156900A05579 /* AppDelegate.swift */; };
13 | 21887E8C1F22156900A05579 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21887E851F22156900A05579 /* ViewController.swift */; };
14 | 21887E8E1F22156900A05579 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21887E881F22156900A05579 /* Main.storyboard */; };
15 | 21887E931F22158000A05579 /* HotKey.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 21887E361F2209DE00A05579 /* HotKey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16 | 21887E951F22159100A05579 /* HotKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 21887E361F2209DE00A05579 /* HotKey.framework */; };
17 | 219E0D191FC5E8AB00F80DF1 /* NSEventModifierFlags+HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D0E1FC5E8AB00F80DF1 /* NSEventModifierFlags+HotKey.swift */; };
18 | 219E0D1A1FC5E8AB00F80DF1 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D0F1FC5E8AB00F80DF1 /* KeyCombo.swift */; };
19 | 219E0D1B1FC5E8AB00F80DF1 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D101FC5E8AB00F80DF1 /* Key.swift */; };
20 | 219E0D1C1FC5E8AB00F80DF1 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D111FC5E8AB00F80DF1 /* HotKeysController.swift */; };
21 | 219E0D1D1FC5E8AB00F80DF1 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D121FC5E8AB00F80DF1 /* HotKey.swift */; };
22 | 219E0D1F1FC5E8AB00F80DF1 /* HotKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 219E0D151FC5E8AB00F80DF1 /* HotKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
23 | 219E0D211FC5E8BF00F80DF1 /* ModifierFlagsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D0A1FC5E8AB00F80DF1 /* ModifierFlagsTests.swift */; };
24 | 219E0D221FC5E8BF00F80DF1 /* KeyComboTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219E0D0B1FC5E8AB00F80DF1 /* KeyComboTests.swift */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXContainerItemProxy section */
28 | 21887E411F2209DE00A05579 /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 21887E2D1F2209DE00A05579 /* Project object */;
31 | proxyType = 1;
32 | remoteGlobalIDString = 21887E351F2209DE00A05579;
33 | remoteInfo = HotKey;
34 | };
35 | 21887E901F22157300A05579 /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = 21887E2D1F2209DE00A05579 /* Project object */;
38 | proxyType = 1;
39 | remoteGlobalIDString = 21887E351F2209DE00A05579;
40 | remoteInfo = HotKey;
41 | };
42 | /* End PBXContainerItemProxy section */
43 |
44 | /* Begin PBXCopyFilesBuildPhase section */
45 | 21887E921F22157700A05579 /* Embed Frameworks */ = {
46 | isa = PBXCopyFilesBuildPhase;
47 | buildActionMask = 2147483647;
48 | dstPath = "";
49 | dstSubfolderSpec = 10;
50 | files = (
51 | 21887E931F22158000A05579 /* HotKey.framework in Embed Frameworks */,
52 | );
53 | name = "Embed Frameworks";
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXCopyFilesBuildPhase section */
57 |
58 | /* Begin PBXFileReference section */
59 | 21739E65233E992B00F3A8EF /* KeyCombo+System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyCombo+System.swift"; sourceTree = ""; };
60 | 21887E361F2209DE00A05579 /* HotKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HotKey.framework; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 21887E3F1F2209DE00A05579 /* HotKeyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HotKeyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
62 | 21887E661F22154600A05579 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
63 | 21887E841F22156900A05579 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
64 | 21887E851F22156900A05579 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
65 | 21887E891F22156900A05579 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
66 | 21887E8A1F22156900A05579 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
67 | 219E0D0A1FC5E8AB00F80DF1 /* ModifierFlagsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierFlagsTests.swift; sourceTree = ""; };
68 | 219E0D0B1FC5E8AB00F80DF1 /* KeyComboTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyComboTests.swift; sourceTree = ""; };
69 | 219E0D0E1FC5E8AB00F80DF1 /* NSEventModifierFlags+HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEventModifierFlags+HotKey.swift"; sourceTree = ""; };
70 | 219E0D0F1FC5E8AB00F80DF1 /* KeyCombo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyCombo.swift; sourceTree = ""; };
71 | 219E0D101FC5E8AB00F80DF1 /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = ""; };
72 | 219E0D111FC5E8AB00F80DF1 /* HotKeysController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKeysController.swift; sourceTree = ""; };
73 | 219E0D121FC5E8AB00F80DF1 /* HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKey.swift; sourceTree = ""; };
74 | 219E0D141FC5E8AB00F80DF1 /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; };
75 | 219E0D151FC5E8AB00F80DF1 /* HotKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HotKey.h; sourceTree = ""; };
76 | 219E0D161FC5E8AB00F80DF1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
77 | /* End PBXFileReference section */
78 |
79 | /* Begin PBXFrameworksBuildPhase section */
80 | 21887E321F2209DE00A05579 /* Frameworks */ = {
81 | isa = PBXFrameworksBuildPhase;
82 | buildActionMask = 2147483647;
83 | files = (
84 | );
85 | runOnlyForDeploymentPostprocessing = 0;
86 | };
87 | 21887E3C1F2209DE00A05579 /* Frameworks */ = {
88 | isa = PBXFrameworksBuildPhase;
89 | buildActionMask = 2147483647;
90 | files = (
91 | 21887E401F2209DE00A05579 /* HotKey.framework in Frameworks */,
92 | );
93 | runOnlyForDeploymentPostprocessing = 0;
94 | };
95 | 21887E631F22154600A05579 /* Frameworks */ = {
96 | isa = PBXFrameworksBuildPhase;
97 | buildActionMask = 2147483647;
98 | files = (
99 | 21887E951F22159100A05579 /* HotKey.framework in Frameworks */,
100 | );
101 | runOnlyForDeploymentPostprocessing = 0;
102 | };
103 | /* End PBXFrameworksBuildPhase section */
104 |
105 | /* Begin PBXGroup section */
106 | 21887E2C1F2209DE00A05579 = {
107 | isa = PBXGroup;
108 | children = (
109 | 219E0D0C1FC5E8AB00F80DF1 /* Sources */,
110 | 219E0D131FC5E8AB00F80DF1 /* Support */,
111 | 219E0D081FC5E8AB00F80DF1 /* Tests */,
112 | 21887E671F22154600A05579 /* Example */,
113 | 21887E371F2209DE00A05579 /* Products */,
114 | );
115 | sourceTree = "";
116 | };
117 | 21887E371F2209DE00A05579 /* Products */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 21887E361F2209DE00A05579 /* HotKey.framework */,
121 | 21887E3F1F2209DE00A05579 /* HotKeyTests.xctest */,
122 | 21887E661F22154600A05579 /* Example.app */,
123 | );
124 | name = Products;
125 | sourceTree = "";
126 | };
127 | 21887E671F22154600A05579 /* Example */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 21887E831F22156900A05579 /* Sources */,
131 | 21887E861F22156900A05579 /* Support */,
132 | );
133 | path = Example;
134 | sourceTree = "";
135 | };
136 | 21887E831F22156900A05579 /* Sources */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 21887E841F22156900A05579 /* AppDelegate.swift */,
140 | 21887E851F22156900A05579 /* ViewController.swift */,
141 | );
142 | path = Sources;
143 | sourceTree = "";
144 | };
145 | 21887E861F22156900A05579 /* Support */ = {
146 | isa = PBXGroup;
147 | children = (
148 | 21887E881F22156900A05579 /* Main.storyboard */,
149 | 21887E8A1F22156900A05579 /* Info.plist */,
150 | );
151 | path = Support;
152 | sourceTree = "";
153 | };
154 | 219E0D081FC5E8AB00F80DF1 /* Tests */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 219E0D091FC5E8AB00F80DF1 /* HotKeyTests */,
158 | );
159 | path = Tests;
160 | sourceTree = "";
161 | };
162 | 219E0D091FC5E8AB00F80DF1 /* HotKeyTests */ = {
163 | isa = PBXGroup;
164 | children = (
165 | 219E0D0A1FC5E8AB00F80DF1 /* ModifierFlagsTests.swift */,
166 | 219E0D0B1FC5E8AB00F80DF1 /* KeyComboTests.swift */,
167 | );
168 | path = HotKeyTests;
169 | sourceTree = "";
170 | };
171 | 219E0D0C1FC5E8AB00F80DF1 /* Sources */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 219E0D0D1FC5E8AB00F80DF1 /* HotKey */,
175 | );
176 | path = Sources;
177 | sourceTree = "";
178 | };
179 | 219E0D0D1FC5E8AB00F80DF1 /* HotKey */ = {
180 | isa = PBXGroup;
181 | children = (
182 | 219E0D121FC5E8AB00F80DF1 /* HotKey.swift */,
183 | 219E0D111FC5E8AB00F80DF1 /* HotKeysController.swift */,
184 | 219E0D101FC5E8AB00F80DF1 /* Key.swift */,
185 | 219E0D0F1FC5E8AB00F80DF1 /* KeyCombo.swift */,
186 | 21739E65233E992B00F3A8EF /* KeyCombo+System.swift */,
187 | 219E0D0E1FC5E8AB00F80DF1 /* NSEventModifierFlags+HotKey.swift */,
188 | );
189 | path = HotKey;
190 | sourceTree = "";
191 | };
192 | 219E0D131FC5E8AB00F80DF1 /* Support */ = {
193 | isa = PBXGroup;
194 | children = (
195 | 219E0D141FC5E8AB00F80DF1 /* Tests-Info.plist */,
196 | 219E0D151FC5E8AB00F80DF1 /* HotKey.h */,
197 | 219E0D161FC5E8AB00F80DF1 /* Info.plist */,
198 | );
199 | path = Support;
200 | sourceTree = "";
201 | };
202 | /* End PBXGroup section */
203 |
204 | /* Begin PBXHeadersBuildPhase section */
205 | 21887E331F2209DE00A05579 /* Headers */ = {
206 | isa = PBXHeadersBuildPhase;
207 | buildActionMask = 2147483647;
208 | files = (
209 | 219E0D1F1FC5E8AB00F80DF1 /* HotKey.h in Headers */,
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | /* End PBXHeadersBuildPhase section */
214 |
215 | /* Begin PBXNativeTarget section */
216 | 21887E351F2209DE00A05579 /* HotKey */ = {
217 | isa = PBXNativeTarget;
218 | buildConfigurationList = 21887E4A1F2209DE00A05579 /* Build configuration list for PBXNativeTarget "HotKey" */;
219 | buildPhases = (
220 | 21887E311F2209DE00A05579 /* Sources */,
221 | 21887E321F2209DE00A05579 /* Frameworks */,
222 | 21887E331F2209DE00A05579 /* Headers */,
223 | 21887E341F2209DE00A05579 /* Resources */,
224 | );
225 | buildRules = (
226 | );
227 | dependencies = (
228 | );
229 | name = HotKey;
230 | productName = HotKey;
231 | productReference = 21887E361F2209DE00A05579 /* HotKey.framework */;
232 | productType = "com.apple.product-type.framework";
233 | };
234 | 21887E3E1F2209DE00A05579 /* HotKeyTests */ = {
235 | isa = PBXNativeTarget;
236 | buildConfigurationList = 21887E4D1F2209DE00A05579 /* Build configuration list for PBXNativeTarget "HotKeyTests" */;
237 | buildPhases = (
238 | 21887E3B1F2209DE00A05579 /* Sources */,
239 | 21887E3C1F2209DE00A05579 /* Frameworks */,
240 | 21887E3D1F2209DE00A05579 /* Resources */,
241 | );
242 | buildRules = (
243 | );
244 | dependencies = (
245 | 21887E421F2209DE00A05579 /* PBXTargetDependency */,
246 | );
247 | name = HotKeyTests;
248 | productName = HotKeyTests;
249 | productReference = 21887E3F1F2209DE00A05579 /* HotKeyTests.xctest */;
250 | productType = "com.apple.product-type.bundle.unit-test";
251 | };
252 | 21887E651F22154600A05579 /* Example */ = {
253 | isa = PBXNativeTarget;
254 | buildConfigurationList = 21887E811F22154600A05579 /* Build configuration list for PBXNativeTarget "Example" */;
255 | buildPhases = (
256 | 21887E621F22154600A05579 /* Sources */,
257 | 21887E631F22154600A05579 /* Frameworks */,
258 | 21887E641F22154600A05579 /* Resources */,
259 | 21887E921F22157700A05579 /* Embed Frameworks */,
260 | );
261 | buildRules = (
262 | );
263 | dependencies = (
264 | 21887E911F22157300A05579 /* PBXTargetDependency */,
265 | );
266 | name = Example;
267 | productName = Example;
268 | productReference = 21887E661F22154600A05579 /* Example.app */;
269 | productType = "com.apple.product-type.application";
270 | };
271 | /* End PBXNativeTarget section */
272 |
273 | /* Begin PBXProject section */
274 | 21887E2D1F2209DE00A05579 /* Project object */ = {
275 | isa = PBXProject;
276 | attributes = {
277 | BuildIndependentTargetsInParallel = YES;
278 | LastSwiftUpdateCheck = 0830;
279 | LastUpgradeCheck = 1430;
280 | ORGANIZATIONNAME = "Sam Soffes";
281 | TargetAttributes = {
282 | 21887E351F2209DE00A05579 = {
283 | CreatedOnToolsVersion = 8.3.3;
284 | LastSwiftMigration = 1030;
285 | ProvisioningStyle = Automatic;
286 | };
287 | 21887E3E1F2209DE00A05579 = {
288 | CreatedOnToolsVersion = 8.3.3;
289 | LastSwiftMigration = 1030;
290 | ProvisioningStyle = Automatic;
291 | };
292 | 21887E651F22154600A05579 = {
293 | CreatedOnToolsVersion = 8.3.3;
294 | ProvisioningStyle = Automatic;
295 | };
296 | };
297 | };
298 | buildConfigurationList = 21887E301F2209DE00A05579 /* Build configuration list for PBXProject "HotKey" */;
299 | compatibilityVersion = "Xcode 3.2";
300 | developmentRegion = en;
301 | hasScannedForEncodings = 0;
302 | knownRegions = (
303 | en,
304 | Base,
305 | );
306 | mainGroup = 21887E2C1F2209DE00A05579;
307 | productRefGroup = 21887E371F2209DE00A05579 /* Products */;
308 | projectDirPath = "";
309 | projectRoot = "";
310 | targets = (
311 | 21887E351F2209DE00A05579 /* HotKey */,
312 | 21887E3E1F2209DE00A05579 /* HotKeyTests */,
313 | 21887E651F22154600A05579 /* Example */,
314 | );
315 | };
316 | /* End PBXProject section */
317 |
318 | /* Begin PBXResourcesBuildPhase section */
319 | 21887E341F2209DE00A05579 /* Resources */ = {
320 | isa = PBXResourcesBuildPhase;
321 | buildActionMask = 2147483647;
322 | files = (
323 | );
324 | runOnlyForDeploymentPostprocessing = 0;
325 | };
326 | 21887E3D1F2209DE00A05579 /* Resources */ = {
327 | isa = PBXResourcesBuildPhase;
328 | buildActionMask = 2147483647;
329 | files = (
330 | );
331 | runOnlyForDeploymentPostprocessing = 0;
332 | };
333 | 21887E641F22154600A05579 /* Resources */ = {
334 | isa = PBXResourcesBuildPhase;
335 | buildActionMask = 2147483647;
336 | files = (
337 | 21887E8E1F22156900A05579 /* Main.storyboard in Resources */,
338 | );
339 | runOnlyForDeploymentPostprocessing = 0;
340 | };
341 | /* End PBXResourcesBuildPhase section */
342 |
343 | /* Begin PBXSourcesBuildPhase section */
344 | 21887E311F2209DE00A05579 /* Sources */ = {
345 | isa = PBXSourcesBuildPhase;
346 | buildActionMask = 2147483647;
347 | files = (
348 | 219E0D1B1FC5E8AB00F80DF1 /* Key.swift in Sources */,
349 | 21739E66233E992B00F3A8EF /* KeyCombo+System.swift in Sources */,
350 | 219E0D1C1FC5E8AB00F80DF1 /* HotKeysController.swift in Sources */,
351 | 219E0D1A1FC5E8AB00F80DF1 /* KeyCombo.swift in Sources */,
352 | 219E0D191FC5E8AB00F80DF1 /* NSEventModifierFlags+HotKey.swift in Sources */,
353 | 219E0D1D1FC5E8AB00F80DF1 /* HotKey.swift in Sources */,
354 | );
355 | runOnlyForDeploymentPostprocessing = 0;
356 | };
357 | 21887E3B1F2209DE00A05579 /* Sources */ = {
358 | isa = PBXSourcesBuildPhase;
359 | buildActionMask = 2147483647;
360 | files = (
361 | 219E0D221FC5E8BF00F80DF1 /* KeyComboTests.swift in Sources */,
362 | 219E0D211FC5E8BF00F80DF1 /* ModifierFlagsTests.swift in Sources */,
363 | );
364 | runOnlyForDeploymentPostprocessing = 0;
365 | };
366 | 21887E621F22154600A05579 /* Sources */ = {
367 | isa = PBXSourcesBuildPhase;
368 | buildActionMask = 2147483647;
369 | files = (
370 | 21887E8C1F22156900A05579 /* ViewController.swift in Sources */,
371 | 21887E8B1F22156900A05579 /* AppDelegate.swift in Sources */,
372 | );
373 | runOnlyForDeploymentPostprocessing = 0;
374 | };
375 | /* End PBXSourcesBuildPhase section */
376 |
377 | /* Begin PBXTargetDependency section */
378 | 21887E421F2209DE00A05579 /* PBXTargetDependency */ = {
379 | isa = PBXTargetDependency;
380 | target = 21887E351F2209DE00A05579 /* HotKey */;
381 | targetProxy = 21887E411F2209DE00A05579 /* PBXContainerItemProxy */;
382 | };
383 | 21887E911F22157300A05579 /* PBXTargetDependency */ = {
384 | isa = PBXTargetDependency;
385 | target = 21887E351F2209DE00A05579 /* HotKey */;
386 | targetProxy = 21887E901F22157300A05579 /* PBXContainerItemProxy */;
387 | };
388 | /* End PBXTargetDependency section */
389 |
390 | /* Begin PBXVariantGroup section */
391 | 21887E881F22156900A05579 /* Main.storyboard */ = {
392 | isa = PBXVariantGroup;
393 | children = (
394 | 21887E891F22156900A05579 /* Base */,
395 | );
396 | name = Main.storyboard;
397 | sourceTree = "";
398 | };
399 | /* End PBXVariantGroup section */
400 |
401 | /* Begin XCBuildConfiguration section */
402 | 21887E481F2209DE00A05579 /* Debug */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | ALWAYS_SEARCH_USER_PATHS = NO;
406 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
407 | CLANG_ANALYZER_NONNULL = YES;
408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
410 | CLANG_CXX_LIBRARY = "libc++";
411 | CLANG_ENABLE_MODULES = YES;
412 | CLANG_ENABLE_OBJC_ARC = YES;
413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
414 | CLANG_WARN_BOOL_CONVERSION = YES;
415 | CLANG_WARN_COMMA = YES;
416 | CLANG_WARN_CONSTANT_CONVERSION = YES;
417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
419 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
420 | CLANG_WARN_EMPTY_BODY = YES;
421 | CLANG_WARN_ENUM_CONVERSION = YES;
422 | CLANG_WARN_INFINITE_RECURSION = YES;
423 | CLANG_WARN_INT_CONVERSION = YES;
424 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
425 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
426 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
427 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
428 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
429 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
430 | CLANG_WARN_STRICT_PROTOTYPES = YES;
431 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
432 | CLANG_WARN_UNREACHABLE_CODE = YES;
433 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
434 | COPY_PHASE_STRIP = NO;
435 | CURRENT_PROJECT_VERSION = 1;
436 | DEAD_CODE_STRIPPING = YES;
437 | DEBUG_INFORMATION_FORMAT = dwarf;
438 | ENABLE_STRICT_OBJC_MSGSEND = YES;
439 | ENABLE_TESTABILITY = YES;
440 | GCC_C_LANGUAGE_STANDARD = gnu99;
441 | GCC_DYNAMIC_NO_PIC = NO;
442 | GCC_NO_COMMON_BLOCKS = YES;
443 | GCC_OPTIMIZATION_LEVEL = 0;
444 | GCC_PREPROCESSOR_DEFINITIONS = (
445 | "DEBUG=1",
446 | "$(inherited)",
447 | );
448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
450 | GCC_WARN_UNDECLARED_SELECTOR = YES;
451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
452 | GCC_WARN_UNUSED_FUNCTION = YES;
453 | GCC_WARN_UNUSED_VARIABLE = YES;
454 | MACOSX_DEPLOYMENT_TARGET = 10.13;
455 | MTL_ENABLE_DEBUG_INFO = YES;
456 | ONLY_ACTIVE_ARCH = YES;
457 | SDKROOT = macosx;
458 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
459 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
460 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
461 | SWIFT_VERSION = 5.0;
462 | VERSIONING_SYSTEM = "apple-generic";
463 | VERSION_INFO_PREFIX = "";
464 | };
465 | name = Debug;
466 | };
467 | 21887E491F2209DE00A05579 /* Release */ = {
468 | isa = XCBuildConfiguration;
469 | buildSettings = {
470 | ALWAYS_SEARCH_USER_PATHS = NO;
471 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
472 | CLANG_ANALYZER_NONNULL = YES;
473 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
474 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
475 | CLANG_CXX_LIBRARY = "libc++";
476 | CLANG_ENABLE_MODULES = YES;
477 | CLANG_ENABLE_OBJC_ARC = YES;
478 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
479 | CLANG_WARN_BOOL_CONVERSION = YES;
480 | CLANG_WARN_COMMA = YES;
481 | CLANG_WARN_CONSTANT_CONVERSION = YES;
482 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
483 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
484 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
485 | CLANG_WARN_EMPTY_BODY = YES;
486 | CLANG_WARN_ENUM_CONVERSION = YES;
487 | CLANG_WARN_INFINITE_RECURSION = YES;
488 | CLANG_WARN_INT_CONVERSION = YES;
489 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
490 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
491 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
492 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
493 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
494 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
495 | CLANG_WARN_STRICT_PROTOTYPES = YES;
496 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
497 | CLANG_WARN_UNREACHABLE_CODE = YES;
498 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
499 | COPY_PHASE_STRIP = NO;
500 | CURRENT_PROJECT_VERSION = 1;
501 | DEAD_CODE_STRIPPING = YES;
502 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
503 | ENABLE_NS_ASSERTIONS = NO;
504 | ENABLE_STRICT_OBJC_MSGSEND = YES;
505 | GCC_C_LANGUAGE_STANDARD = gnu99;
506 | GCC_NO_COMMON_BLOCKS = YES;
507 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
508 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
509 | GCC_WARN_UNDECLARED_SELECTOR = YES;
510 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
511 | GCC_WARN_UNUSED_FUNCTION = YES;
512 | GCC_WARN_UNUSED_VARIABLE = YES;
513 | MACOSX_DEPLOYMENT_TARGET = 10.13;
514 | MTL_ENABLE_DEBUG_INFO = NO;
515 | SDKROOT = macosx;
516 | SWIFT_COMPILATION_MODE = wholemodule;
517 | SWIFT_OPTIMIZATION_LEVEL = "-O";
518 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
519 | SWIFT_VERSION = 5.0;
520 | VERSIONING_SYSTEM = "apple-generic";
521 | VERSION_INFO_PREFIX = "";
522 | };
523 | name = Release;
524 | };
525 | 21887E4B1F2209DE00A05579 /* Debug */ = {
526 | isa = XCBuildConfiguration;
527 | buildSettings = {
528 | COMBINE_HIDPI_IMAGES = YES;
529 | DEAD_CODE_STRIPPING = YES;
530 | DEFINES_MODULE = YES;
531 | DYLIB_COMPATIBILITY_VERSION = 1;
532 | DYLIB_CURRENT_VERSION = 1;
533 | DYLIB_INSTALL_NAME_BASE = "@rpath";
534 | ENABLE_MODULE_VERIFIER = YES;
535 | FRAMEWORK_VERSION = A;
536 | INFOPLIST_FILE = "$(SRCROOT)/Support/Info.plist";
537 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
538 | LD_RUNPATH_SEARCH_PATHS = (
539 | "$(inherited)",
540 | "@executable_path/../Frameworks",
541 | "@loader_path/Frameworks",
542 | );
543 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
544 | MARKETING_VERSION = 0.2.0;
545 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
546 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11";
547 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.HotKey;
548 | PRODUCT_NAME = "$(TARGET_NAME)";
549 | SKIP_INSTALL = YES;
550 | };
551 | name = Debug;
552 | };
553 | 21887E4C1F2209DE00A05579 /* Release */ = {
554 | isa = XCBuildConfiguration;
555 | buildSettings = {
556 | COMBINE_HIDPI_IMAGES = YES;
557 | DEAD_CODE_STRIPPING = YES;
558 | DEFINES_MODULE = YES;
559 | DYLIB_COMPATIBILITY_VERSION = 1;
560 | DYLIB_CURRENT_VERSION = 1;
561 | DYLIB_INSTALL_NAME_BASE = "@rpath";
562 | ENABLE_MODULE_VERIFIER = YES;
563 | FRAMEWORK_VERSION = A;
564 | INFOPLIST_FILE = "$(SRCROOT)/Support/Info.plist";
565 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
566 | LD_RUNPATH_SEARCH_PATHS = (
567 | "$(inherited)",
568 | "@executable_path/../Frameworks",
569 | "@loader_path/Frameworks",
570 | );
571 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
572 | MARKETING_VERSION = 0.2.0;
573 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
574 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11";
575 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.HotKey;
576 | PRODUCT_NAME = "$(TARGET_NAME)";
577 | SKIP_INSTALL = YES;
578 | };
579 | name = Release;
580 | };
581 | 21887E4E1F2209DE00A05579 /* Debug */ = {
582 | isa = XCBuildConfiguration;
583 | buildSettings = {
584 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
585 | COMBINE_HIDPI_IMAGES = YES;
586 | DEAD_CODE_STRIPPING = YES;
587 | INFOPLIST_FILE = "Support/Tests-Info.plist";
588 | LD_RUNPATH_SEARCH_PATHS = (
589 | "$(inherited)",
590 | "@executable_path/../Frameworks",
591 | "@loader_path/../Frameworks",
592 | );
593 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
594 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.HotKeyTests;
595 | PRODUCT_NAME = "$(TARGET_NAME)";
596 | };
597 | name = Debug;
598 | };
599 | 21887E4F1F2209DE00A05579 /* Release */ = {
600 | isa = XCBuildConfiguration;
601 | buildSettings = {
602 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
603 | COMBINE_HIDPI_IMAGES = YES;
604 | DEAD_CODE_STRIPPING = YES;
605 | INFOPLIST_FILE = "Support/Tests-Info.plist";
606 | LD_RUNPATH_SEARCH_PATHS = (
607 | "$(inherited)",
608 | "@executable_path/../Frameworks",
609 | "@loader_path/../Frameworks",
610 | );
611 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
612 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.HotKeyTests;
613 | PRODUCT_NAME = "$(TARGET_NAME)";
614 | };
615 | name = Release;
616 | };
617 | 21887E7D1F22154600A05579 /* Debug */ = {
618 | isa = XCBuildConfiguration;
619 | buildSettings = {
620 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
621 | COMBINE_HIDPI_IMAGES = YES;
622 | DEAD_CODE_STRIPPING = YES;
623 | INFOPLIST_FILE = "$(SRCROOT)/Example/Support/Info.plist";
624 | LD_RUNPATH_SEARCH_PATHS = (
625 | "$(inherited)",
626 | "@executable_path/../Frameworks",
627 | );
628 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
629 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.Example;
630 | PRODUCT_NAME = "$(TARGET_NAME)";
631 | };
632 | name = Debug;
633 | };
634 | 21887E7E1F22154600A05579 /* Release */ = {
635 | isa = XCBuildConfiguration;
636 | buildSettings = {
637 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
638 | COMBINE_HIDPI_IMAGES = YES;
639 | DEAD_CODE_STRIPPING = YES;
640 | INFOPLIST_FILE = "$(SRCROOT)/Example/Support/Info.plist";
641 | LD_RUNPATH_SEARCH_PATHS = (
642 | "$(inherited)",
643 | "@executable_path/../Frameworks",
644 | );
645 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
646 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.Example;
647 | PRODUCT_NAME = "$(TARGET_NAME)";
648 | };
649 | name = Release;
650 | };
651 | /* End XCBuildConfiguration section */
652 |
653 | /* Begin XCConfigurationList section */
654 | 21887E301F2209DE00A05579 /* Build configuration list for PBXProject "HotKey" */ = {
655 | isa = XCConfigurationList;
656 | buildConfigurations = (
657 | 21887E481F2209DE00A05579 /* Debug */,
658 | 21887E491F2209DE00A05579 /* Release */,
659 | );
660 | defaultConfigurationIsVisible = 0;
661 | defaultConfigurationName = Release;
662 | };
663 | 21887E4A1F2209DE00A05579 /* Build configuration list for PBXNativeTarget "HotKey" */ = {
664 | isa = XCConfigurationList;
665 | buildConfigurations = (
666 | 21887E4B1F2209DE00A05579 /* Debug */,
667 | 21887E4C1F2209DE00A05579 /* Release */,
668 | );
669 | defaultConfigurationIsVisible = 0;
670 | defaultConfigurationName = Release;
671 | };
672 | 21887E4D1F2209DE00A05579 /* Build configuration list for PBXNativeTarget "HotKeyTests" */ = {
673 | isa = XCConfigurationList;
674 | buildConfigurations = (
675 | 21887E4E1F2209DE00A05579 /* Debug */,
676 | 21887E4F1F2209DE00A05579 /* Release */,
677 | );
678 | defaultConfigurationIsVisible = 0;
679 | defaultConfigurationName = Release;
680 | };
681 | 21887E811F22154600A05579 /* Build configuration list for PBXNativeTarget "Example" */ = {
682 | isa = XCConfigurationList;
683 | buildConfigurations = (
684 | 21887E7D1F22154600A05579 /* Debug */,
685 | 21887E7E1F22154600A05579 /* Release */,
686 | );
687 | defaultConfigurationIsVisible = 0;
688 | defaultConfigurationName = Release;
689 | };
690 | /* End XCConfigurationList section */
691 | };
692 | rootObject = 21887E2D1F2209DE00A05579 /* Project object */;
693 | }
694 |
--------------------------------------------------------------------------------
/HotKey.xcodeproj/xcshareddata/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
64 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/HotKey.xcodeproj/xcshareddata/xcschemes/HotKey.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017–2019 Sam Soffes, http://soff.es
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "HotKey",
7 | products: [
8 | .library(name: "HotKey", targets: ["HotKey"])
9 | ],
10 | targets: [
11 | .target(name: "HotKey"),
12 | .testTarget(name: "HotKeyTests", dependencies: ["HotKey"])
13 | ]
14 | )
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HotKey
2 |
3 | [](https://github.com/soffes/HotKey/releases)
4 | [](https://github.com/Carthage/Carthage)
5 | [](https://cocoapods.org/pods/HotKey)
6 |
7 | Simple global shortcuts in macOS. HotKey wraps the Carbon APIs for dealing with global hot keys to make it easy to use in Swift.
8 |
9 | ## Installation
10 |
11 | ### Swift Package Manager
12 |
13 | For installation with [Swift Package Manager](https://github.com/apple/swift-package-manager), simply add the following to your `Package.swift`:
14 |
15 | ``` swift
16 | .package(url: "https://github.com/soffes/HotKey", from: "0.2.1")
17 | ```
18 |
19 | ### Carthage
20 |
21 | For installation with [Carthage](https://github.com/carthage/carthage), simply add the following to your `Cartfile`:
22 |
23 | ``` ruby
24 | github "soffes/HotKey"
25 | ```
26 |
27 | ### CocoaPods
28 |
29 | For installation with [CocoaPods](http://cocoapods.org), simply add the following to your `Podfile`:
30 |
31 | ```ruby
32 | pod 'HotKey'
33 | ```
34 |
35 | ## Usage
36 |
37 | Simply initialize a `HotKey` with a key and modifiers:
38 |
39 | ```swift
40 | // Setup hot key for ⌥⌘R
41 | let hotKey = HotKey(key: .r, modifiers: [.command, .option])
42 | ```
43 |
44 | This is a convenice initializer that creates a `KeyCombo` for you. You can also initialize with a Carbon key code and Carbon modifier flags if you’re feeling old school.
45 |
46 | Now you can set the `keyDownHandler` and get callbacks for when your hot key is pressed:
47 |
48 | ```swift
49 | hotKey.keyDownHandler = {
50 | print("Pressed at \(Date())")
51 | }
52 | ```
53 |
54 | You can also implement `hotKey.keyUpHandler` if you’d like.
55 |
56 | You don’t need to think about when handlers are registered and unregistered. This all happens automatically based on the `HotKey` object’s lifecycle.
57 |
58 | ## Thanks
59 |
60 | HotKey was heavily inspired by PTHotKey.
61 |
--------------------------------------------------------------------------------
/Sources/HotKey/HotKey.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(AppKit) && canImport(Carbon)
2 | import AppKit
3 | import Carbon
4 |
5 | public final class HotKey {
6 |
7 | // MARK: - Types
8 |
9 | public typealias Handler = () -> Void
10 |
11 | // MARK: - Properties
12 |
13 | let identifier = UUID()
14 |
15 | public let keyCombo: KeyCombo
16 | public var keyDownHandler: Handler?
17 | public var keyUpHandler: Handler?
18 | public var isPaused = false {
19 | didSet {
20 | if isPaused {
21 | HotKeysController.unregister(self)
22 | } else {
23 | HotKeysController.register(self)
24 | }
25 | }
26 | }
27 |
28 | // MARK: - Initializers
29 |
30 | public init(keyCombo: KeyCombo, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
31 | self.keyCombo = keyCombo
32 | self.keyDownHandler = keyDownHandler
33 | self.keyUpHandler = keyUpHandler
34 |
35 | HotKeysController.register(self)
36 | }
37 |
38 | public convenience init(carbonKeyCode: UInt32, carbonModifiers: UInt32, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
39 | let keyCombo = KeyCombo(carbonKeyCode: carbonKeyCode, carbonModifiers: carbonModifiers)
40 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
41 | }
42 |
43 | public convenience init(key: Key, modifiers: NSEvent.ModifierFlags, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
44 | let keyCombo = KeyCombo(key: key, modifiers: modifiers)
45 | self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
46 | }
47 |
48 | deinit {
49 | HotKeysController.unregister(self)
50 | }
51 | }
52 | #endif
53 |
--------------------------------------------------------------------------------
/Sources/HotKey/HotKeysController.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(Carbon)
2 | import Carbon
3 |
4 | final class HotKeysController {
5 |
6 | // MARK: - Types
7 |
8 | final class HotKeyBox {
9 | let identifier: UUID
10 | weak var hotKey: HotKey?
11 | let carbonHotKeyID: UInt32
12 | var carbonEventHotKey: EventHotKeyRef?
13 |
14 | init(hotKey: HotKey, carbonHotKeyID: UInt32) {
15 | self.identifier = hotKey.identifier
16 | self.hotKey = hotKey
17 | self.carbonHotKeyID = carbonHotKeyID
18 | }
19 | }
20 |
21 | // MARK: - Properties
22 |
23 | static var hotKeys = [UInt32: HotKeyBox]()
24 | static private var hotKeysCount: UInt32 = 0
25 |
26 | static let eventHotKeySignature: UInt32 = {
27 | let string = "SSHk"
28 | var result: FourCharCode = 0
29 | for char in string.utf16 {
30 | result = (result << 8) + FourCharCode(char)
31 | }
32 | return result
33 | }()
34 |
35 | private static let eventSpec = [
36 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
37 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
38 | ]
39 |
40 | private static var eventHandler: EventHandlerRef?
41 |
42 | // MARK: - Registration
43 |
44 | static func register(_ hotKey: HotKey) {
45 | // Don't register an already registered HotKey
46 | if hotKeys.values.first(where: { $0.identifier == hotKey.identifier }) != nil {
47 | return
48 | }
49 |
50 | // Increment the count which will become out next ID
51 | hotKeysCount += 1
52 |
53 | // Create a box for our metadata and weak HotKey
54 | let box = HotKeyBox(hotKey: hotKey, carbonHotKeyID: hotKeysCount)
55 | hotKeys[box.carbonHotKeyID] = box
56 |
57 | // Register with the system
58 | var eventHotKey: EventHotKeyRef?
59 | let hotKeyID = EventHotKeyID(signature: eventHotKeySignature, id: box.carbonHotKeyID)
60 | let registerError = RegisterEventHotKey(
61 | hotKey.keyCombo.carbonKeyCode,
62 | hotKey.keyCombo.carbonModifiers,
63 | hotKeyID,
64 | GetEventDispatcherTarget(),
65 | 0,
66 | &eventHotKey
67 | )
68 |
69 | // Ensure registration worked
70 | guard registerError == noErr, eventHotKey != nil else {
71 | return
72 | }
73 |
74 | // Store the event so we can unregister it later
75 | box.carbonEventHotKey = eventHotKey
76 |
77 | // Setup the event handler if needed
78 | updateEventHandler()
79 | }
80 |
81 | static func unregister(_ hotKey: HotKey) {
82 | // Find the box
83 | guard let box = self.box(for: hotKey) else {
84 | return
85 | }
86 |
87 | // Unregister the hot key
88 | UnregisterEventHotKey(box.carbonEventHotKey)
89 |
90 | // Destroy the box
91 | box.hotKey = nil
92 | hotKeys.removeValue(forKey: box.carbonHotKeyID)
93 | }
94 |
95 |
96 | // MARK: - Events
97 |
98 | static func handleCarbonEvent(_ event: EventRef?) -> OSStatus {
99 | // Ensure we have an event
100 | guard let event = event else {
101 | return OSStatus(eventNotHandledErr)
102 | }
103 |
104 | // Get the hot key ID from the event
105 | var hotKeyID = EventHotKeyID()
106 | let error = GetEventParameter(
107 | event,
108 | UInt32(kEventParamDirectObject),
109 | UInt32(typeEventHotKeyID),
110 | nil,
111 | MemoryLayout.size,
112 | nil,
113 | &hotKeyID
114 | )
115 |
116 | if error != noErr {
117 | return error
118 | }
119 |
120 | // Ensure we have a HotKey registered for this ID
121 | guard hotKeyID.signature == eventHotKeySignature,
122 | let hotKey = self.hotKey(for: hotKeyID.id)
123 | else {
124 | return OSStatus(eventNotHandledErr)
125 | }
126 |
127 | // Call the handler
128 | switch GetEventKind(event) {
129 | case UInt32(kEventHotKeyPressed):
130 | if !hotKey.isPaused, let handler = hotKey.keyDownHandler {
131 | handler()
132 | return noErr
133 | }
134 | case UInt32(kEventHotKeyReleased):
135 | if !hotKey.isPaused, let handler = hotKey.keyUpHandler {
136 | handler()
137 | return noErr
138 | }
139 | default:
140 | break
141 | }
142 |
143 | return OSStatus(eventNotHandledErr)
144 | }
145 |
146 | private static func updateEventHandler() {
147 | if hotKeysCount == 0 || eventHandler != nil {
148 | return
149 | }
150 |
151 | // Register for key down and key up
152 | let eventSpec = [
153 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
154 | EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
155 | ]
156 |
157 | // Install the handler
158 | InstallEventHandler(GetEventDispatcherTarget(), hotKeyEventHandler, 2, eventSpec, nil, &eventHandler)
159 | }
160 |
161 |
162 | // MARK: - Querying
163 |
164 | private static func hotKey(for carbonHotKeyID: UInt32) -> HotKey? {
165 | if let hotKey = hotKeys[carbonHotKeyID]?.hotKey {
166 | return hotKey
167 | }
168 |
169 | hotKeys.removeValue(forKey: carbonHotKeyID)
170 | return nil
171 | }
172 |
173 | private static func box(for hotKey: HotKey) -> HotKeyBox? {
174 | for box in hotKeys.values {
175 | if box.identifier == hotKey.identifier {
176 | return box
177 | }
178 | }
179 |
180 | return nil
181 | }
182 | }
183 |
184 | private func hotKeyEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {
185 | return HotKeysController.handleCarbonEvent(event)
186 | }
187 |
188 | #endif
189 |
--------------------------------------------------------------------------------
/Sources/HotKey/Key.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(Carbon)
2 | import Carbon
3 |
4 | public enum Key {
5 |
6 | // MARK: - Letters
7 |
8 | case a
9 | case b
10 | case c
11 | case d
12 | case e
13 | case f
14 | case g
15 | case h
16 | case i
17 | case j
18 | case k
19 | case l
20 | case m
21 | case n
22 | case o
23 | case p
24 | case q
25 | case r
26 | case s
27 | case t
28 | case u
29 | case v
30 | case w
31 | case x
32 | case y
33 | case z
34 |
35 | // MARK: - Numbers
36 |
37 | case zero
38 | case one
39 | case two
40 | case three
41 | case four
42 | case five
43 | case six
44 | case seven
45 | case eight
46 | case nine
47 |
48 | // MARK: - Symbols
49 |
50 | case period
51 | case quote
52 | case rightBracket
53 | case semicolon
54 | case slash
55 | case backslash
56 | case comma
57 | case equal
58 | case grave // Backtick
59 | case leftBracket
60 | case minus
61 | case section
62 |
63 | // MARK: - Whitespace
64 |
65 | case space
66 | case tab
67 | case `return`
68 |
69 | // MARK: - Modifiers
70 |
71 | case command
72 | case rightCommand
73 | case option
74 | case rightOption
75 | case control
76 | case rightControl
77 | case shift
78 | case rightShift
79 | case function
80 | case capsLock
81 |
82 | // MARK: - Navigation
83 |
84 | case pageUp
85 | case pageDown
86 | case home
87 | case end
88 | case upArrow
89 | case rightArrow
90 | case downArrow
91 | case leftArrow
92 |
93 | // MARK: - Functions
94 |
95 | case f1
96 | case f2
97 | case f3
98 | case f4
99 | case f5
100 | case f6
101 | case f7
102 | case f8
103 | case f9
104 | case f10
105 | case f11
106 | case f12
107 | case f13
108 | case f14
109 | case f15
110 | case f16
111 | case f17
112 | case f18
113 | case f19
114 | case f20
115 |
116 | // MARK: - Keypad
117 |
118 | case keypad0
119 | case keypad1
120 | case keypad2
121 | case keypad3
122 | case keypad4
123 | case keypad5
124 | case keypad6
125 | case keypad7
126 | case keypad8
127 | case keypad9
128 | case keypadClear
129 | case keypadDecimal
130 | case keypadDivide
131 | case keypadEnter
132 | case keypadEquals
133 | case keypadMinus
134 | case keypadMultiply
135 | case keypadPlus
136 |
137 | // MARK: - Misc
138 |
139 | case escape
140 | case delete
141 | case forwardDelete
142 | case help
143 | case volumeUp
144 | case volumeDown
145 | case mute
146 |
147 | // MARK: - Initializers
148 |
149 | public init?(string: String) {
150 | switch string.lowercased() {
151 | case "a": self = .a
152 | case "s": self = .s
153 | case "d": self = .d
154 | case "f": self = .f
155 | case "h": self = .h
156 | case "g": self = .g
157 | case "z": self = .z
158 | case "x": self = .x
159 | case "c": self = .c
160 | case "v": self = .v
161 | case "b": self = .b
162 | case "q": self = .q
163 | case "w": self = .w
164 | case "e": self = .e
165 | case "r": self = .r
166 | case "y": self = .y
167 | case "t": self = .t
168 | case "one", "1": self = .one
169 | case "two", "2": self = .two
170 | case "three", "3": self = .three
171 | case "four", "4": self = .four
172 | case "six", "6": self = .six
173 | case "five", "5": self = .five
174 | case "equal", "=": self = .equal
175 | case "nine", "9": self = .nine
176 | case "seven", "7": self = .seven
177 | case "minus", "-": self = .minus
178 | case "eight", "8": self = .eight
179 | case "zero", "0": self = .zero
180 | case "rightBracket", "]": self = .rightBracket
181 | case "o": self = .o
182 | case "u": self = .u
183 | case "leftBracket", "[": self = .leftBracket
184 | case "i": self = .i
185 | case "p": self = .p
186 | case "l": self = .l
187 | case "j": self = .j
188 | case "quote", "\"": self = .quote
189 | case "k": self = .k
190 | case "semicolon", ";": self = .semicolon
191 | case "backslash", "\\": self = .backslash
192 | case "comma", ",": self = .comma
193 | case "slash", "/": self = .slash
194 | case "n": self = .n
195 | case "m": self = .m
196 | case "period", ".": self = .period
197 | case "grave", "`", "ˋ", "`": self = .grave
198 | case "keypaddecimal": self = .keypadDecimal
199 | case "keypadmultiply": self = .keypadMultiply
200 | case "keypadplus": self = .keypadPlus
201 | case "keypadclear", "⌧": self = .keypadClear
202 | case "keypaddivide": self = .keypadDivide
203 | case "keypadenter": self = .keypadEnter
204 | case "keypadminus": self = .keypadMinus
205 | case "keypadequals": self = .keypadEquals
206 | case "keypad0": self = .keypad0
207 | case "keypad1": self = .keypad1
208 | case "keypad2": self = .keypad2
209 | case "keypad3": self = .keypad3
210 | case "keypad4": self = .keypad4
211 | case "keypad5": self = .keypad5
212 | case "keypad6": self = .keypad6
213 | case "keypad7": self = .keypad7
214 | case "keypad8": self = .keypad8
215 | case "keypad9": self = .keypad9
216 | case "return", "\r", "↩︎", "⏎", "⮐": self = .return
217 | case "tab", "\t", "⇥": self = .tab
218 | case "space", " ", "␣": self = .space
219 | case "delete", "⌫": self = .delete
220 | case "escape", "⎋": self = .escape
221 | case "command", "⌘", "": self = .command
222 | case "shift", "⇧": self = .shift
223 | case "capslock", "⇪": self = .capsLock
224 | case "option", "⌥": self = .option
225 | case "control", "⌃": self = .control
226 | case "rightcommand": self = .rightCommand
227 | case "rightshift": self = .rightShift
228 | case "rightoption": self = .rightOption
229 | case "rightcontrol": self = .rightControl
230 | case "function", "fn": self = .function
231 | case "f17", "F17": self = .f17
232 | case "volumeup", "🔊": self = .volumeUp
233 | case "volumedown", "🔉": self = .volumeDown
234 | case "mute", "🔇": self = .mute
235 | case "f18", "F18": self = .f18
236 | case "f19", "F19": self = .f19
237 | case "f20", "F20": self = .f20
238 | case "f5", "F5": self = .f5
239 | case "f6", "F6": self = .f6
240 | case "f7", "F7": self = .f7
241 | case "f3", "F3": self = .f3
242 | case "f8", "F8": self = .f8
243 | case "f9", "F9": self = .f9
244 | case "f11", "F11": self = .f11
245 | case "f13", "F13": self = .f13
246 | case "f16", "F16": self = .f16
247 | case "f14", "F14": self = .f14
248 | case "f10", "F10": self = .f10
249 | case "f12", "F12": self = .f12
250 | case "f15", "F15": self = .f15
251 | case "help", "?⃝": self = .help
252 | case "home", "↖": self = .home
253 | case "pageup", "⇞": self = .pageUp
254 | case "forwarddelete", "⌦": self = .forwardDelete
255 | case "f4", "F4": self = .f4
256 | case "end", "↘": self = .end
257 | case "f2", "F2": self = .f2
258 | case "pagedown", "⇟": self = .pageDown
259 | case "f1", "F1": self = .f1
260 | case "leftarrow", "←": self = .leftArrow
261 | case "rightarrow", "→": self = .rightArrow
262 | case "downarrow", "↓": self = .downArrow
263 | case "uparrow", "↑": self = .upArrow
264 | case "section", "§": self = .section
265 | default: return nil
266 | }
267 | }
268 |
269 | public init?(carbonKeyCode: UInt32) {
270 | switch carbonKeyCode {
271 | case UInt32(kVK_ANSI_A): self = .a
272 | case UInt32(kVK_ANSI_S): self = .s
273 | case UInt32(kVK_ANSI_D): self = .d
274 | case UInt32(kVK_ANSI_F): self = .f
275 | case UInt32(kVK_ANSI_H): self = .h
276 | case UInt32(kVK_ANSI_G): self = .g
277 | case UInt32(kVK_ANSI_Z): self = .z
278 | case UInt32(kVK_ANSI_X): self = .x
279 | case UInt32(kVK_ANSI_C): self = .c
280 | case UInt32(kVK_ANSI_V): self = .v
281 | case UInt32(kVK_ANSI_B): self = .b
282 | case UInt32(kVK_ANSI_Q): self = .q
283 | case UInt32(kVK_ANSI_W): self = .w
284 | case UInt32(kVK_ANSI_E): self = .e
285 | case UInt32(kVK_ANSI_R): self = .r
286 | case UInt32(kVK_ANSI_Y): self = .y
287 | case UInt32(kVK_ANSI_T): self = .t
288 | case UInt32(kVK_ANSI_1): self = .one
289 | case UInt32(kVK_ANSI_2): self = .two
290 | case UInt32(kVK_ANSI_3): self = .three
291 | case UInt32(kVK_ANSI_4): self = .four
292 | case UInt32(kVK_ANSI_6): self = .six
293 | case UInt32(kVK_ANSI_5): self = .five
294 | case UInt32(kVK_ANSI_Equal): self = .equal
295 | case UInt32(kVK_ANSI_9): self = .nine
296 | case UInt32(kVK_ANSI_7): self = .seven
297 | case UInt32(kVK_ANSI_Minus): self = .minus
298 | case UInt32(kVK_ANSI_8): self = .eight
299 | case UInt32(kVK_ANSI_0): self = .zero
300 | case UInt32(kVK_ANSI_RightBracket): self = .rightBracket
301 | case UInt32(kVK_ANSI_O): self = .o
302 | case UInt32(kVK_ANSI_U): self = .u
303 | case UInt32(kVK_ANSI_LeftBracket): self = .leftBracket
304 | case UInt32(kVK_ANSI_I): self = .i
305 | case UInt32(kVK_ANSI_P): self = .p
306 | case UInt32(kVK_ANSI_L): self = .l
307 | case UInt32(kVK_ANSI_J): self = .j
308 | case UInt32(kVK_ANSI_Quote): self = .quote
309 | case UInt32(kVK_ANSI_K): self = .k
310 | case UInt32(kVK_ANSI_Semicolon): self = .semicolon
311 | case UInt32(kVK_ANSI_Backslash): self = .backslash
312 | case UInt32(kVK_ANSI_Comma): self = .comma
313 | case UInt32(kVK_ANSI_Slash): self = .slash
314 | case UInt32(kVK_ANSI_N): self = .n
315 | case UInt32(kVK_ANSI_M): self = .m
316 | case UInt32(kVK_ANSI_Period): self = .period
317 | case UInt32(kVK_ANSI_Grave): self = .grave
318 | case UInt32(kVK_ANSI_KeypadDecimal): self = .keypadDecimal
319 | case UInt32(kVK_ANSI_KeypadMultiply): self = .keypadMultiply
320 | case UInt32(kVK_ANSI_KeypadPlus): self = .keypadPlus
321 | case UInt32(kVK_ANSI_KeypadClear): self = .keypadClear
322 | case UInt32(kVK_ANSI_KeypadDivide): self = .keypadDivide
323 | case UInt32(kVK_ANSI_KeypadEnter): self = .keypadEnter
324 | case UInt32(kVK_ANSI_KeypadMinus): self = .keypadMinus
325 | case UInt32(kVK_ANSI_KeypadEquals): self = .keypadEquals
326 | case UInt32(kVK_ANSI_Keypad0): self = .keypad0
327 | case UInt32(kVK_ANSI_Keypad1): self = .keypad1
328 | case UInt32(kVK_ANSI_Keypad2): self = .keypad2
329 | case UInt32(kVK_ANSI_Keypad3): self = .keypad3
330 | case UInt32(kVK_ANSI_Keypad4): self = .keypad4
331 | case UInt32(kVK_ANSI_Keypad5): self = .keypad5
332 | case UInt32(kVK_ANSI_Keypad6): self = .keypad6
333 | case UInt32(kVK_ANSI_Keypad7): self = .keypad7
334 | case UInt32(kVK_ANSI_Keypad8): self = .keypad8
335 | case UInt32(kVK_ANSI_Keypad9): self = .keypad9
336 | case UInt32(kVK_Return): self = .`return`
337 | case UInt32(kVK_Tab): self = .tab
338 | case UInt32(kVK_Space): self = .space
339 | case UInt32(kVK_Delete): self = .delete
340 | case UInt32(kVK_Escape): self = .escape
341 | case UInt32(kVK_Command): self = .command
342 | case UInt32(kVK_Shift): self = .shift
343 | case UInt32(kVK_CapsLock): self = .capsLock
344 | case UInt32(kVK_Option): self = .option
345 | case UInt32(kVK_Control): self = .control
346 | case UInt32(kVK_RightCommand): self = .rightCommand
347 | case UInt32(kVK_RightShift): self = .rightShift
348 | case UInt32(kVK_RightOption): self = .rightOption
349 | case UInt32(kVK_RightControl): self = .rightControl
350 | case UInt32(kVK_Function): self = .function
351 | case UInt32(kVK_F17): self = .f17
352 | case UInt32(kVK_VolumeUp): self = .volumeUp
353 | case UInt32(kVK_VolumeDown): self = .volumeDown
354 | case UInt32(kVK_Mute): self = .mute
355 | case UInt32(kVK_F18): self = .f18
356 | case UInt32(kVK_F19): self = .f19
357 | case UInt32(kVK_F20): self = .f20
358 | case UInt32(kVK_F5): self = .f5
359 | case UInt32(kVK_F6): self = .f6
360 | case UInt32(kVK_F7): self = .f7
361 | case UInt32(kVK_F3): self = .f3
362 | case UInt32(kVK_F8): self = .f8
363 | case UInt32(kVK_F9): self = .f9
364 | case UInt32(kVK_F11): self = .f11
365 | case UInt32(kVK_F13): self = .f13
366 | case UInt32(kVK_F16): self = .f16
367 | case UInt32(kVK_F14): self = .f14
368 | case UInt32(kVK_F10): self = .f10
369 | case UInt32(kVK_F12): self = .f12
370 | case UInt32(kVK_F15): self = .f15
371 | case UInt32(kVK_Help): self = .help
372 | case UInt32(kVK_Home): self = .home
373 | case UInt32(kVK_PageUp): self = .pageUp
374 | case UInt32(kVK_ForwardDelete): self = .forwardDelete
375 | case UInt32(kVK_F4): self = .f4
376 | case UInt32(kVK_End): self = .end
377 | case UInt32(kVK_F2): self = .f2
378 | case UInt32(kVK_PageDown): self = .pageDown
379 | case UInt32(kVK_F1): self = .f1
380 | case UInt32(kVK_LeftArrow): self = .leftArrow
381 | case UInt32(kVK_RightArrow): self = .rightArrow
382 | case UInt32(kVK_DownArrow): self = .downArrow
383 | case UInt32(kVK_UpArrow): self = .upArrow
384 | case UInt32(kVK_ISO_Section): self = .section
385 | default: return nil
386 | }
387 | }
388 |
389 | public var carbonKeyCode: UInt32 {
390 | switch self {
391 | case .a: return UInt32(kVK_ANSI_A)
392 | case .s: return UInt32(kVK_ANSI_S)
393 | case .d: return UInt32(kVK_ANSI_D)
394 | case .f: return UInt32(kVK_ANSI_F)
395 | case .h: return UInt32(kVK_ANSI_H)
396 | case .g: return UInt32(kVK_ANSI_G)
397 | case .z: return UInt32(kVK_ANSI_Z)
398 | case .x: return UInt32(kVK_ANSI_X)
399 | case .c: return UInt32(kVK_ANSI_C)
400 | case .v: return UInt32(kVK_ANSI_V)
401 | case .b: return UInt32(kVK_ANSI_B)
402 | case .q: return UInt32(kVK_ANSI_Q)
403 | case .w: return UInt32(kVK_ANSI_W)
404 | case .e: return UInt32(kVK_ANSI_E)
405 | case .r: return UInt32(kVK_ANSI_R)
406 | case .y: return UInt32(kVK_ANSI_Y)
407 | case .t: return UInt32(kVK_ANSI_T)
408 | case .one: return UInt32(kVK_ANSI_1)
409 | case .two: return UInt32(kVK_ANSI_2)
410 | case .three: return UInt32(kVK_ANSI_3)
411 | case .four: return UInt32(kVK_ANSI_4)
412 | case .six: return UInt32(kVK_ANSI_6)
413 | case .five: return UInt32(kVK_ANSI_5)
414 | case .equal: return UInt32(kVK_ANSI_Equal)
415 | case .nine: return UInt32(kVK_ANSI_9)
416 | case .seven: return UInt32(kVK_ANSI_7)
417 | case .minus: return UInt32(kVK_ANSI_Minus)
418 | case .eight: return UInt32(kVK_ANSI_8)
419 | case .zero: return UInt32(kVK_ANSI_0)
420 | case .rightBracket: return UInt32(kVK_ANSI_RightBracket)
421 | case .o: return UInt32(kVK_ANSI_O)
422 | case .u: return UInt32(kVK_ANSI_U)
423 | case .leftBracket: return UInt32(kVK_ANSI_LeftBracket)
424 | case .i: return UInt32(kVK_ANSI_I)
425 | case .p: return UInt32(kVK_ANSI_P)
426 | case .l: return UInt32(kVK_ANSI_L)
427 | case .j: return UInt32(kVK_ANSI_J)
428 | case .quote: return UInt32(kVK_ANSI_Quote)
429 | case .k: return UInt32(kVK_ANSI_K)
430 | case .semicolon: return UInt32(kVK_ANSI_Semicolon)
431 | case .backslash: return UInt32(kVK_ANSI_Backslash)
432 | case .comma: return UInt32(kVK_ANSI_Comma)
433 | case .slash: return UInt32(kVK_ANSI_Slash)
434 | case .n: return UInt32(kVK_ANSI_N)
435 | case .m: return UInt32(kVK_ANSI_M)
436 | case .period: return UInt32(kVK_ANSI_Period)
437 | case .grave: return UInt32(kVK_ANSI_Grave)
438 | case .keypadDecimal: return UInt32(kVK_ANSI_KeypadDecimal)
439 | case .keypadMultiply: return UInt32(kVK_ANSI_KeypadMultiply)
440 | case .keypadPlus: return UInt32(kVK_ANSI_KeypadPlus)
441 | case .keypadClear: return UInt32(kVK_ANSI_KeypadClear)
442 | case .keypadDivide: return UInt32(kVK_ANSI_KeypadDivide)
443 | case .keypadEnter: return UInt32(kVK_ANSI_KeypadEnter)
444 | case .keypadMinus: return UInt32(kVK_ANSI_KeypadMinus)
445 | case .keypadEquals: return UInt32(kVK_ANSI_KeypadEquals)
446 | case .keypad0: return UInt32(kVK_ANSI_Keypad0)
447 | case .keypad1: return UInt32(kVK_ANSI_Keypad1)
448 | case .keypad2: return UInt32(kVK_ANSI_Keypad2)
449 | case .keypad3: return UInt32(kVK_ANSI_Keypad3)
450 | case .keypad4: return UInt32(kVK_ANSI_Keypad4)
451 | case .keypad5: return UInt32(kVK_ANSI_Keypad5)
452 | case .keypad6: return UInt32(kVK_ANSI_Keypad6)
453 | case .keypad7: return UInt32(kVK_ANSI_Keypad7)
454 | case .keypad8: return UInt32(kVK_ANSI_Keypad8)
455 | case .keypad9: return UInt32(kVK_ANSI_Keypad9)
456 | case .`return`: return UInt32(kVK_Return)
457 | case .tab: return UInt32(kVK_Tab)
458 | case .space: return UInt32(kVK_Space)
459 | case .delete: return UInt32(kVK_Delete)
460 | case .escape: return UInt32(kVK_Escape)
461 | case .command: return UInt32(kVK_Command)
462 | case .shift: return UInt32(kVK_Shift)
463 | case .capsLock: return UInt32(kVK_CapsLock)
464 | case .option: return UInt32(kVK_Option)
465 | case .control: return UInt32(kVK_Control)
466 | case .rightCommand: return UInt32(kVK_RightCommand)
467 | case .rightShift: return UInt32(kVK_RightShift)
468 | case .rightOption: return UInt32(kVK_RightOption)
469 | case .rightControl: return UInt32(kVK_RightControl)
470 | case .function: return UInt32(kVK_Function)
471 | case .f17: return UInt32(kVK_F17)
472 | case .volumeUp: return UInt32(kVK_VolumeUp)
473 | case .volumeDown: return UInt32(kVK_VolumeDown)
474 | case .mute: return UInt32(kVK_Mute)
475 | case .f18: return UInt32(kVK_F18)
476 | case .f19: return UInt32(kVK_F19)
477 | case .f20: return UInt32(kVK_F20)
478 | case .f5: return UInt32(kVK_F5)
479 | case .f6: return UInt32(kVK_F6)
480 | case .f7: return UInt32(kVK_F7)
481 | case .f3: return UInt32(kVK_F3)
482 | case .f8: return UInt32(kVK_F8)
483 | case .f9: return UInt32(kVK_F9)
484 | case .f11: return UInt32(kVK_F11)
485 | case .f13: return UInt32(kVK_F13)
486 | case .f16: return UInt32(kVK_F16)
487 | case .f14: return UInt32(kVK_F14)
488 | case .f10: return UInt32(kVK_F10)
489 | case .f12: return UInt32(kVK_F12)
490 | case .f15: return UInt32(kVK_F15)
491 | case .help: return UInt32(kVK_Help)
492 | case .home: return UInt32(kVK_Home)
493 | case .pageUp: return UInt32(kVK_PageUp)
494 | case .forwardDelete: return UInt32(kVK_ForwardDelete)
495 | case .f4: return UInt32(kVK_F4)
496 | case .end: return UInt32(kVK_End)
497 | case .f2: return UInt32(kVK_F2)
498 | case .pageDown: return UInt32(kVK_PageDown)
499 | case .f1: return UInt32(kVK_F1)
500 | case .leftArrow: return UInt32(kVK_LeftArrow)
501 | case .rightArrow: return UInt32(kVK_RightArrow)
502 | case .downArrow: return UInt32(kVK_DownArrow)
503 | case .upArrow: return UInt32(kVK_UpArrow)
504 | case .section: return UInt32(kVK_ISO_Section)
505 | }
506 | }
507 | }
508 |
509 | extension Key: CustomStringConvertible {
510 | public var description: String {
511 | switch self {
512 | case .a: return "A"
513 | case .s: return "S"
514 | case .d: return "D"
515 | case .f: return "F"
516 | case .h: return "H"
517 | case .g: return "G"
518 | case .z: return "Z"
519 | case .x: return "X"
520 | case .c: return "C"
521 | case .v: return "V"
522 | case .b: return "B"
523 | case .q: return "Q"
524 | case .w: return "W"
525 | case .e: return "E"
526 | case .r: return "R"
527 | case .y: return "Y"
528 | case .t: return "T"
529 | case .one, .keypad1: return "1"
530 | case .two, .keypad2: return "2"
531 | case .three, .keypad3: return "3"
532 | case .four, .keypad4: return "4"
533 | case .six, .keypad6: return "6"
534 | case .five, .keypad5: return "5"
535 | case .equal: return "="
536 | case .nine, .keypad9: return "9"
537 | case .seven, .keypad7: return "7"
538 | case .minus: return "-"
539 | case .eight, .keypad8: return "8"
540 | case .zero, .keypad0: return "0"
541 | case .rightBracket: return "]"
542 | case .o: return "O"
543 | case .u: return "U"
544 | case .leftBracket: return "["
545 | case .i: return "I"
546 | case .p: return "P"
547 | case .l: return "L"
548 | case .j: return "J"
549 | case .quote: return "\""
550 | case .k: return "K"
551 | case .semicolon: return ";"
552 | case .backslash: return "\\"
553 | case .comma: return ","
554 | case .slash: return "/"
555 | case .n: return "N"
556 | case .m: return "M"
557 | case .period: return "."
558 | case .grave: return "`"
559 | case .keypadDecimal: return "."
560 | case .keypadMultiply: return "𝗑"
561 | case .keypadPlus: return "+"
562 | case .keypadClear: return "⌧"
563 | case .keypadDivide: return "/"
564 | case .keypadEnter: return "↩︎"
565 | case .keypadMinus: return "-"
566 | case .keypadEquals: return "="
567 | case .`return`: return "↩︎"
568 | case .tab: return "⇥"
569 | case .space: return "␣"
570 | case .delete: return "⌫"
571 | case .escape: return "⎋"
572 | case .command, .rightCommand: return "⌘"
573 | case .shift, .rightShift: return "⇧"
574 | case .capsLock: return "⇪"
575 | case .option, .rightOption: return "⌥"
576 | case .control, .rightControl: return "⌃"
577 | case .function: return "fn"
578 | case .f17: return "F17"
579 | case .volumeUp: return "🔊"
580 | case .volumeDown: return "🔉"
581 | case .mute: return "🔇"
582 | case .f18: return "F18"
583 | case .f19: return "F19"
584 | case .f20: return "F20"
585 | case .f5: return "F5"
586 | case .f6: return "F6"
587 | case .f7: return "F7"
588 | case .f3: return "F3"
589 | case .f8: return "F8"
590 | case .f9: return "F9"
591 | case .f11: return "F11"
592 | case .f13: return "F13"
593 | case .f16: return "F16"
594 | case .f14: return "F14"
595 | case .f10: return "F10"
596 | case .f12: return "F12"
597 | case .f15: return "F15"
598 | case .help: return "?⃝"
599 | case .home: return "↖"
600 | case .pageUp: return "⇞"
601 | case .forwardDelete: return "⌦"
602 | case .f4: return "F4"
603 | case .end: return "↘"
604 | case .f2: return "F2"
605 | case .pageDown: return "⇟"
606 | case .f1: return "F1"
607 | case .leftArrow: return "←"
608 | case .rightArrow: return "→"
609 | case .downArrow: return "↓"
610 | case .upArrow: return "↑"
611 | case .section: return "§"
612 | }
613 | }
614 | }
615 | #endif
616 |
--------------------------------------------------------------------------------
/Sources/HotKey/KeyCombo+System.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(AppKit) && canImport(Carbon)
2 | import AppKit
3 | import Carbon
4 |
5 | extension KeyCombo {
6 | /// All system key combos
7 | ///
8 | /// - returns: array of key combos
9 | public static func systemKeyCombos() -> [KeyCombo] {
10 | var unmanagedGlobalHotkeys: Unmanaged?
11 | guard CopySymbolicHotKeys(&unmanagedGlobalHotkeys) == noErr,
12 | let globalHotkeys = unmanagedGlobalHotkeys?.takeRetainedValue() else
13 | {
14 | assertionFailure("Unable to get system-wide hotkeys")
15 | return []
16 | }
17 |
18 | return (0.. [KeyCombo] {
38 | guard let menu = NSApp.mainMenu else {
39 | return []
40 | }
41 |
42 | return keyCombos(in: menu)
43 | }
44 |
45 | /// Recursively find key combos in a menu
46 | ///
47 | /// - parameter menu: menu to search
48 | ///
49 | /// - returns: array of key combos
50 | public static func keyCombos(in menu: NSMenu) -> [KeyCombo] {
51 | var keyCombos = [KeyCombo]()
52 |
53 | for item in menu.items {
54 | if let key = Key(string: item.keyEquivalent) {
55 | keyCombos.append(KeyCombo(key: key, modifiers: item.keyEquivalentModifierMask))
56 | }
57 |
58 | if let submenu = item.submenu {
59 | keyCombos += self.keyCombos(in: submenu)
60 | }
61 | }
62 |
63 | return keyCombos
64 | }
65 |
66 | /// Standard application key combos
67 | ///
68 | /// - returns: array of key combos
69 | public static func standardKeyCombos() -> [KeyCombo] {
70 | return [
71 | // Application
72 | KeyCombo(key: .comma, modifiers: .command),
73 | KeyCombo(key: .h, modifiers: .command),
74 | KeyCombo(key: .h, modifiers: [.command, .option]),
75 | KeyCombo(key: .q, modifiers: .command),
76 |
77 | // File
78 | KeyCombo(key: .n, modifiers: .command),
79 | KeyCombo(key: .o, modifiers: .command),
80 | KeyCombo(key: .w, modifiers: .command),
81 | KeyCombo(key: .s, modifiers: .command),
82 | KeyCombo(key: .s, modifiers: [.command, .shift]),
83 | KeyCombo(key: .r, modifiers: .command),
84 | KeyCombo(key: .p, modifiers: [.command, .shift]),
85 | KeyCombo(key: .p, modifiers: .command),
86 |
87 | // Edit
88 | KeyCombo(key: .z, modifiers: .command),
89 | KeyCombo(key: .z, modifiers: [.command, .shift]),
90 | KeyCombo(key: .x, modifiers: .command),
91 | KeyCombo(key: .c, modifiers: .command),
92 | KeyCombo(key: .v, modifiers: .command),
93 | KeyCombo(key: .v, modifiers: [.command, .option, .shift]),
94 | KeyCombo(key: .a, modifiers: .command),
95 | KeyCombo(key: .f, modifiers: .command),
96 | KeyCombo(key: .f, modifiers: [.command, .option]),
97 | KeyCombo(key: .g, modifiers: .command),
98 | KeyCombo(key: .g, modifiers: [.command, .shift]),
99 | KeyCombo(key: .e, modifiers: .command),
100 | KeyCombo(key: .j, modifiers: .command),
101 | KeyCombo(key: .semicolon, modifiers: [.command, .shift]),
102 | KeyCombo(key: .semicolon, modifiers: .command),
103 |
104 | // Format
105 | KeyCombo(key: .t, modifiers: .command),
106 | KeyCombo(key: .b, modifiers: .command),
107 | KeyCombo(key: .i, modifiers: .command),
108 | KeyCombo(key: .u, modifiers: .command),
109 | KeyCombo(key: .equal, modifiers: [.command, .shift]),
110 | KeyCombo(key: .minus, modifiers: .command),
111 | KeyCombo(key: .c, modifiers: [.command, .shift]),
112 | KeyCombo(key: .c, modifiers: [.command, .option]),
113 | KeyCombo(key: .v, modifiers: [.command, .option]),
114 | KeyCombo(key: .leftBracket, modifiers: [.command, .shift]),
115 | KeyCombo(key: .backslash, modifiers: [.command, .shift]),
116 | KeyCombo(key: .rightBracket, modifiers: [.command, .shift]),
117 | KeyCombo(key: .c, modifiers: [.command, .control]),
118 | KeyCombo(key: .v, modifiers: [.command, .control]),
119 |
120 | // View
121 | KeyCombo(key: .t, modifiers: [.command, .option]),
122 | KeyCombo(key: .s, modifiers: [.command, .control]),
123 | KeyCombo(key: .f, modifiers: [.command, .control]),
124 |
125 | // Window
126 | KeyCombo(key: .m, modifiers: .command),
127 |
128 | // Help
129 | KeyCombo(key: .slash, modifiers: [.command, .shift])
130 | ]
131 | }
132 | }
133 | #endif
134 |
--------------------------------------------------------------------------------
/Sources/HotKey/KeyCombo.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(AppKit)
2 | import AppKit
3 |
4 | public struct KeyCombo: Equatable {
5 |
6 | // MARK: - Properties
7 |
8 | public var carbonKeyCode: UInt32
9 | public var carbonModifiers: UInt32
10 |
11 | public var key: Key? {
12 | get {
13 | return Key(carbonKeyCode: carbonKeyCode)
14 | }
15 |
16 | set {
17 | carbonKeyCode = newValue?.carbonKeyCode ?? 0
18 | }
19 | }
20 |
21 | public var modifiers: NSEvent.ModifierFlags {
22 | get {
23 | return NSEvent.ModifierFlags(carbonFlags: carbonModifiers)
24 | }
25 |
26 | set {
27 | carbonModifiers = newValue.carbonFlags
28 | }
29 | }
30 |
31 | public var isValid: Bool {
32 | return carbonKeyCode >= 0
33 | }
34 |
35 | // MARK: - Initializers
36 |
37 | public init(carbonKeyCode: UInt32, carbonModifiers: UInt32 = 0) {
38 | self.carbonKeyCode = carbonKeyCode
39 | self.carbonModifiers = carbonModifiers
40 | }
41 |
42 | public init(key: Key, modifiers: NSEvent.ModifierFlags = []) {
43 | self.carbonKeyCode = key.carbonKeyCode
44 | self.carbonModifiers = modifiers.carbonFlags
45 | }
46 |
47 | // MARK: - Converting Keys
48 |
49 | public static func carbonKeyCodeToString(_ carbonKeyCode: UInt32) -> String? {
50 | return nil
51 | }
52 | }
53 |
54 | extension KeyCombo {
55 | public var dictionary: [String: Any] {
56 | return [
57 | "keyCode": Int(carbonKeyCode),
58 | "modifiers": Int(carbonModifiers)
59 | ]
60 | }
61 |
62 | public init?(dictionary: [String: Any]) {
63 | guard let keyCode = dictionary["keyCode"] as? Int,
64 | let modifiers = dictionary["modifiers"] as? Int
65 | else {
66 | return nil
67 | }
68 |
69 | self.init(carbonKeyCode: UInt32(keyCode), carbonModifiers: UInt32(modifiers))
70 | }
71 | }
72 |
73 | extension KeyCombo: CustomStringConvertible {
74 | public var description: String {
75 | var output = modifiers.description
76 |
77 | if let keyDescription = key?.description {
78 | output += keyDescription
79 | }
80 |
81 | return output
82 | }
83 | }
84 | #endif
85 |
--------------------------------------------------------------------------------
/Sources/HotKey/NSEventModifierFlags+HotKey.swift:
--------------------------------------------------------------------------------
1 | #if !targetEnvironment(macCatalyst) && canImport(AppKit) && canImport(Carbon)
2 | import AppKit
3 | import Carbon
4 |
5 | extension NSEvent.ModifierFlags {
6 | public var carbonFlags: UInt32 {
7 | var carbonFlags: UInt32 = 0
8 |
9 | if contains(.command) {
10 | carbonFlags |= UInt32(cmdKey)
11 | }
12 |
13 | if contains(.option) {
14 | carbonFlags |= UInt32(optionKey)
15 | }
16 |
17 | if contains(.control) {
18 | carbonFlags |= UInt32(controlKey)
19 | }
20 |
21 | if contains(.shift) {
22 | carbonFlags |= UInt32(shiftKey)
23 | }
24 |
25 | return carbonFlags
26 | }
27 |
28 | public init(carbonFlags: UInt32) {
29 | self.init()
30 |
31 | if carbonFlags & UInt32(cmdKey) == UInt32(cmdKey) {
32 | insert(.command)
33 | }
34 |
35 | if carbonFlags & UInt32(optionKey) == UInt32(optionKey) {
36 | insert(.option)
37 | }
38 |
39 | if carbonFlags & UInt32(controlKey) == UInt32(controlKey) {
40 | insert(.control)
41 | }
42 |
43 | if carbonFlags & UInt32(shiftKey) == UInt32(shiftKey) {
44 | insert(.shift)
45 | }
46 | }
47 | }
48 |
49 | extension NSEvent.ModifierFlags: CustomStringConvertible {
50 | public var description: String {
51 | var output = ""
52 |
53 | if contains(.control) {
54 | output += Key.control.description
55 | }
56 |
57 | if contains(.option) {
58 | output += Key.option.description
59 | }
60 |
61 | if contains(.shift) {
62 | output += Key.shift.description
63 | }
64 |
65 | if contains(.command) {
66 | output += Key.command.description
67 | }
68 |
69 | return output
70 | }
71 | }
72 | #endif
73 |
--------------------------------------------------------------------------------
/Support/HotKey.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | //! Project version number for HotKey.
4 | FOUNDATION_EXPORT double HotKeyVersionNumber;
5 |
6 | //! Project version string for HotKey.
7 | FOUNDATION_EXPORT const unsigned char HotKeyVersionString[];
8 |
--------------------------------------------------------------------------------
/Support/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2017 Sam Soffes. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Support/Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/HotKeyTests/KeyComboTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import HotKey
3 |
4 | final class KeyComboTests: XCTestCase {
5 | func testSerialization() {
6 | let keyCombo = KeyCombo(key: .c, modifiers: .command)
7 |
8 | let dictionary = keyCombo.dictionary
9 | XCTAssertEqual(8, dictionary["keyCode"] as! Int)
10 | XCTAssertEqual(256, dictionary["modifiers"] as! Int)
11 |
12 | let keyCombo2 = KeyCombo(dictionary: dictionary)!
13 | XCTAssertEqual(keyCombo.key, keyCombo2.key)
14 | XCTAssertEqual(keyCombo.modifiers, keyCombo2.modifiers)
15 | }
16 |
17 | func testDescription() {
18 | XCTAssertEqual("⌘C", KeyCombo(key: .c, modifiers: .command).description)
19 | XCTAssertEqual("⌃⌥⇧⌘C", KeyCombo(key: .c, modifiers: [.command, .shift, .control, .option]).description)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/HotKeyTests/ModifierFlagsTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import AppKit
3 | import Carbon
4 | import HotKey
5 |
6 | final class ModiferFlagsTests: XCTestCase {
7 | func testCarbonToCocoaConversion() {
8 | var cocoa = NSEvent.ModifierFlags()
9 | cocoa.insert(.command)
10 | XCTAssertEqual(NSEvent.ModifierFlags(carbonFlags: UInt32(cmdKey)), cocoa)
11 |
12 | cocoa.insert(.control)
13 | cocoa.insert(.option)
14 | XCTAssertEqual(NSEvent.ModifierFlags(carbonFlags: UInt32(cmdKey|controlKey|optionKey)), cocoa)
15 | }
16 |
17 | func testCocoaToCarbonConversion() {
18 | var cocoa = NSEvent.ModifierFlags()
19 | cocoa.insert(.command)
20 | XCTAssertEqual(UInt32(cmdKey), cocoa.carbonFlags)
21 |
22 | cocoa.insert(.control)
23 | cocoa.insert(.option)
24 | XCTAssertEqual(UInt32(cmdKey|controlKey|optionKey), cocoa.carbonFlags)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------