├── 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 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | Default 560 | 561 | 562 | 563 | 564 | 565 | 566 | Left to Right 567 | 568 | 569 | 570 | 571 | 572 | 573 | Right to Left 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | --------------------------------------------------------------------------------