├── MKS
├── Sounds
│ ├── profile-1-large-up.wav
│ ├── profile-1-mouse-up.wav
│ ├── profile-1-normal-up.wav
│ ├── profile-2-enter-up.wav
│ ├── profile-2-large-up.wav
│ ├── profile-2-mouse-up.wav
│ ├── profile-2-normal-up.wav
│ ├── profile-3-enter-up.wav
│ ├── profile-3-large-up.wav
│ ├── profile-3-mouse-up.wav
│ ├── profile-3-normal-up.wav
│ ├── profile-1-large-down.wav
│ ├── profile-1-mouse-down.wav
│ ├── profile-1-normal-down.wav
│ ├── profile-2-enter-down.wav
│ ├── profile-2-large-down.wav
│ ├── profile-2-mouse-down.wav
│ ├── profile-2-normal-down.wav
│ ├── profile-3-enter-down.wav
│ ├── profile-3-large-down.wav
│ ├── profile-3-mouse-down.wav
│ └── profile-3-normal-down.wav
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ ├── keyboard-solid-128.png
│ │ ├── keyboard-solid-16.png
│ │ ├── keyboard-solid-257.png
│ │ ├── keyboard-solid-33.png
│ │ ├── keyboard-solid-513.png
│ │ ├── keyboard-solid-128@2x.png
│ │ ├── keyboard-solid-16@2x.png
│ │ ├── keyboard-solid-256@2x.png
│ │ ├── keyboard-solid-32@2x.png
│ │ ├── keyboard-solid-512@2x.png
│ │ └── Contents.json
├── AppIcon.appiconset
│ ├── sysmenuicon.png
│ ├── sysmenuicon-2.png
│ ├── keyboard-solid-16.png
│ ├── keyboard-solid-32.png
│ ├── keyboard-solid-64.png
│ ├── keyboard-solid-128.png
│ ├── keyboard-solid-256.png
│ ├── keyboard-solid-512.png
│ ├── keyboard-solid-128@2x.png
│ ├── keyboard-solid-16@2x.png
│ ├── keyboard-solid-256@2x.png
│ ├── keyboard-solid-32@2x.png
│ ├── keyboard-solid-512@2x.png
│ ├── keyboard-solid-64@2x.png
│ └── Contents.json
├── Info.plist
├── AppDelegate.swift
└── Base.lproj
│ └── MainMenu.xib
├── MKS.xcodeproj
├── project.xcworkspace
│ ├── xcuserdata
│ │ └── bogdan.xcuserdatad
│ │ │ ├── UserInterfaceState.xcuserstate
│ │ │ └── WorkspaceSettings.xcsettings
│ ├── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── bogdan.xcuserdatad
│ │ ├── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── MKS.xcscheme
│ │ └── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
└── project.pbxproj
├── LICENSE
└── README.md
/MKS/Sounds/profile-1-large-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-large-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-1-mouse-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-mouse-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-1-normal-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-normal-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-enter-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-enter-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-large-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-large-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-mouse-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-mouse-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-normal-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-normal-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-enter-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-enter-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-large-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-large-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-mouse-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-mouse-up.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-normal-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-normal-up.wav
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/MKS/Sounds/profile-1-large-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-large-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-1-mouse-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-mouse-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-1-normal-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-1-normal-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-enter-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-enter-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-large-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-large-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-mouse-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-mouse-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-2-normal-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-2-normal-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-enter-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-enter-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-large-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-large-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-mouse-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-mouse-down.wav
--------------------------------------------------------------------------------
/MKS/Sounds/profile-3-normal-down.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Sounds/profile-3-normal-down.wav
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/sysmenuicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/sysmenuicon.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/sysmenuicon-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/sysmenuicon-2.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-16.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-32.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-64.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-128.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-256.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-512.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-128@2x.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-16@2x.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-256@2x.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-32@2x.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-512@2x.png
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/keyboard-solid-64@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/AppIcon.appiconset/keyboard-solid-64@2x.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-128.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-16.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-257.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-257.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-33.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-513.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-513.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-128@2x.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-16@2x.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-256@2x.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-32@2x.png
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS/Assets.xcassets/AppIcon.appiconset/keyboard-solid-512@2x.png
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.xcworkspace/xcuserdata/bogdan.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x0054/MKS/HEAD/MKS.xcodeproj/project.xcworkspace/xcuserdata/bogdan.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/xcuserdata/bogdan.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MKS.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 43EDED8A2140E57000FF0FC2
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.xcworkspace/xcuserdata/bogdan.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | CustomLocation
7 | CustomBuildIntermediatesPath
8 | /Users/bogdan/Desktop/Build/Intermediates
9 | CustomBuildLocationType
10 | Absolute
11 | CustomBuildProductsPath
12 | /Users/bogdan/Desktop/Build/Products
13 | DerivedDataLocationStyle
14 | Default
15 | EnabledFullIndexStoreVisibility
16 |
17 | IssueFilterStyle
18 | ShowActiveSchemeOnly
19 | LiveSourceIssuesEnabled
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Bogdan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MKS/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 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSApplicationCategoryType
26 | public.app-category.utilities
27 | LSMinimumSystemVersion
28 | $(MACOSX_DEPLOYMENT_TARGET)
29 | LSUIElement
30 |
31 | NSHumanReadableCopyright
32 | Copyright © 2018 Bogdan Ryabyshchuk. All rights reserved.
33 | NSMainNibFile
34 | MainMenu
35 | NSPrincipalClass
36 | NSApplication
37 |
38 |
39 |
--------------------------------------------------------------------------------
/MKS/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "keyboard-solid-16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "keyboard-solid-32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "keyboard-solid-32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "keyboard-solid-64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "keyboard-solid-128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "keyboard-solid-256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "keyboard-solid-256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "keyboard-solid-512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "keyboard-solid-512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "keyboard-solid-1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/MKS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "keyboard-solid-16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "keyboard-solid-16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "keyboard-solid-33.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "keyboard-solid-32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "keyboard-solid-128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "keyboard-solid-128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "keyboard-solid-257.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "keyboard-solid-256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "keyboard-solid-513.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "keyboard-solid-512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/MKS.xcodeproj/xcuserdata/bogdan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
22 |
23 |
24 |
26 |
38 |
39 |
41 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MKS: Mechanical Keyboard Simulator
2 |
3 | Do you love the sweet sound of a mechanical keyboard with those Cherry MX Blue keys? Are your coworkers and family members sharpening their pitchforks and are getting ready to murder you just to stop the incessant clicking? Well, you are in luck! I have a program that will make them happy, keep you alive for a bit longer, AND will allow you to continue listening to that wonderful clicking sound you love so much. It’s called the Mechanical Keyboard Simulator, or MKS for short.
4 |
5 | I wrote this shortly after getting a mechanical keyboard. The process of keyboard acquisition was one of both joy and sorrow. The joy of gloriously typing on that beautiful mechanical marvel, and the sorrow of having to listen to my wife type on that beautiful mechanical marvel.
6 |
7 | Quickly I realized that the part that I like the best about the mechanical keyboard is actually the sound. There are some logical reasons behind it, it’s not just for the aesthetics. The sound is a form of feedback. On modern laptop keyboards there is so little feedback that every bit counts. The sound is like a reassurance. It’s your computer telling you, yes, you did indeed hit that key.
8 |
9 | Thereafter it quickly occurred to me that it would be easy to simulate the sound, rather than having to actually mechanically reproduce it. I Googled a few apps, but all of them came short, so I decided to write my own. Here are some special features of my system-wide Mechanical Keyboard Simulator:
10 |
11 | 1. Adjustable Volume as a percentage of the system volume setting.
12 | 1. Separate keydown and keyup sounds to increase realism.
13 | 1. Multiple Sound Profiles.
14 | 1. Support for Modifier Keys.
15 | 1. Stereo and Mono Sound Profile.
16 | 1. Separate sound profiles for various keys.
17 | 1. Randomized pitch.
18 |
19 | Give it a try, see what you think. I think you are going to love it, and since it works with headphones too, your family friends and coworkers are going to love it too. :)
20 |
21 | ## Update
22 |
23 | Upon user request I added a mouse simulator as well. I was very skeptical at first, but it's actually a wonderful secondary feedback. Give it a try.
24 |
25 | ## Installation
26 |
27 | ### Brew
28 |
29 | 1. Install [brew](https://brew.sh/).
30 | 1. Run:
31 |
32 | $ brew install --cask mks
33 |
34 | 1. Grant Accessibility permissions (see below).
35 |
36 | ### Manual
37 |
38 | 1. Download the [MKS.dmg](https://github.com/x0054/MKS/releases/latest) from the releases tab above.
39 | 1. Open the MKS.dmg and drag MKS app to the Application folder on your dock.
40 | 1. Grant Accessibility permissions (see below).
41 |
42 | ### Grant Accessibility permissions
43 |
44 | 1. To work the app needs Accessibilities permissions. To give the app permissions:
45 |
46 | 1. Open `System Preferences > Security & Privacy > Privacy > Accessibility`.
47 | 1. Click on the Padlock in the bottom left hand corner.
48 | 1. Drag the MKS app into the list. That’s all.
49 |
50 | 1. Now start MKS. It will add an icon to the System Bar. Play with the options and Enjoy!
51 |
52 | ## License
53 |
54 | I am releasing this app under the MIT license
55 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/xcuserdata/bogdan.xcuserdatad/xcschemes/MKS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/MKS.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 43EDED8F2140E57000FF0FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EDED8E2140E57000FF0FC2 /* AppDelegate.swift */; };
11 | 43EDED912140E57000FF0FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43EDED902140E57000FF0FC2 /* Assets.xcassets */; };
12 | 43EDED942140E57000FF0FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43EDED922140E57000FF0FC2 /* MainMenu.xib */; };
13 | 43EDEDB6214321D700FF0FC2 /* sysmenuicon.png in Resources */ = {isa = PBXBuildFile; fileRef = 43EDEDB5214321D700FF0FC2 /* sysmenuicon.png */; };
14 | 43F0C4BA261A159E008C1D1D /* profile-1-large-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A4261A159D008C1D1D /* profile-1-large-up.wav */; };
15 | 43F0C4BB261A159E008C1D1D /* profile-2-enter-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A5261A159D008C1D1D /* profile-2-enter-down.wav */; };
16 | 43F0C4BC261A159E008C1D1D /* profile-1-large-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A6261A159D008C1D1D /* profile-1-large-down.wav */; };
17 | 43F0C4BD261A159E008C1D1D /* profile-2-large-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A7261A159D008C1D1D /* profile-2-large-down.wav */; };
18 | 43F0C4BE261A159E008C1D1D /* profile-2-normal-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A8261A159D008C1D1D /* profile-2-normal-down.wav */; };
19 | 43F0C4BF261A159E008C1D1D /* profile-3-mouse-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4A9261A159D008C1D1D /* profile-3-mouse-up.wav */; };
20 | 43F0C4C0261A159E008C1D1D /* profile-3-enter-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AA261A159D008C1D1D /* profile-3-enter-up.wav */; };
21 | 43F0C4C1261A159E008C1D1D /* profile-3-normal-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AB261A159D008C1D1D /* profile-3-normal-up.wav */; };
22 | 43F0C4C2261A159E008C1D1D /* profile-2-mouse-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AC261A159D008C1D1D /* profile-2-mouse-down.wav */; };
23 | 43F0C4C3261A159E008C1D1D /* profile-2-large-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AD261A159D008C1D1D /* profile-2-large-up.wav */; };
24 | 43F0C4C4261A159E008C1D1D /* profile-2-mouse-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AE261A159D008C1D1D /* profile-2-mouse-up.wav */; };
25 | 43F0C4C5261A159E008C1D1D /* profile-2-normal-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4AF261A159D008C1D1D /* profile-2-normal-up.wav */; };
26 | 43F0C4C6261A159E008C1D1D /* profile-3-enter-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B0261A159D008C1D1D /* profile-3-enter-down.wav */; };
27 | 43F0C4C7261A159E008C1D1D /* profile-3-normal-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B1261A159D008C1D1D /* profile-3-normal-down.wav */; };
28 | 43F0C4C8261A159E008C1D1D /* profile-1-mouse-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B2261A159D008C1D1D /* profile-1-mouse-up.wav */; };
29 | 43F0C4C9261A159E008C1D1D /* profile-1-mouse-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B3261A159E008C1D1D /* profile-1-mouse-down.wav */; };
30 | 43F0C4CA261A159E008C1D1D /* profile-2-enter-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B4261A159E008C1D1D /* profile-2-enter-up.wav */; };
31 | 43F0C4CB261A159E008C1D1D /* profile-1-normal-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B5261A159E008C1D1D /* profile-1-normal-up.wav */; };
32 | 43F0C4CC261A159E008C1D1D /* profile-1-normal-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B6261A159E008C1D1D /* profile-1-normal-down.wav */; };
33 | 43F0C4CD261A159E008C1D1D /* profile-3-large-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B7261A159E008C1D1D /* profile-3-large-down.wav */; };
34 | 43F0C4CE261A159E008C1D1D /* profile-3-large-up.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B8261A159E008C1D1D /* profile-3-large-up.wav */; };
35 | 43F0C4CF261A159E008C1D1D /* profile-3-mouse-down.wav in Resources */ = {isa = PBXBuildFile; fileRef = 43F0C4B9261A159E008C1D1D /* profile-3-mouse-down.wav */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXFileReference section */
39 | 43EDED8B2140E57000FF0FC2 /* MKS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MKS.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | 43EDED8E2140E57000FF0FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | 43EDED902140E57000FF0FC2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
42 | 43EDED932140E57000FF0FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
43 | 43EDED952140E57000FF0FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | 43EDEDB5214321D700FF0FC2 /* sysmenuicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = sysmenuicon.png; path = AppIcon.appiconset/sysmenuicon.png; sourceTree = ""; };
45 | 43F0C4A4261A159D008C1D1D /* profile-1-large-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-large-up.wav"; path = "Sounds/profile-1-large-up.wav"; sourceTree = ""; };
46 | 43F0C4A5261A159D008C1D1D /* profile-2-enter-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-enter-down.wav"; path = "Sounds/profile-2-enter-down.wav"; sourceTree = ""; };
47 | 43F0C4A6261A159D008C1D1D /* profile-1-large-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-large-down.wav"; path = "Sounds/profile-1-large-down.wav"; sourceTree = ""; };
48 | 43F0C4A7261A159D008C1D1D /* profile-2-large-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-large-down.wav"; path = "Sounds/profile-2-large-down.wav"; sourceTree = ""; };
49 | 43F0C4A8261A159D008C1D1D /* profile-2-normal-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-normal-down.wav"; path = "Sounds/profile-2-normal-down.wav"; sourceTree = ""; };
50 | 43F0C4A9261A159D008C1D1D /* profile-3-mouse-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-mouse-up.wav"; path = "Sounds/profile-3-mouse-up.wav"; sourceTree = ""; };
51 | 43F0C4AA261A159D008C1D1D /* profile-3-enter-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-enter-up.wav"; path = "Sounds/profile-3-enter-up.wav"; sourceTree = ""; };
52 | 43F0C4AB261A159D008C1D1D /* profile-3-normal-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-normal-up.wav"; path = "Sounds/profile-3-normal-up.wav"; sourceTree = ""; };
53 | 43F0C4AC261A159D008C1D1D /* profile-2-mouse-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-mouse-down.wav"; path = "Sounds/profile-2-mouse-down.wav"; sourceTree = ""; };
54 | 43F0C4AD261A159D008C1D1D /* profile-2-large-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-large-up.wav"; path = "Sounds/profile-2-large-up.wav"; sourceTree = ""; };
55 | 43F0C4AE261A159D008C1D1D /* profile-2-mouse-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-mouse-up.wav"; path = "Sounds/profile-2-mouse-up.wav"; sourceTree = ""; };
56 | 43F0C4AF261A159D008C1D1D /* profile-2-normal-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-normal-up.wav"; path = "Sounds/profile-2-normal-up.wav"; sourceTree = ""; };
57 | 43F0C4B0261A159D008C1D1D /* profile-3-enter-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-enter-down.wav"; path = "Sounds/profile-3-enter-down.wav"; sourceTree = ""; };
58 | 43F0C4B1261A159D008C1D1D /* profile-3-normal-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-normal-down.wav"; path = "Sounds/profile-3-normal-down.wav"; sourceTree = ""; };
59 | 43F0C4B2261A159D008C1D1D /* profile-1-mouse-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-mouse-up.wav"; path = "Sounds/profile-1-mouse-up.wav"; sourceTree = ""; };
60 | 43F0C4B3261A159E008C1D1D /* profile-1-mouse-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-mouse-down.wav"; path = "Sounds/profile-1-mouse-down.wav"; sourceTree = ""; };
61 | 43F0C4B4261A159E008C1D1D /* profile-2-enter-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-2-enter-up.wav"; path = "Sounds/profile-2-enter-up.wav"; sourceTree = ""; };
62 | 43F0C4B5261A159E008C1D1D /* profile-1-normal-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-normal-up.wav"; path = "Sounds/profile-1-normal-up.wav"; sourceTree = ""; };
63 | 43F0C4B6261A159E008C1D1D /* profile-1-normal-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-1-normal-down.wav"; path = "Sounds/profile-1-normal-down.wav"; sourceTree = ""; };
64 | 43F0C4B7261A159E008C1D1D /* profile-3-large-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-large-down.wav"; path = "Sounds/profile-3-large-down.wav"; sourceTree = ""; };
65 | 43F0C4B8261A159E008C1D1D /* profile-3-large-up.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-large-up.wav"; path = "Sounds/profile-3-large-up.wav"; sourceTree = ""; };
66 | 43F0C4B9261A159E008C1D1D /* profile-3-mouse-down.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = "profile-3-mouse-down.wav"; path = "Sounds/profile-3-mouse-down.wav"; sourceTree = ""; };
67 | /* End PBXFileReference section */
68 |
69 | /* Begin PBXFrameworksBuildPhase section */
70 | 43EDED882140E57000FF0FC2 /* Frameworks */ = {
71 | isa = PBXFrameworksBuildPhase;
72 | buildActionMask = 2147483647;
73 | files = (
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXFrameworksBuildPhase section */
78 |
79 | /* Begin PBXGroup section */
80 | 43EDED822140E57000FF0FC2 = {
81 | isa = PBXGroup;
82 | children = (
83 | 43EDED8D2140E57000FF0FC2 /* MKS */,
84 | 43EDED8C2140E57000FF0FC2 /* Products */,
85 | );
86 | sourceTree = "";
87 | };
88 | 43EDED8C2140E57000FF0FC2 /* Products */ = {
89 | isa = PBXGroup;
90 | children = (
91 | 43EDED8B2140E57000FF0FC2 /* MKS.app */,
92 | );
93 | name = Products;
94 | sourceTree = "";
95 | };
96 | 43EDED8D2140E57000FF0FC2 /* MKS */ = {
97 | isa = PBXGroup;
98 | children = (
99 | 43EDEDAA214317EF00FF0FC2 /* Images */,
100 | 43EDEDA32141C93D00FF0FC2 /* Sounds */,
101 | 43EDED8E2140E57000FF0FC2 /* AppDelegate.swift */,
102 | 43EDED902140E57000FF0FC2 /* Assets.xcassets */,
103 | 43EDED922140E57000FF0FC2 /* MainMenu.xib */,
104 | 43EDED952140E57000FF0FC2 /* Info.plist */,
105 | );
106 | path = MKS;
107 | sourceTree = "";
108 | };
109 | 43EDEDA32141C93D00FF0FC2 /* Sounds */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 43F0C4A6261A159D008C1D1D /* profile-1-large-down.wav */,
113 | 43F0C4A4261A159D008C1D1D /* profile-1-large-up.wav */,
114 | 43F0C4B3261A159E008C1D1D /* profile-1-mouse-down.wav */,
115 | 43F0C4B2261A159D008C1D1D /* profile-1-mouse-up.wav */,
116 | 43F0C4B6261A159E008C1D1D /* profile-1-normal-down.wav */,
117 | 43F0C4B5261A159E008C1D1D /* profile-1-normal-up.wav */,
118 | 43F0C4A5261A159D008C1D1D /* profile-2-enter-down.wav */,
119 | 43F0C4B4261A159E008C1D1D /* profile-2-enter-up.wav */,
120 | 43F0C4A7261A159D008C1D1D /* profile-2-large-down.wav */,
121 | 43F0C4AD261A159D008C1D1D /* profile-2-large-up.wav */,
122 | 43F0C4AC261A159D008C1D1D /* profile-2-mouse-down.wav */,
123 | 43F0C4AE261A159D008C1D1D /* profile-2-mouse-up.wav */,
124 | 43F0C4A8261A159D008C1D1D /* profile-2-normal-down.wav */,
125 | 43F0C4AF261A159D008C1D1D /* profile-2-normal-up.wav */,
126 | 43F0C4B0261A159D008C1D1D /* profile-3-enter-down.wav */,
127 | 43F0C4AA261A159D008C1D1D /* profile-3-enter-up.wav */,
128 | 43F0C4B7261A159E008C1D1D /* profile-3-large-down.wav */,
129 | 43F0C4B8261A159E008C1D1D /* profile-3-large-up.wav */,
130 | 43F0C4B9261A159E008C1D1D /* profile-3-mouse-down.wav */,
131 | 43F0C4A9261A159D008C1D1D /* profile-3-mouse-up.wav */,
132 | 43F0C4B1261A159D008C1D1D /* profile-3-normal-down.wav */,
133 | 43F0C4AB261A159D008C1D1D /* profile-3-normal-up.wav */,
134 | );
135 | name = Sounds;
136 | sourceTree = "";
137 | };
138 | 43EDEDAA214317EF00FF0FC2 /* Images */ = {
139 | isa = PBXGroup;
140 | children = (
141 | 43EDEDB5214321D700FF0FC2 /* sysmenuicon.png */,
142 | );
143 | name = Images;
144 | sourceTree = "";
145 | };
146 | /* End PBXGroup section */
147 |
148 | /* Begin PBXNativeTarget section */
149 | 43EDED8A2140E57000FF0FC2 /* MKS */ = {
150 | isa = PBXNativeTarget;
151 | buildConfigurationList = 43EDED982140E57000FF0FC2 /* Build configuration list for PBXNativeTarget "MKS" */;
152 | buildPhases = (
153 | 43EDED872140E57000FF0FC2 /* Sources */,
154 | 43EDED882140E57000FF0FC2 /* Frameworks */,
155 | 43EDED892140E57000FF0FC2 /* Resources */,
156 | );
157 | buildRules = (
158 | );
159 | dependencies = (
160 | );
161 | name = MKS;
162 | productName = MechKey;
163 | productReference = 43EDED8B2140E57000FF0FC2 /* MKS.app */;
164 | productType = "com.apple.product-type.application";
165 | };
166 | /* End PBXNativeTarget section */
167 |
168 | /* Begin PBXProject section */
169 | 43EDED832140E57000FF0FC2 /* Project object */ = {
170 | isa = PBXProject;
171 | attributes = {
172 | LastSwiftUpdateCheck = 0730;
173 | LastUpgradeCheck = 1100;
174 | ORGANIZATIONNAME = "Bogdan Ryabyshchuk";
175 | TargetAttributes = {
176 | 43EDED8A2140E57000FF0FC2 = {
177 | CreatedOnToolsVersion = 7.3;
178 | DevelopmentTeam = 7VC9H747X6;
179 | LastSwiftMigration = 1100;
180 | ProvisioningStyle = Manual;
181 | };
182 | };
183 | };
184 | buildConfigurationList = 43EDED862140E57000FF0FC2 /* Build configuration list for PBXProject "MKS" */;
185 | compatibilityVersion = "Xcode 3.2";
186 | developmentRegion = en;
187 | hasScannedForEncodings = 0;
188 | knownRegions = (
189 | en,
190 | Base,
191 | );
192 | mainGroup = 43EDED822140E57000FF0FC2;
193 | productRefGroup = 43EDED8C2140E57000FF0FC2 /* Products */;
194 | projectDirPath = "";
195 | projectRoot = "";
196 | targets = (
197 | 43EDED8A2140E57000FF0FC2 /* MKS */,
198 | );
199 | };
200 | /* End PBXProject section */
201 |
202 | /* Begin PBXResourcesBuildPhase section */
203 | 43EDED892140E57000FF0FC2 /* Resources */ = {
204 | isa = PBXResourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 43F0C4BF261A159E008C1D1D /* profile-3-mouse-up.wav in Resources */,
208 | 43F0C4C8261A159E008C1D1D /* profile-1-mouse-up.wav in Resources */,
209 | 43F0C4BE261A159E008C1D1D /* profile-2-normal-down.wav in Resources */,
210 | 43F0C4BC261A159E008C1D1D /* profile-1-large-down.wav in Resources */,
211 | 43F0C4C2261A159E008C1D1D /* profile-2-mouse-down.wav in Resources */,
212 | 43EDEDB6214321D700FF0FC2 /* sysmenuicon.png in Resources */,
213 | 43F0C4C6261A159E008C1D1D /* profile-3-enter-down.wav in Resources */,
214 | 43F0C4CB261A159E008C1D1D /* profile-1-normal-up.wav in Resources */,
215 | 43F0C4CD261A159E008C1D1D /* profile-3-large-down.wav in Resources */,
216 | 43F0C4C3261A159E008C1D1D /* profile-2-large-up.wav in Resources */,
217 | 43F0C4BB261A159E008C1D1D /* profile-2-enter-down.wav in Resources */,
218 | 43F0C4C5261A159E008C1D1D /* profile-2-normal-up.wav in Resources */,
219 | 43EDED912140E57000FF0FC2 /* Assets.xcassets in Resources */,
220 | 43F0C4C1261A159E008C1D1D /* profile-3-normal-up.wav in Resources */,
221 | 43F0C4CC261A159E008C1D1D /* profile-1-normal-down.wav in Resources */,
222 | 43EDED942140E57000FF0FC2 /* MainMenu.xib in Resources */,
223 | 43F0C4C4261A159E008C1D1D /* profile-2-mouse-up.wav in Resources */,
224 | 43F0C4BD261A159E008C1D1D /* profile-2-large-down.wav in Resources */,
225 | 43F0C4CF261A159E008C1D1D /* profile-3-mouse-down.wav in Resources */,
226 | 43F0C4CA261A159E008C1D1D /* profile-2-enter-up.wav in Resources */,
227 | 43F0C4C9261A159E008C1D1D /* profile-1-mouse-down.wav in Resources */,
228 | 43F0C4C0261A159E008C1D1D /* profile-3-enter-up.wav in Resources */,
229 | 43F0C4CE261A159E008C1D1D /* profile-3-large-up.wav in Resources */,
230 | 43F0C4BA261A159E008C1D1D /* profile-1-large-up.wav in Resources */,
231 | 43F0C4C7261A159E008C1D1D /* profile-3-normal-down.wav in Resources */,
232 | );
233 | runOnlyForDeploymentPostprocessing = 0;
234 | };
235 | /* End PBXResourcesBuildPhase section */
236 |
237 | /* Begin PBXSourcesBuildPhase section */
238 | 43EDED872140E57000FF0FC2 /* Sources */ = {
239 | isa = PBXSourcesBuildPhase;
240 | buildActionMask = 2147483647;
241 | files = (
242 | 43EDED8F2140E57000FF0FC2 /* AppDelegate.swift in Sources */,
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | /* End PBXSourcesBuildPhase section */
247 |
248 | /* Begin PBXVariantGroup section */
249 | 43EDED922140E57000FF0FC2 /* MainMenu.xib */ = {
250 | isa = PBXVariantGroup;
251 | children = (
252 | 43EDED932140E57000FF0FC2 /* Base */,
253 | );
254 | name = MainMenu.xib;
255 | sourceTree = "";
256 | };
257 | /* End PBXVariantGroup section */
258 |
259 | /* Begin XCBuildConfiguration section */
260 | 43EDED962140E57000FF0FC2 /* Debug */ = {
261 | isa = XCBuildConfiguration;
262 | buildSettings = {
263 | ALWAYS_SEARCH_USER_PATHS = NO;
264 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
265 | CLANG_ANALYZER_NONNULL = YES;
266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
267 | CLANG_CXX_LIBRARY = "libc++";
268 | CLANG_ENABLE_MODULES = YES;
269 | CLANG_ENABLE_OBJC_ARC = YES;
270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
271 | CLANG_WARN_BOOL_CONVERSION = YES;
272 | CLANG_WARN_COMMA = YES;
273 | CLANG_WARN_CONSTANT_CONVERSION = YES;
274 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
276 | CLANG_WARN_EMPTY_BODY = YES;
277 | CLANG_WARN_ENUM_CONVERSION = YES;
278 | CLANG_WARN_INFINITE_RECURSION = YES;
279 | CLANG_WARN_INT_CONVERSION = YES;
280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
285 | CLANG_WARN_STRICT_PROTOTYPES = YES;
286 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
287 | CLANG_WARN_UNREACHABLE_CODE = YES;
288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
289 | CODE_SIGN_IDENTITY = "Developer ID Installer: Bogdan Ryabyshchuk (7VC9H747X6)";
290 | COPY_PHASE_STRIP = NO;
291 | DEBUG_INFORMATION_FORMAT = dwarf;
292 | ENABLE_STRICT_OBJC_MSGSEND = YES;
293 | ENABLE_TESTABILITY = YES;
294 | GCC_C_LANGUAGE_STANDARD = gnu99;
295 | GCC_DYNAMIC_NO_PIC = NO;
296 | GCC_NO_COMMON_BLOCKS = YES;
297 | GCC_OPTIMIZATION_LEVEL = 0;
298 | GCC_PREPROCESSOR_DEFINITIONS = (
299 | "DEBUG=1",
300 | "$(inherited)",
301 | );
302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
304 | GCC_WARN_UNDECLARED_SELECTOR = YES;
305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
306 | GCC_WARN_UNUSED_FUNCTION = YES;
307 | GCC_WARN_UNUSED_VARIABLE = YES;
308 | MACOSX_DEPLOYMENT_TARGET = 10.11;
309 | MTL_ENABLE_DEBUG_INFO = YES;
310 | ONLY_ACTIVE_ARCH = YES;
311 | SDKROOT = macosx;
312 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
313 | SWIFT_VERSION = 4.2;
314 | };
315 | name = Debug;
316 | };
317 | 43EDED972140E57000FF0FC2 /* Release */ = {
318 | isa = XCBuildConfiguration;
319 | buildSettings = {
320 | ALWAYS_SEARCH_USER_PATHS = NO;
321 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
322 | CLANG_ANALYZER_NONNULL = YES;
323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
324 | CLANG_CXX_LIBRARY = "libc++";
325 | CLANG_ENABLE_MODULES = YES;
326 | CLANG_ENABLE_OBJC_ARC = YES;
327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
328 | CLANG_WARN_BOOL_CONVERSION = YES;
329 | CLANG_WARN_COMMA = YES;
330 | CLANG_WARN_CONSTANT_CONVERSION = YES;
331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
333 | CLANG_WARN_EMPTY_BODY = YES;
334 | CLANG_WARN_ENUM_CONVERSION = YES;
335 | CLANG_WARN_INFINITE_RECURSION = YES;
336 | CLANG_WARN_INT_CONVERSION = YES;
337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
342 | CLANG_WARN_STRICT_PROTOTYPES = YES;
343 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
344 | CLANG_WARN_UNREACHABLE_CODE = YES;
345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
346 | CODE_SIGN_IDENTITY = "Developer ID Installer: Bogdan Ryabyshchuk (7VC9H747X6)";
347 | COPY_PHASE_STRIP = NO;
348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
349 | ENABLE_NS_ASSERTIONS = NO;
350 | ENABLE_STRICT_OBJC_MSGSEND = YES;
351 | GCC_C_LANGUAGE_STANDARD = gnu99;
352 | GCC_NO_COMMON_BLOCKS = YES;
353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
355 | GCC_WARN_UNDECLARED_SELECTOR = YES;
356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
357 | GCC_WARN_UNUSED_FUNCTION = YES;
358 | GCC_WARN_UNUSED_VARIABLE = YES;
359 | MACOSX_DEPLOYMENT_TARGET = 10.11;
360 | MTL_ENABLE_DEBUG_INFO = NO;
361 | SDKROOT = macosx;
362 | SWIFT_COMPILATION_MODE = wholemodule;
363 | SWIFT_VERSION = 4.2;
364 | };
365 | name = Release;
366 | };
367 | 43EDED992140E57000FF0FC2 /* Debug */ = {
368 | isa = XCBuildConfiguration;
369 | buildSettings = {
370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
371 | CODE_SIGN_IDENTITY = "Developer ID Application";
372 | CODE_SIGN_STYLE = Manual;
373 | COMBINE_HIDPI_IMAGES = YES;
374 | DEVELOPMENT_TEAM = 7VC9H747X6;
375 | ENABLE_HARDENED_RUNTIME = YES;
376 | INFOPLIST_FILE = "$(SRCROOT)/MKS/Info.plist";
377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
378 | MARKETING_VERSION = 1.7;
379 | PRODUCT_BUNDLE_IDENTIFIER = com.zynath.MKS;
380 | PRODUCT_NAME = MKS;
381 | PROVISIONING_PROFILE_SPECIFIER = "MKS-Profile";
382 | SWIFT_VERSION = 5.0;
383 | };
384 | name = Debug;
385 | };
386 | 43EDED9A2140E57000FF0FC2 /* Release */ = {
387 | isa = XCBuildConfiguration;
388 | buildSettings = {
389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
390 | CODE_SIGN_IDENTITY = "Developer ID Application";
391 | CODE_SIGN_STYLE = Manual;
392 | COMBINE_HIDPI_IMAGES = YES;
393 | DEVELOPMENT_TEAM = 7VC9H747X6;
394 | ENABLE_HARDENED_RUNTIME = YES;
395 | INFOPLIST_FILE = "$(SRCROOT)/MKS/Info.plist";
396 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
397 | MARKETING_VERSION = 1.7;
398 | PRODUCT_BUNDLE_IDENTIFIER = com.zynath.MKS;
399 | PRODUCT_NAME = MKS;
400 | PROVISIONING_PROFILE_SPECIFIER = "MKS-Profile";
401 | SWIFT_VERSION = 5.0;
402 | };
403 | name = Release;
404 | };
405 | /* End XCBuildConfiguration section */
406 |
407 | /* Begin XCConfigurationList section */
408 | 43EDED862140E57000FF0FC2 /* Build configuration list for PBXProject "MKS" */ = {
409 | isa = XCConfigurationList;
410 | buildConfigurations = (
411 | 43EDED962140E57000FF0FC2 /* Debug */,
412 | 43EDED972140E57000FF0FC2 /* Release */,
413 | );
414 | defaultConfigurationIsVisible = 0;
415 | defaultConfigurationName = Release;
416 | };
417 | 43EDED982140E57000FF0FC2 /* Build configuration list for PBXNativeTarget "MKS" */ = {
418 | isa = XCConfigurationList;
419 | buildConfigurations = (
420 | 43EDED992140E57000FF0FC2 /* Debug */,
421 | 43EDED9A2140E57000FF0FC2 /* Release */,
422 | );
423 | defaultConfigurationIsVisible = 0;
424 | defaultConfigurationName = Release;
425 | };
426 | /* End XCConfigurationList section */
427 | };
428 | rootObject = 43EDED832140E57000FF0FC2 /* Project object */;
429 | }
430 |
--------------------------------------------------------------------------------
/MKS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MechKey
4 | //
5 | // Created by Bogdan Ryabyshchuk on 9/5/18.
6 | // Copyright © 2018 Bogdan Ryabyshchuk. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import AVFoundation
11 | import Darwin
12 | import AppKit
13 | import Foundation
14 |
15 | @NSApplicationMain
16 | class AppDelegate: NSObject, NSApplicationDelegate {
17 |
18 | // MARK: App Wide Variables and Settings
19 |
20 | // Player Arrays and Sound Profiles
21 | var profile: Int = 0
22 |
23 | let soundFiles: [[Int: (String, String)]] = [[0: ("profile-1-normal-down", "profile-1-normal-up"),
24 | 1: ("profile-1-large-down", "profile-1-large-up"),
25 | 2: ("profile-1-large-down", "profile-1-large-up"),
26 | 3: ("profile-1-mouse-down", "profile-1-mouse-up")],
27 | [0: ("profile-2-normal-down", "profile-2-normal-up"),
28 | 1: ("profile-2-large-down", "profile-2-large-up"),
29 | 2: ("profile-2-enter-down", "profile-2-enter-up"),
30 | 3: ("profile-2-mouse-down", "profile-2-mouse-up")],
31 | [0: ("profile-3-normal-down", "profile-3-normal-up"),
32 | 1: ("profile-3-large-down", "profile-3-large-up"),
33 | 2: ("profile-3-enter-down", "profile-3-enter-up"),
34 | 3: ("profile-3-mouse-down", "profile-3-mouse-up")]]
35 |
36 | var players: [Int: ([AVAudioPlayer?], [AVAudioPlayer?])] = [:]
37 | var playersCurrentPlayer: [Int: (Int, Int)] = [:]
38 | var playersMax: Int = 15
39 |
40 | // App Settings
41 | var volumeLevel:Float = 1.0
42 | var volumeMuted:Bool = false
43 |
44 | var modKeysMuted:Bool = false
45 |
46 | var stereoWidth:Float = 0.2
47 | var stereoWidthDefult:Float = 0.2
48 |
49 | var keyUpSound = true
50 |
51 | var keyRandomize = false
52 |
53 | var mouseEffects = true
54 |
55 | // Other Variables
56 | var menuItem:NSStatusItem? = nil
57 |
58 | // Debugging Messages
59 | var debugging = false
60 |
61 | // MARK: Start and Exit Functions
62 | func applicationDidFinishLaunching(_ notification: Notification) {
63 | // Load Settings
64 | volumeLoad()
65 | modKeyMutedLoad()
66 | stereoWidthLoad()
67 | profileLoad()
68 | keyUpSoundLoad()
69 | keyRandomizeLoad()
70 | volumeUpdate()
71 | mouseEffectsLoad()
72 |
73 | // Create the Menu
74 | menuCreate()
75 |
76 | // Check for Permissions
77 | checkPrivecyAccess()
78 |
79 | // Add Key Listeners
80 | loadKeyListeners()
81 | }
82 |
83 | // MARK: Event Listeners
84 |
85 | func loadKeyListeners() {
86 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.keyDown, handler: { (event) -> Void in
87 | self.keyPressDown(event: event)
88 | })
89 |
90 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.keyUp, handler: { (event) -> Void in
91 | self.keyPressUp(event: event)
92 | })
93 |
94 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.flagsChanged, handler: { (event) -> Void in
95 | self.keyPressMod(event: event)
96 | })
97 |
98 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.leftMouseDown, handler: { (event) -> Void in
99 | self.mousePressDown(event: event)
100 | })
101 |
102 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.leftMouseUp, handler: { (event) -> Void in
103 | self.mousePressUp(event: event)
104 | })
105 |
106 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.rightMouseDown, handler: { (event) -> Void in
107 | self.mousePressDown(event: event)
108 | })
109 |
110 | NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.rightMouseUp, handler: { (event) -> Void in
111 | self.mousePressUp(event: event)
112 | })
113 | }
114 |
115 | // MARK: Mouse Functions
116 | var mousePrevDown: NSDate = NSDate()
117 | var mousePrevUp: NSDate = NSDate()
118 |
119 | // Mouse Press Down Event Function
120 | func mousePressDown(event:NSEvent){
121 | if self.mouseEffects {
122 | let timeSinceLastMouseDownEvent = mousePrevDown.timeIntervalSinceNow
123 | mousePrevDown = NSDate()
124 | let prevEventTooClose: Bool = (timeSinceLastMouseDownEvent >= -0.045)
125 | if self.debugging {
126 | if prevEventTooClose {
127 | systemMenuMessage(message: "Mouse - Too Close")
128 | } else {
129 | systemMenuMessage(message: "Mouse - Ok")
130 | }
131 | }
132 | if !volumeMuted && !prevEventTooClose {
133 | playSoundForKey(key: 1000, keyIsDown: true)
134 | }
135 | }
136 | }
137 |
138 | // Mouse Press Up Event Function
139 | func mousePressUp(event:NSEvent){
140 | if self.mouseEffects {
141 | let timeSinceLastMouseUpEvent = mousePrevUp.timeIntervalSinceNow
142 | mousePrevUp = NSDate()
143 | let prevEventTooClose: Bool = (timeSinceLastMouseUpEvent >= -0.045)
144 | if self.debugging {
145 | if prevEventTooClose {
146 | systemMenuMessage(message: "Mouse - Too Close")
147 | } else {
148 | systemMenuMessage(message: "Mouse - Ok")
149 | }
150 | }
151 | if !volumeMuted && !prevEventTooClose {
152 | playSoundForKey(key: 1000, keyIsDown: false)
153 | }
154 | }
155 | }
156 |
157 | // MARK: Key Functions
158 | // Key Press Function for Normal Keys
159 | func keyPressDown(event:NSEvent){
160 | if !volumeMuted {
161 | if !event.isARepeat {
162 | playSoundForKey(key: Int(event.keyCode), keyIsDown: true)
163 | systemMenuMessage(message: "\(event.keyCode)")
164 | }
165 | }
166 |
167 | // Some Hot Key Processing Code
168 | if modKeysPrevMask == modKeyMask["keyFunction"]! {
169 | if !event.isARepeat {
170 | // Fn+Esc Mute Keys
171 | if event.keyCode == 53 {
172 | if volumeMuted {
173 | volumeSet(vol: self.volumeLevel, muted: false)
174 | } else {
175 | volumeSet(vol: self.volumeLevel, muted: true)
176 | }
177 | }
178 | }
179 | }
180 | }
181 |
182 | func keyPressUp(event:NSEvent){
183 | if !volumeMuted {
184 | if !event.isARepeat {
185 | playSoundForKey(key: Int(event.keyCode), keyIsDown: false)
186 | }
187 | }
188 | }
189 |
190 |
191 | // Key Press Functions for Mod Keys
192 | var modKeysPrev: [String: Bool] = ["keyShiftRight": false,
193 | "keyShiftLeft": false,
194 | "keyAltLeft": false,
195 | "keyAltRight": false,
196 | "keyCMDLeft": false,
197 | "keyCMDRight": false,
198 | "keyControl": false,
199 | "keyFunction": false]
200 | var modKeysPrevMask: UInt = 0
201 |
202 | let modKeyMask: [String: UInt] = ["keyShiftRight": 0b000000100000000100000100,
203 | "keyShiftLeft": 0b000000100000000100000010,
204 | "keyAltLeft": 0b000010000000000100100000,
205 | "keyAltRight": 0b000010000000000101000000,
206 | "keyCMDLeft": 0b000100000000000100001000,
207 | "keyCMDRight": 0b000100000000000100010000,
208 | "keyControl": 0b000001000000000100000001,
209 | "keyFunction": 0b100000000000000100000000]
210 |
211 | var modKeysPrevEvent: NSDate = NSDate()
212 |
213 | func keyPressMod(event:NSEvent){
214 | // Record Event Time Interval Since Last
215 | let timeSinceLastModKeyEvent = modKeysPrevEvent.timeIntervalSinceNow
216 | self.modKeysPrevEvent = NSDate()
217 |
218 | let prevEventTooClose: Bool = (timeSinceLastModKeyEvent <= -0.045)
219 | if self.debugging {
220 | if prevEventTooClose {
221 | systemMenuMessage(message: "Too Close")
222 | } else {
223 | systemMenuMessage(message: "Not Too Close")
224 | }
225 | }
226 |
227 | // Grab the Current Mod Key Mask
228 | let bitmask = event.modifierFlags.rawValue
229 |
230 | // Key Maps for the Keys
231 | let modKeysCodes: [String: Int] = ["keyShiftRight": 60,
232 | "keyShiftLeft": 57,
233 | "keyAltLeft": 58,
234 | "keyAltRight": 61,
235 | "keyCMDLeft": 55,
236 | "keyCMDRight": 555, // Not the actual Key code. Used to Diff left from right.
237 | "keyControl": 59,
238 | "keyFunction": 63]
239 |
240 | // Create New Key Press Array
241 | var modKeysCurrent: [String: Bool] = ["keyShiftRight": false,
242 | "keyShiftLeft": false,
243 | "keyAltLeft": false,
244 | "keyAltRight": false,
245 | "keyCMDLeft": false,
246 | "keyCMDRight": false,
247 | "keyControl": false,
248 | "keyFunction": false]
249 |
250 | // Create New Mod Key Array and See Which Key was Pressed or Released
251 | for (key, _) in self.modKeysPrev {
252 | modKeysCurrent[key] = (bitmask & modKeyMask[key]! == modKeyMask[key])
253 | if modKeysCurrent[key] != self.modKeysPrev[key] && !self.modKeysMuted && !self.volumeMuted && prevEventTooClose {
254 | if let keyWasDown = self.modKeysPrev[key] {
255 | if keyWasDown {
256 | // Key was Released
257 | playSoundForKey(key: modKeysCodes[key]!, keyIsDown: false)
258 | systemMenuMessage(message: "\(key) Up")
259 | } else {
260 | // Key was Pressed
261 | playSoundForKey(key: modKeysCodes[key]!, keyIsDown: true)
262 | systemMenuMessage(message: "\(key) Down")
263 | }
264 | }
265 | }
266 | }
267 |
268 | // Now Let's Update the Stored Key Mask
269 | self.modKeysPrev = modKeysCurrent
270 | self.modKeysPrevMask = bitmask
271 | }
272 |
273 | // MARK: Key Sound Function
274 |
275 | // The Map of All the Keys with Location and Sound to Play.
276 | // [key: [location, sound]]
277 | // The key is the numarical key, location is 0-10, 0 is left 10 is right, 5 is middle
278 | // Sounds are 0, 1, or 2 with 0 being normal key sounds, 1 being larger keys,
279 | // and all of the mod keys are 3, for a shifting like sound that I may add in the future
280 |
281 | let keyMap: [Int: Array] = [1000: [5,3], // Mouse Key
282 | 53: [0,0],
283 | 50: [0,0],
284 | 48: [0,1],
285 | 57: [0,2],
286 | 63: [0,2],
287 | 122: [1,0],
288 | 18: [1,0],
289 | 19: [1,0],
290 | 12: [1,0],
291 | 0: [1,0],
292 | 59: [1,2],
293 | 120: [2,0],
294 | 99: [2,0],
295 | 20: [2,0],
296 | 13: [2,0],
297 | 1: [2,0],
298 | 6: [2,0],
299 | 7: [2,0],
300 | 58: [2,2],
301 | 118: [3,0],
302 | 21: [3,0],
303 | 14: [3,0],
304 | 15: [3,0],
305 | 2: [3,0],
306 | 8: [3,0],
307 | 55: [3,2],
308 | 96: [4,0],
309 | 23: [4,0],
310 | 22: [4,0],
311 | 17: [4,0],
312 | 3: [4,0],
313 | 5: [4,0],
314 | 9: [4,0],
315 | 97: [5,0],
316 | 26: [5,0],
317 | 16: [5,0],
318 | 4: [5,0],
319 | 11: [5,0],
320 | 45: [5,0],
321 | 49: [5,1],
322 | 98: [6,0],
323 | 28: [6,0],
324 | 32: [6,0],
325 | 34: [6,0],
326 | 38: [6,0],
327 | 40: [6,0],
328 | 46: [6,0],
329 | 100: [7,0],
330 | 25: [7,0],
331 | 29: [7,0],
332 | 31: [7,0],
333 | 37: [7,0],
334 | 43: [7,0],
335 | 555: [7,2],
336 | 101: [8,0],
337 | 109: [8,0],
338 | 27: [8,0],
339 | 35: [8,0],
340 | 41: [8,0],
341 | 47: [8,0],
342 | 44: [8,0],
343 | 61: [8,2],
344 | 103: [9,0],
345 | 24: [9,0],
346 | 33: [9,0],
347 | 30: [9,0],
348 | 39: [9,0],
349 | 123: [9,1],
350 | 111: [10,0],
351 | 51: [10,1],
352 | 42: [10,0],
353 | 76: [10,1],
354 | 36: [10,1],
355 | 60: [10,2],
356 | 126: [10,1],
357 | 125: [10,1],
358 | 124: [10,1]]
359 |
360 | // Load an Array of Sound Players
361 |
362 | func loadSounds() {
363 | for (sound, files) in soundFiles[self.profile] {
364 | var downFiles: [AVAudioPlayer?] = []
365 | if let soundURL = Bundle.main.url(forResource: files.0, withExtension: "wav"){
366 | for _ in 0...self.playersMax {
367 | do {
368 | try downFiles.append( AVAudioPlayer(contentsOf: soundURL) )
369 | } catch {
370 | print("Failed to load \(files.0)")
371 | }
372 | }
373 | }else{
374 | print("Can't Find Sound Files \(files.0)")
375 | }
376 | var upFiles: [AVAudioPlayer?] = []
377 | if let soundURL = Bundle.main.url(forResource: files.1, withExtension: "wav"){
378 | for _ in 0...self.playersMax {
379 | do {
380 | try upFiles.append( AVAudioPlayer(contentsOf: soundURL) )
381 | } catch {
382 | print("Failed to load \(files.1)")
383 | }
384 | }
385 | }else{
386 | print("Can't Find Sound Files \(files.1)")
387 | }
388 |
389 | self.players[sound] = (downFiles, upFiles)
390 | self.playersCurrentPlayer[sound] = (0, 0)
391 | }
392 |
393 | // Set the Sound Level to the Settings Level
394 | volumeUpdate()
395 | }
396 |
397 | func playSoundForKey(key: Int, keyIsDown down: Bool){
398 |
399 | var keyLocation: Float = 0
400 | var keySound: Int = 0
401 |
402 | if let keySetings = keyMap[key] {
403 | keyLocation = (Float(keySetings[0]) - 5) / 5 * self.stereoWidth
404 | keySound = keySetings[1]
405 | }
406 |
407 | func play(player: AVAudioPlayer, keyLocation: Float){
408 | if !player.isPlaying {
409 | // Randomize pitch and Sound.
410 | if self.keyRandomize {
411 | // Randomize Pitch
412 | player.enableRate = true
413 | player.rate = Float.random(in: 0.9 ... 1.1 )
414 |
415 | // Randomize Volume
416 | player.volume = self.volumeLevel * Float.random(in: 0.95 ... 1.0 )
417 | }
418 |
419 | // Set the Location of the Key and Play the sound
420 | player.pan = keyLocation
421 | player.play()
422 | }
423 | }
424 |
425 | if down {
426 | if let player = self.players[keySound]?.0[(self.playersCurrentPlayer[keySound]?.0)!]{
427 | play(player: player, keyLocation: keyLocation)
428 | }
429 | self.playersCurrentPlayer[keySound]?.0 += 1
430 | if (self.playersCurrentPlayer[keySound]?.0)! >= self.playersMax {
431 | self.playersCurrentPlayer[keySound]?.0 = 0
432 | }
433 | } else if self.keyUpSound {
434 | if let player = self.players[keySound]?.1[(self.playersCurrentPlayer[keySound]?.1)!]{
435 | play(player: player, keyLocation: keyLocation)
436 | }
437 | self.playersCurrentPlayer[keySound]?.1 += 1
438 | if (self.playersCurrentPlayer[keySound]?.1)! >= self.playersMax {
439 | self.playersCurrentPlayer[keySound]?.1 = 0
440 | }
441 | }
442 | }
443 |
444 | // MARK: System Menu Setup
445 | let menuItemVolumeMute = NSMenuItem(title: "Mute Keys", action: #selector(menuSetVolMute), keyEquivalent: "")
446 | let menuItemVolume10 = NSMenuItem(title: "10% Volume", action: #selector(menuSetVol0), keyEquivalent: "")
447 | let menuItemVolume25 = NSMenuItem(title: "25% Volume", action: #selector(menuSetVol1), keyEquivalent: "")
448 | let menuItemVolume50 = NSMenuItem(title: "50% Volume", action: #selector(menuSetVol2), keyEquivalent: "")
449 | let menuItemVolume75 = NSMenuItem(title: "75% Volume", action: #selector(menuSetVol3), keyEquivalent: "")
450 | let menuItemVolume100 = NSMenuItem(title: "100% Volume", action: #selector(menuSetVol4), keyEquivalent: "")
451 | let menuItemSoundStereo = NSMenuItem(title: "Stereo Sound", action: #selector(menuStereo), keyEquivalent: "")
452 | let menuItemSoundMono = NSMenuItem(title: "Mono Sound", action: #selector(menuMono), keyEquivalent: "")
453 | let menuItemModKeysOn = NSMenuItem(title: "Mod Keys On", action: #selector(menuModsOn), keyEquivalent: "")
454 | let menuItemModKeysOff = NSMenuItem(title: "Mod Keys Off", action: #selector(menuModKeysMuted), keyEquivalent: "")
455 |
456 | let menuItemKeyUpOn = NSMenuItem(title: "Keyup Sound On", action: #selector(menuKeyupSoundOn), keyEquivalent: "")
457 | let menuItemKeyUpOff = NSMenuItem(title: "Keyup Sound Off", action: #selector(menuKeyupSoundOff), keyEquivalent: "")
458 |
459 | let menuItemRandomizeOn = NSMenuItem(title: "Randomize Pitch On", action: #selector(menuRandomizeOn), keyEquivalent: "")
460 | let menuItemRandomizeOff = NSMenuItem(title: "Randomize Pitch Off", action: #selector(menuRandomizeOff), keyEquivalent: "")
461 |
462 | let menuItemProfile0 = NSMenuItem(title: "Profile 1", action: #selector(menuProfile0), keyEquivalent: "")
463 | let menuItemProfile1 = NSMenuItem(title: "Profile 2", action: #selector(menuProfile1), keyEquivalent: "")
464 | let menuItemProfile2 = NSMenuItem(title: "Profile 3", action: #selector(menuProfile2), keyEquivalent: "")
465 |
466 | let menuItemMouseEffectsOn = NSMenuItem(title: "Mouse Effects On", action: #selector(menuMouseEffectsOn), keyEquivalent: "")
467 | let menuItemMouseEffectsOff = NSMenuItem(title: "Mouse Effects Off", action: #selector(menuMouseEffectsOff), keyEquivalent: "")
468 |
469 | let menuItemAbout = NSMenuItem(title: "About MKS", action: #selector(menuAbout), keyEquivalent: "")
470 | let menuItemQuit = NSMenuItem(title: "Quit", action: #selector(menuQuit), keyEquivalent: "")
471 |
472 | func menuCreate(){
473 | self.menuItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
474 |
475 | self.menuItem?.highlightMode = true
476 |
477 | if let imgURL = Bundle.main.url(forResource: "sysmenuicon", withExtension: "png"){
478 | let image = NSImage(byReferencing: imgURL)
479 | image.isTemplate = true
480 | image.size.width = 18
481 | image.size.height = 18
482 | self.menuItem?.image = image
483 | } else {
484 | self.menuItem?.title = "MechKey"
485 | }
486 |
487 | let menu = NSMenu()
488 | menu.addItem(self.menuItemVolumeMute)
489 | menu.addItem(self.menuItemVolume10)
490 | menu.addItem(self.menuItemVolume25)
491 | menu.addItem(self.menuItemVolume50)
492 | menu.addItem(self.menuItemVolume75)
493 | menu.addItem(self.menuItemVolume100)
494 | menu.addItem(NSMenuItem.separator())
495 | menu.addItem(self.menuItemSoundStereo)
496 | menu.addItem(self.menuItemSoundMono)
497 | menu.addItem(NSMenuItem.separator())
498 | menu.addItem(self.menuItemModKeysOn)
499 | menu.addItem(self.menuItemModKeysOff)
500 | menu.addItem(NSMenuItem.separator())
501 | menu.addItem(self.menuItemKeyUpOn)
502 | menu.addItem(self.menuItemKeyUpOff)
503 | menu.addItem(NSMenuItem.separator())
504 | menu.addItem(self.menuItemRandomizeOn)
505 | menu.addItem(self.menuItemRandomizeOff)
506 | menu.addItem(NSMenuItem.separator())
507 | menu.addItem(self.menuItemProfile0)
508 | menu.addItem(self.menuItemProfile1)
509 | menu.addItem(self.menuItemProfile2)
510 | menu.addItem(NSMenuItem.separator())
511 | menu.addItem(self.menuItemMouseEffectsOn)
512 | menu.addItem(self.menuItemMouseEffectsOff)
513 | menu.addItem(NSMenuItem.separator())
514 | menu.addItem(self.menuItemAbout)
515 | menu.addItem(self.menuItemQuit)
516 |
517 | self.menuItem?.menu = menu
518 | }
519 |
520 | @objc func menuSetVolMute(){
521 | volumeSet(vol: self.volumeLevel, muted: true)
522 | }
523 |
524 | @objc func menuSetVol0(){
525 | volumeSet(vol: 0.10, muted: false)
526 | }
527 |
528 | @objc func menuSetVol1(){
529 | volumeSet(vol: 0.25, muted: false)
530 | }
531 |
532 | @objc func menuSetVol2(){
533 | volumeSet(vol: 0.50, muted: false)
534 | }
535 |
536 | @objc func menuSetVol3(){
537 | volumeSet(vol: 0.75, muted: false)
538 | }
539 |
540 | @objc func menuSetVol4(){
541 | volumeSet(vol: 1.00, muted: false)
542 | }
543 |
544 | @objc func menuStereo(){
545 | stereoWidthSet(width: self.stereoWidthDefult)
546 | }
547 |
548 | @objc func menuMono(){
549 | stereoWidthSet(width: 0)
550 | }
551 |
552 | @objc func menuModsOn(){
553 | modKeyMutedSet(muted: false)
554 | }
555 |
556 | @objc func menuModKeysMuted(){
557 | modKeyMutedSet(muted: true)
558 | }
559 |
560 | @objc func menuKeyupSoundOn(){
561 | keyUpSoundSet(on: true)
562 | }
563 |
564 | @objc func menuKeyupSoundOff(){
565 | keyUpSoundSet(on: false)
566 | }
567 |
568 | @objc func menuRandomizeOn(){
569 | keyRandomizeSet(on: true)
570 | }
571 |
572 | @objc func menuRandomizeOff(){
573 | keyRandomizeSet(on: false)
574 | }
575 |
576 | @objc func menuProfile0(){
577 | profileSet(profile: 0)
578 | }
579 |
580 | @objc func menuProfile1(){
581 | profileSet(profile: 1)
582 | }
583 |
584 | @objc func menuProfile2(){
585 | profileSet(profile: 2)
586 | }
587 |
588 | @objc func menuMouseEffectsOn(){
589 | mouseEffectsSet(on: true)
590 | }
591 |
592 | @objc func menuMouseEffectsOff(){
593 | mouseEffectsSet(on: false)
594 | }
595 |
596 | @objc func menuAbout(){
597 | NSWorkspace.shared.open(NSURL(string: "http://www.zynath.com/MKS")! as URL)
598 | }
599 |
600 | @objc func menuQuit(){
601 | NSApp.terminate(nil)
602 | }
603 |
604 | // MARK: Volume Settings
605 | func volumeLoad(){
606 | if UserDefaults.standard.object(forKey: "VolumeLevel") != nil {
607 | self.volumeLevel = UserDefaults.standard.float(forKey: "VolumeLevel")
608 | }
609 | if UserDefaults.standard.object(forKey: "VolumeMuted") != nil {
610 | self.volumeMuted = UserDefaults.standard.bool(forKey: "VolumeMuted")
611 | }
612 | }
613 |
614 | func volumeSave(){
615 | UserDefaults.standard.set(self.volumeLevel, forKey: "VolumeLevel")
616 | UserDefaults.standard.set(self.volumeMuted, forKey: "VolumeMuted")
617 | UserDefaults.standard.synchronize()
618 | }
619 |
620 | func volumeSet(vol: Float, muted: Bool){
621 | self.volumeMuted = muted
622 | self.volumeLevel = vol
623 |
624 | volumeUpdate()
625 | volumeSave()
626 | playSoundForKey(key: 0, keyIsDown: true)
627 | }
628 |
629 | func volumeUpdate(){
630 | for (_, players) in self.players{
631 | for player in players.0 {
632 | player?.volume = self.volumeLevel
633 | player?.enableRate = false
634 | player?.rate = 1
635 | }
636 | for player in players.1 {
637 | player?.volume = self.volumeLevel
638 | player?.enableRate = false
639 | player?.rate = 1
640 | }
641 | }
642 |
643 | // Update Menu to Match the Setting
644 | self.menuItemVolumeMute.state = NSControl.StateValue.off
645 | self.menuItemVolume10.state = NSControl.StateValue.off
646 | self.menuItemVolume25.state = NSControl.StateValue.off
647 | self.menuItemVolume50.state = NSControl.StateValue.off
648 | self.menuItemVolume75.state = NSControl.StateValue.off
649 | self.menuItemVolume100.state = NSControl.StateValue.off
650 |
651 | if self.volumeMuted {
652 | self.menuItemVolumeMute.state = NSControl.StateValue.on
653 | } else if self.volumeLevel == 0.10 {
654 | self.menuItemVolume10.state = NSControl.StateValue.on
655 | } else if self.volumeLevel == 0.25 {
656 | self.menuItemVolume25.state = NSControl.StateValue.on
657 | } else if self.volumeLevel == 0.50 {
658 | self.menuItemVolume50.state = NSControl.StateValue.on
659 | } else if self.volumeLevel == 0.75 {
660 | self.menuItemVolume75.state = NSControl.StateValue.on
661 | } else if self.volumeLevel == 1 {
662 | self.menuItemVolume100.state = NSControl.StateValue.on
663 | }
664 | }
665 |
666 | // MARK: Stereo Settings
667 |
668 | func stereoWidthLoad(){
669 |
670 | if UserDefaults.standard.object(forKey: "stereoWidth") != nil {
671 | self.stereoWidth = UserDefaults.standard.float(forKey: "stereoWidth")
672 | }
673 | stereoWidthUpdate()
674 | }
675 |
676 | func stereoWidthSet(width: Float){
677 | self.stereoWidth = width
678 | UserDefaults.standard.set(self.stereoWidth, forKey: "stereoWidth")
679 | UserDefaults.standard.synchronize()
680 | stereoWidthUpdate()
681 | }
682 |
683 | func stereoWidthUpdate(){
684 | if self.stereoWidth == 0 {
685 | menuItemSoundMono.state = NSControl.StateValue.on
686 | menuItemSoundStereo.state = NSControl.StateValue.off
687 | } else {
688 | menuItemSoundMono.state = NSControl.StateValue.off
689 | menuItemSoundStereo.state = NSControl.StateValue.on
690 | }
691 | }
692 |
693 | // MARK: Mod Key Settings
694 |
695 | func modKeyMutedLoad() {
696 | if UserDefaults.standard.object(forKey: "modKeysMuted") != nil {
697 | self.modKeysMuted = UserDefaults.standard.bool(forKey: "modKeysMuted")
698 | }
699 | modKeyMutedUpdate()
700 | }
701 |
702 | func modKeyMutedSet(muted: Bool) {
703 | self.modKeysMuted = muted
704 | UserDefaults.standard.set(self.modKeysMuted, forKey: "modKeysMuted")
705 | UserDefaults.standard.synchronize()
706 | modKeyMutedUpdate()
707 | }
708 |
709 | func modKeyMutedUpdate() {
710 | if self.modKeysMuted {
711 | menuItemModKeysOff.state = NSControl.StateValue.on
712 | menuItemModKeysOn.state = NSControl.StateValue.off
713 | } else {
714 | menuItemModKeysOff.state = NSControl.StateValue.off
715 | menuItemModKeysOn.state = NSControl.StateValue.on
716 | }
717 | }
718 |
719 | // MARK: KeyUp Sound Settings
720 |
721 | func keyUpSoundLoad() {
722 | if UserDefaults.standard.object(forKey: "keyUpSound") != nil {
723 | self.keyUpSound = UserDefaults.standard.bool(forKey: "keyUpSound")
724 | }
725 | keyUpSoundUpdate()
726 | }
727 |
728 | func keyUpSoundSet(on: Bool) {
729 | self.keyUpSound = on
730 | UserDefaults.standard.set(self.keyUpSound, forKey: "keyUpSound")
731 | UserDefaults.standard.synchronize()
732 | keyUpSoundUpdate()
733 | }
734 |
735 | func keyUpSoundUpdate() {
736 | if self.keyUpSound {
737 | menuItemKeyUpOn.state = NSControl.StateValue.on
738 | menuItemKeyUpOff.state = NSControl.StateValue.off
739 | } else {
740 | menuItemKeyUpOn.state = NSControl.StateValue.off
741 | menuItemKeyUpOff.state = NSControl.StateValue.on
742 | }
743 | }
744 |
745 | // MARK: Mouse Effect Settings
746 |
747 | func mouseEffectsLoad() {
748 | if UserDefaults.standard.object(forKey: "mouseEffects") != nil {
749 | self.mouseEffects = UserDefaults.standard.bool(forKey: "mouseEffects")
750 | }
751 | mouseEffectsUpdate()
752 | }
753 |
754 | func mouseEffectsSet(on: Bool) {
755 | self.mouseEffects = on
756 | UserDefaults.standard.set(self.mouseEffects, forKey: "mouseEffects")
757 | UserDefaults.standard.synchronize()
758 | mouseEffectsUpdate()
759 | }
760 |
761 | func mouseEffectsUpdate() {
762 | if self.mouseEffects {
763 | menuItemMouseEffectsOn.state = NSControl.StateValue.on
764 | menuItemMouseEffectsOff.state = NSControl.StateValue.off
765 | } else {
766 | menuItemMouseEffectsOn.state = NSControl.StateValue.off
767 | menuItemMouseEffectsOff.state = NSControl.StateValue.on
768 | }
769 | }
770 |
771 | // MARK: Randomize Sound Setting
772 |
773 | func keyRandomizeLoad() {
774 | if UserDefaults.standard.object(forKey: "keyRandomize") != nil {
775 | self.keyRandomize = UserDefaults.standard.bool(forKey: "keyRandomize")
776 | }
777 | keyRandomizeUpdate()
778 | }
779 |
780 | func keyRandomizeSet(on: Bool) {
781 | self.keyRandomize = on
782 | UserDefaults.standard.set(self.keyRandomize, forKey: "keyRandomize")
783 | UserDefaults.standard.synchronize()
784 | keyRandomizeUpdate()
785 | }
786 |
787 | func keyRandomizeUpdate() {
788 | if self.keyRandomize {
789 | menuItemRandomizeOn.state = NSControl.StateValue.on
790 | menuItemRandomizeOff.state = NSControl.StateValue.off
791 | } else {
792 | menuItemRandomizeOn.state = NSControl.StateValue.off
793 | menuItemRandomizeOff.state = NSControl.StateValue.on
794 | // Update the preset Volume and Rates
795 | volumeUpdate()
796 | }
797 | }
798 |
799 | // MARK: Profile Settings
800 |
801 | func profileLoad(){
802 | if UserDefaults.standard.object(forKey: "profile") != nil {
803 | self.profile = UserDefaults.standard.integer(forKey: "profile")
804 | }
805 | profileUpdate()
806 | }
807 | func profileSet(profile: Int) {
808 | self.profile = profile
809 | UserDefaults.standard.set(self.profile, forKey: "profile")
810 | UserDefaults.standard.synchronize()
811 | profileUpdate()
812 | }
813 | func profileUpdate(){
814 | self.players = [:]
815 | self.playersCurrentPlayer = [:]
816 | loadSounds()
817 |
818 | self.menuItemProfile0.state = NSControl.StateValue.off
819 | self.menuItemProfile1.state = NSControl.StateValue.off
820 | self.menuItemProfile2.state = NSControl.StateValue.off
821 |
822 | if self.profile == 0 {
823 | self.menuItemProfile0.state = NSControl.StateValue.on
824 | } else if self.profile == 1 {
825 | self.menuItemProfile1.state = NSControl.StateValue.on
826 | } else if self.profile == 2 {
827 | self.menuItemProfile2.state = NSControl.StateValue.on
828 | }
829 | }
830 |
831 | // MARK: Permissions Request
832 | func checkPrivecyAccess(){
833 | //get the value for accesibility
834 | let checkOptPrompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
835 | //set the options: false means it wont ask
836 | //true means it will popup and ask
837 | let options = [checkOptPrompt: true]
838 | //translate into boolean value
839 | let accessEnabled = AXIsProcessTrustedWithOptions(options as CFDictionary?)
840 |
841 | if !accessEnabled {
842 | let alert = NSAlert()
843 | alert.messageText = "MKS Needs Permissions"
844 | alert.informativeText = "macOS is awesome at protecting your privacy! However, as a result, in order for MKS to work, you will need to add it to the list of apps that are allowed to control your computer. That's the only way MKS can know when you press a key to play that sweet mechanical keyboard sound :) To add MKS to the list of trusted apps do the following: \n\nOpen System Preferences > Security & Privacy > Privacy > Accessibility, click on the Padlock in the bottom lefthand corner, and drag the MKS app into the list. \n\nHitting OK will close MKS. After you have done this, restart the app."
845 | alert.runModal()
846 | NSApp.terminate(nil)
847 | }
848 | }
849 |
850 | // MARK: Debugging Functions
851 |
852 | func systemMenuMessage(message: String){
853 | if self.debugging {
854 | self.menuItem?.title = message
855 | }
856 | }
857 | }
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
--------------------------------------------------------------------------------
/MKS/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
665 |
666 |
667 |
--------------------------------------------------------------------------------