├── .gitignore ├── Gamepad Menu Helper ├── AppDelegate.h ├── AppDelegate.m ├── Info.plist └── main.m ├── Gamepad Menu.xcodeproj └── project.pbxproj ├── Gamepad Menu ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── AppIcon-128x128.png │ │ ├── AppIcon-128x128@2x.png │ │ ├── AppIcon-16x16.png │ │ ├── AppIcon-16x16@2x.png │ │ ├── AppIcon-256x256.png │ │ ├── AppIcon-256x256@2x.png │ │ ├── AppIcon-32x32.png │ │ ├── AppIcon-32x32@2x.png │ │ ├── AppIcon-512x512.png │ │ ├── AppIcon-512x512@2x.png │ │ └── Contents.json │ ├── Contents.json │ └── StatusMenuTemplate.imageset │ │ ├── Contents.json │ │ ├── StatusMenuTemplate.png │ │ └── StatusMenuTemplate@2x.png ├── AxisElementBinding.h ├── AxisElementBinding.m ├── Base.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── ButtonElementBinding.h ├── ButtonElementBinding.m ├── ElementBinding.h ├── ElementBinding.m ├── GamepadManager.h ├── GamepadManager.m ├── HatSwitchElementBinding.h ├── HatSwitchElementBinding.m ├── Info.plist ├── PresetMenuItem.h ├── PresetMenuItem.m ├── StartAtLoginController.h ├── StartAtLoginController.m ├── main.m └── nl.lproj │ ├── Localizable.strings │ └── MainMenu.strings ├── LICENSE ├── Makefile ├── README.md └── Resources ├── Device Profiles ├── PlayStation 4 Controller.plist ├── SteelSeries Nimbus.plist └── Xbox 360 Controller.plist ├── Graphics ├── AppIcon.ai └── StatusMenuTemplate.eps └── Presets ├── Broforce - Player 1.plist ├── Broforce - Player 2.plist ├── Generic.plist └── Super Meat Boy.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | *.xcworkspace 15 | !default.xcworkspace 16 | xcuserdata 17 | profile 18 | *.moved-aside 19 | DerivedData/ 20 | 21 | # Makefile products 22 | Gamepad Menu.app 23 | Gamepad Menu.zip 24 | -------------------------------------------------------------------------------- /Gamepad Menu Helper/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Gamepad Menu Helper 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Gamepad Menu Helper/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Gamepad Menu Helper 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (void)applicationWillFinishLaunching:(NSNotification *)notification { 14 | NSString *appPath = [[[[[[NSBundle mainBundle] bundlePath] 15 | stringByDeletingLastPathComponent] 16 | stringByDeletingLastPathComponent] 17 | stringByDeletingLastPathComponent] 18 | stringByDeletingLastPathComponent]; 19 | [[NSWorkspace sharedWorkspace] launchApplication:appPath]; 20 | [NSApp terminate:nil]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Gamepad Menu Helper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Robbert Klarenbeek. All rights reserved. 29 | NSPrincipalClass 30 | NSApplication 31 | LSUIElement 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Gamepad Menu Helper/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Gamepad Menu Helper 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, const char * argv[]) { 13 | AppDelegate *delegate = [[AppDelegate alloc] init]; 14 | [[NSApplication sharedApplication] setDelegate:delegate]; 15 | [NSApp run]; 16 | } 17 | -------------------------------------------------------------------------------- /Gamepad Menu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5E4346631C4ACF5D00B26F64 /* Device Profiles in Resources */ = {isa = PBXBuildFile; fileRef = 5E4346611C4ACF5D00B26F64 /* Device Profiles */; }; 11 | 5E4346641C4ACF5D00B26F64 /* Presets in Resources */ = {isa = PBXBuildFile; fileRef = 5E4346621C4ACF5D00B26F64 /* Presets */; }; 12 | 5E5B5E7A1C497A840099226A /* ButtonElementBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5B5E791C497A840099226A /* ButtonElementBinding.m */; }; 13 | 5E5B5E7D1C497A8F0099226A /* AxisElementBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5B5E7C1C497A8F0099226A /* AxisElementBinding.m */; }; 14 | 5E5B5E801C497A9E0099226A /* HatSwitchElementBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5B5E7F1C497A9E0099226A /* HatSwitchElementBinding.m */; }; 15 | 5E5B5E831C4987B80099226A /* ElementBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5B5E821C4987B80099226A /* ElementBinding.m */; }; 16 | 5E5D0D4A1C457A4600FE986C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D0D491C457A4600FE986C /* AppDelegate.m */; }; 17 | 5E5D0D4D1C457A4600FE986C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D0D4C1C457A4600FE986C /* main.m */; }; 18 | 5E5D0D4F1C457A4600FE986C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E5D0D4E1C457A4600FE986C /* Assets.xcassets */; }; 19 | 5E5D0D521C457A4600FE986C /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5E5D0D501C457A4600FE986C /* MainMenu.xib */; }; 20 | 5E5D0D5B1C457AF200FE986C /* StartAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D0D5A1C457AF200FE986C /* StartAtLoginController.m */; }; 21 | 5E5D0D651C45806D00FE986C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D0D641C45806D00FE986C /* AppDelegate.m */; }; 22 | 5E5D0D681C45806D00FE986C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D0D671C45806D00FE986C /* main.m */; }; 23 | 5E5D0D721C45809E00FE986C /* Gamepad Menu Helper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5E5D0D611C45806D00FE986C /* Gamepad Menu Helper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24 | 5EF2702B1C46ECAA0035BC2D /* GamepadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EF2702A1C46ECAA0035BC2D /* GamepadManager.m */; }; 25 | 5EF2702E1C46FF990035BC2D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EF270301C46FF990035BC2D /* Localizable.strings */; }; 26 | 5EF270361C4705CE0035BC2D /* PresetMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EF270351C4705CE0035BC2D /* PresetMenuItem.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 5EB27CDF1C4BD5E700ADA1D2 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 5E5D0D3D1C457A4600FE986C /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 5E5D0D601C45806D00FE986C; 35 | remoteInfo = "Gamepad Menu Helper"; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXCopyFilesBuildPhase section */ 40 | 5E5D0D5C1C45802800FE986C /* CopyFiles */ = { 41 | isa = PBXCopyFilesBuildPhase; 42 | buildActionMask = 2147483647; 43 | dstPath = Contents/Library/LoginItems; 44 | dstSubfolderSpec = 1; 45 | files = ( 46 | 5E5D0D721C45809E00FE986C /* Gamepad Menu Helper.app in CopyFiles */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXCopyFilesBuildPhase section */ 51 | 52 | /* Begin PBXFileReference section */ 53 | 5E4346611C4ACF5D00B26F64 /* Device Profiles */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Device Profiles"; path = "Resources/Device Profiles"; sourceTree = SOURCE_ROOT; }; 54 | 5E4346621C4ACF5D00B26F64 /* Presets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Presets; path = Resources/Presets; sourceTree = SOURCE_ROOT; }; 55 | 5E5B5E781C497A840099226A /* ButtonElementBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ButtonElementBinding.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 56 | 5E5B5E791C497A840099226A /* ButtonElementBinding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ButtonElementBinding.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 57 | 5E5B5E7B1C497A8F0099226A /* AxisElementBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxisElementBinding.h; sourceTree = ""; }; 58 | 5E5B5E7C1C497A8F0099226A /* AxisElementBinding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AxisElementBinding.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 59 | 5E5B5E7E1C497A9E0099226A /* HatSwitchElementBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HatSwitchElementBinding.h; sourceTree = ""; }; 60 | 5E5B5E7F1C497A9E0099226A /* HatSwitchElementBinding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HatSwitchElementBinding.m; sourceTree = ""; }; 61 | 5E5B5E811C4987B80099226A /* ElementBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ElementBinding.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 62 | 5E5B5E821C4987B80099226A /* ElementBinding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ElementBinding.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 63 | 5E5D0D451C457A4600FE986C /* Gamepad Menu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Gamepad Menu.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 5E5D0D481C457A4600FE986C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 65 | 5E5D0D491C457A4600FE986C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 66 | 5E5D0D4C1C457A4600FE986C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 67 | 5E5D0D4E1C457A4600FE986C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 68 | 5E5D0D511C457A4600FE986C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 69 | 5E5D0D531C457A4600FE986C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | 5E5D0D591C457AF200FE986C /* StartAtLoginController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StartAtLoginController.h; sourceTree = ""; }; 71 | 5E5D0D5A1C457AF200FE986C /* StartAtLoginController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StartAtLoginController.m; sourceTree = ""; }; 72 | 5E5D0D611C45806D00FE986C /* Gamepad Menu Helper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Gamepad Menu Helper.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 5E5D0D631C45806D00FE986C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 74 | 5E5D0D641C45806D00FE986C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 75 | 5E5D0D671C45806D00FE986C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 76 | 5E5D0D6E1C45806D00FE986C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | 5EF270261C46DD060035BC2D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/MainMenu.strings; sourceTree = ""; }; 78 | 5EF270291C46ECAA0035BC2D /* GamepadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GamepadManager.h; sourceTree = ""; }; 79 | 5EF2702A1C46ECAA0035BC2D /* GamepadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GamepadManager.m; sourceTree = ""; }; 80 | 5EF270311C46FF9C0035BC2D /* Base */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 81 | 5EF270331C4701400035BC2D /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 82 | 5EF270341C4705CE0035BC2D /* PresetMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresetMenuItem.h; sourceTree = ""; }; 83 | 5EF270351C4705CE0035BC2D /* PresetMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresetMenuItem.m; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 5E5D0D421C457A4600FE986C /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | 5E5D0D5E1C45806D00FE986C /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 5E4346601C4ACF3900B26F64 /* Resources */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 5E5D0D4E1C457A4600FE986C /* Assets.xcassets */, 108 | 5EF270301C46FF990035BC2D /* Localizable.strings */, 109 | 5E5D0D501C457A4600FE986C /* MainMenu.xib */, 110 | 5E4346611C4ACF5D00B26F64 /* Device Profiles */, 111 | 5E4346621C4ACF5D00B26F64 /* Presets */, 112 | ); 113 | name = Resources; 114 | sourceTree = ""; 115 | }; 116 | 5E5B5E771C497A6A0099226A /* ElementMapping */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 5E5B5E811C4987B80099226A /* ElementBinding.h */, 120 | 5E5B5E821C4987B80099226A /* ElementBinding.m */, 121 | 5E5B5E7B1C497A8F0099226A /* AxisElementBinding.h */, 122 | 5E5B5E7C1C497A8F0099226A /* AxisElementBinding.m */, 123 | 5E5B5E781C497A840099226A /* ButtonElementBinding.h */, 124 | 5E5B5E791C497A840099226A /* ButtonElementBinding.m */, 125 | 5E5B5E7E1C497A9E0099226A /* HatSwitchElementBinding.h */, 126 | 5E5B5E7F1C497A9E0099226A /* HatSwitchElementBinding.m */, 127 | ); 128 | name = ElementMapping; 129 | sourceTree = ""; 130 | }; 131 | 5E5D0D3C1C457A4600FE986C = { 132 | isa = PBXGroup; 133 | children = ( 134 | 5E5D0D471C457A4600FE986C /* Gamepad Menu */, 135 | 5E5D0D621C45806D00FE986C /* Gamepad Menu Helper */, 136 | 5E5D0D461C457A4600FE986C /* Products */, 137 | ); 138 | sourceTree = ""; 139 | }; 140 | 5E5D0D461C457A4600FE986C /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 5E5D0D451C457A4600FE986C /* Gamepad Menu.app */, 144 | 5E5D0D611C45806D00FE986C /* Gamepad Menu Helper.app */, 145 | ); 146 | name = Products; 147 | sourceTree = ""; 148 | }; 149 | 5E5D0D471C457A4600FE986C /* Gamepad Menu */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 5E5D0D4C1C457A4600FE986C /* main.m */, 153 | 5E5D0D531C457A4600FE986C /* Info.plist */, 154 | 5E5D0D481C457A4600FE986C /* AppDelegate.h */, 155 | 5E5D0D491C457A4600FE986C /* AppDelegate.m */, 156 | 5EF270291C46ECAA0035BC2D /* GamepadManager.h */, 157 | 5EF2702A1C46ECAA0035BC2D /* GamepadManager.m */, 158 | 5EF270341C4705CE0035BC2D /* PresetMenuItem.h */, 159 | 5EF270351C4705CE0035BC2D /* PresetMenuItem.m */, 160 | 5E5D0D591C457AF200FE986C /* StartAtLoginController.h */, 161 | 5E5D0D5A1C457AF200FE986C /* StartAtLoginController.m */, 162 | 5E5B5E771C497A6A0099226A /* ElementMapping */, 163 | 5E4346601C4ACF3900B26F64 /* Resources */, 164 | ); 165 | path = "Gamepad Menu"; 166 | sourceTree = ""; 167 | }; 168 | 5E5D0D621C45806D00FE986C /* Gamepad Menu Helper */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 5E5D0D671C45806D00FE986C /* main.m */, 172 | 5E5D0D6E1C45806D00FE986C /* Info.plist */, 173 | 5E5D0D631C45806D00FE986C /* AppDelegate.h */, 174 | 5E5D0D641C45806D00FE986C /* AppDelegate.m */, 175 | ); 176 | path = "Gamepad Menu Helper"; 177 | sourceTree = ""; 178 | }; 179 | /* End PBXGroup section */ 180 | 181 | /* Begin PBXNativeTarget section */ 182 | 5E5D0D441C457A4600FE986C /* Gamepad Menu */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 5E5D0D561C457A4600FE986C /* Build configuration list for PBXNativeTarget "Gamepad Menu" */; 185 | buildPhases = ( 186 | 5E5D0D411C457A4600FE986C /* Sources */, 187 | 5E5D0D421C457A4600FE986C /* Frameworks */, 188 | 5E5D0D431C457A4600FE986C /* Resources */, 189 | 5E5D0D5C1C45802800FE986C /* CopyFiles */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | 5EB27CE01C4BD5E700ADA1D2 /* PBXTargetDependency */, 195 | ); 196 | name = "Gamepad Menu"; 197 | productName = "Gamepad Menu"; 198 | productReference = 5E5D0D451C457A4600FE986C /* Gamepad Menu.app */; 199 | productType = "com.apple.product-type.application"; 200 | }; 201 | 5E5D0D601C45806D00FE986C /* Gamepad Menu Helper */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = 5E5D0D6F1C45806D00FE986C /* Build configuration list for PBXNativeTarget "Gamepad Menu Helper" */; 204 | buildPhases = ( 205 | 5E5D0D5D1C45806D00FE986C /* Sources */, 206 | 5E5D0D5E1C45806D00FE986C /* Frameworks */, 207 | 5E5D0D5F1C45806D00FE986C /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = "Gamepad Menu Helper"; 214 | productName = "Gamepad Menu Helper"; 215 | productReference = 5E5D0D611C45806D00FE986C /* Gamepad Menu Helper.app */; 216 | productType = "com.apple.product-type.application"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | 5E5D0D3D1C457A4600FE986C /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastUpgradeCheck = 0720; 225 | ORGANIZATIONNAME = "Robbert Klarenbeek"; 226 | TargetAttributes = { 227 | 5E5D0D441C457A4600FE986C = { 228 | CreatedOnToolsVersion = 7.2; 229 | }; 230 | 5E5D0D601C45806D00FE986C = { 231 | CreatedOnToolsVersion = 7.2; 232 | }; 233 | }; 234 | }; 235 | buildConfigurationList = 5E5D0D401C457A4600FE986C /* Build configuration list for PBXProject "Gamepad Menu" */; 236 | compatibilityVersion = "Xcode 3.2"; 237 | developmentRegion = English; 238 | hasScannedForEncodings = 0; 239 | knownRegions = ( 240 | en, 241 | Base, 242 | nl, 243 | ); 244 | mainGroup = 5E5D0D3C1C457A4600FE986C; 245 | productRefGroup = 5E5D0D461C457A4600FE986C /* Products */; 246 | projectDirPath = ""; 247 | projectRoot = ""; 248 | targets = ( 249 | 5E5D0D441C457A4600FE986C /* Gamepad Menu */, 250 | 5E5D0D601C45806D00FE986C /* Gamepad Menu Helper */, 251 | ); 252 | }; 253 | /* End PBXProject section */ 254 | 255 | /* Begin PBXResourcesBuildPhase section */ 256 | 5E5D0D431C457A4600FE986C /* Resources */ = { 257 | isa = PBXResourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 5E4346641C4ACF5D00B26F64 /* Presets in Resources */, 261 | 5EF2702E1C46FF990035BC2D /* Localizable.strings in Resources */, 262 | 5E5D0D4F1C457A4600FE986C /* Assets.xcassets in Resources */, 263 | 5E5D0D521C457A4600FE986C /* MainMenu.xib in Resources */, 264 | 5E4346631C4ACF5D00B26F64 /* Device Profiles in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | 5E5D0D5F1C45806D00FE986C /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | /* End PBXResourcesBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 5E5D0D411C457A4600FE986C /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 5E5D0D4D1C457A4600FE986C /* main.m in Sources */, 283 | 5E5B5E831C4987B80099226A /* ElementBinding.m in Sources */, 284 | 5E5B5E801C497A9E0099226A /* HatSwitchElementBinding.m in Sources */, 285 | 5EF2702B1C46ECAA0035BC2D /* GamepadManager.m in Sources */, 286 | 5E5D0D4A1C457A4600FE986C /* AppDelegate.m in Sources */, 287 | 5E5B5E7A1C497A840099226A /* ButtonElementBinding.m in Sources */, 288 | 5EF270361C4705CE0035BC2D /* PresetMenuItem.m in Sources */, 289 | 5E5B5E7D1C497A8F0099226A /* AxisElementBinding.m in Sources */, 290 | 5E5D0D5B1C457AF200FE986C /* StartAtLoginController.m in Sources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | 5E5D0D5D1C45806D00FE986C /* Sources */ = { 295 | isa = PBXSourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | 5E5D0D681C45806D00FE986C /* main.m in Sources */, 299 | 5E5D0D651C45806D00FE986C /* AppDelegate.m in Sources */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | /* End PBXSourcesBuildPhase section */ 304 | 305 | /* Begin PBXTargetDependency section */ 306 | 5EB27CE01C4BD5E700ADA1D2 /* PBXTargetDependency */ = { 307 | isa = PBXTargetDependency; 308 | target = 5E5D0D601C45806D00FE986C /* Gamepad Menu Helper */; 309 | targetProxy = 5EB27CDF1C4BD5E700ADA1D2 /* PBXContainerItemProxy */; 310 | }; 311 | /* End PBXTargetDependency section */ 312 | 313 | /* Begin PBXVariantGroup section */ 314 | 5E5D0D501C457A4600FE986C /* MainMenu.xib */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 5E5D0D511C457A4600FE986C /* Base */, 318 | 5EF270261C46DD060035BC2D /* nl */, 319 | ); 320 | name = MainMenu.xib; 321 | sourceTree = ""; 322 | }; 323 | 5EF270301C46FF990035BC2D /* Localizable.strings */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | 5EF270311C46FF9C0035BC2D /* Base */, 327 | 5EF270331C4701400035BC2D /* nl */, 328 | ); 329 | name = Localizable.strings; 330 | sourceTree = ""; 331 | }; 332 | /* End PBXVariantGroup section */ 333 | 334 | /* Begin XCBuildConfiguration section */ 335 | 5E5D0D541C457A4600FE986C /* Debug */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ALWAYS_SEARCH_USER_PATHS = NO; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | CODE_SIGN_IDENTITY = "-"; 353 | COPY_PHASE_STRIP = NO; 354 | DEBUG_INFORMATION_FORMAT = dwarf; 355 | ENABLE_STRICT_OBJC_MSGSEND = YES; 356 | ENABLE_TESTABILITY = YES; 357 | GCC_C_LANGUAGE_STANDARD = gnu99; 358 | GCC_DYNAMIC_NO_PIC = NO; 359 | GCC_NO_COMMON_BLOCKS = YES; 360 | GCC_OPTIMIZATION_LEVEL = 0; 361 | GCC_PREPROCESSOR_DEFINITIONS = ( 362 | "DEBUG=1", 363 | "$(inherited)", 364 | ); 365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 367 | GCC_WARN_UNDECLARED_SELECTOR = YES; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 369 | GCC_WARN_UNUSED_FUNCTION = YES; 370 | GCC_WARN_UNUSED_VARIABLE = YES; 371 | MACOSX_DEPLOYMENT_TARGET = 10.11; 372 | MTL_ENABLE_DEBUG_INFO = YES; 373 | ONLY_ACTIVE_ARCH = YES; 374 | SDKROOT = macosx; 375 | }; 376 | name = Debug; 377 | }; 378 | 5E5D0D551C457A4600FE986C /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | CODE_SIGN_IDENTITY = "-"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | MACOSX_DEPLOYMENT_TARGET = 10.11; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = macosx; 411 | }; 412 | name = Release; 413 | }; 414 | 5E5D0D571C457A4600FE986C /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 418 | COMBINE_HIDPI_IMAGES = YES; 419 | INFOPLIST_FILE = "Gamepad Menu/Info.plist"; 420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 421 | PRODUCT_BUNDLE_IDENTIFIER = "nl.lapulapu.Gamepad-Menu"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | }; 424 | name = Debug; 425 | }; 426 | 5E5D0D581C457A4600FE986C /* Release */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | COMBINE_HIDPI_IMAGES = YES; 431 | INFOPLIST_FILE = "Gamepad Menu/Info.plist"; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = "nl.lapulapu.Gamepad-Menu"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | }; 436 | name = Release; 437 | }; 438 | 5E5D0D701C45806D00FE986C /* Debug */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | COMBINE_HIDPI_IMAGES = YES; 443 | INFOPLIST_FILE = "Gamepad Menu Helper/Info.plist"; 444 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 445 | PRODUCT_BUNDLE_IDENTIFIER = "nl.lapulapu.Gamepad-Menu-Helper"; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | }; 448 | name = Debug; 449 | }; 450 | 5E5D0D711C45806D00FE986C /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 454 | COMBINE_HIDPI_IMAGES = YES; 455 | INFOPLIST_FILE = "Gamepad Menu Helper/Info.plist"; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 457 | PRODUCT_BUNDLE_IDENTIFIER = "nl.lapulapu.Gamepad-Menu-Helper"; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | }; 460 | name = Release; 461 | }; 462 | /* End XCBuildConfiguration section */ 463 | 464 | /* Begin XCConfigurationList section */ 465 | 5E5D0D401C457A4600FE986C /* Build configuration list for PBXProject "Gamepad Menu" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | 5E5D0D541C457A4600FE986C /* Debug */, 469 | 5E5D0D551C457A4600FE986C /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | 5E5D0D561C457A4600FE986C /* Build configuration list for PBXNativeTarget "Gamepad Menu" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | 5E5D0D571C457A4600FE986C /* Debug */, 478 | 5E5D0D581C457A4600FE986C /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | 5E5D0D6F1C45806D00FE986C /* Build configuration list for PBXNativeTarget "Gamepad Menu Helper" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 5E5D0D701C45806D00FE986C /* Debug */, 487 | 5E5D0D711C45806D00FE986C /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | /* End XCConfigurationList section */ 493 | }; 494 | rootObject = 5E5D0D3D1C457A4600FE986C /* Project object */; 495 | } 496 | -------------------------------------------------------------------------------- /Gamepad Menu/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | @property (assign) IBOutlet NSMenu *menu; 13 | @property (nonatomic, strong) NSStatusItem *statusItem; 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Gamepad Menu/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "ElementBinding.h" 12 | #import "GamepadManager.h" 13 | #import "PresetMenuItem.h" 14 | 15 | static NSString *const kStatusMenuTemplateName = @"StatusMenuTemplate"; 16 | 17 | static NSString *const kDeviceProfilesDirectory = @"Device Profiles"; 18 | static NSString *const kPresetsDirectory = @"Presets"; 19 | 20 | static NSString *const kEntityTitleKey = @"Title"; 21 | static NSString *const kDeviceProfileIdentifierKey = @"Identifier"; 22 | static NSString *const kDeviceProfileIdentifierVendorKey = @"Vendor"; 23 | static NSString *const kDeviceProfileIdentifierProductKey = @"Product"; 24 | 25 | @interface AppDelegate () { 26 | NSArray *_deviceProfiles; 27 | NSArray *_presets; 28 | 29 | GamepadManager *_gamepadManager; 30 | NSMutableDictionary *_connectedDevices; 31 | NSMutableDictionary *_elementBindings; 32 | } 33 | 34 | @end 35 | 36 | @implementation AppDelegate 37 | 38 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 39 | _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; 40 | _statusItem.menu = self.menu; 41 | _statusItem.image = [NSImage imageNamed:kStatusMenuTemplateName]; 42 | _statusItem.highlightMode = YES; 43 | 44 | _connectedDevices = [NSMutableDictionary new]; 45 | _elementBindings = [NSMutableDictionary new]; 46 | 47 | _deviceProfiles = [self loadPlistsFromDirectory:kDeviceProfilesDirectory]; 48 | _presets = [self loadPlistsFromDirectory:kPresetsDirectory]; 49 | 50 | _gamepadManager = [GamepadManager new]; 51 | _gamepadManager.delegate = self; 52 | } 53 | 54 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 55 | [[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem]; 56 | } 57 | 58 | - (NSArray *)loadPlistsFromDirectory:(NSString *)directory { 59 | NSMutableArray *items = [NSMutableArray new]; 60 | NSBundle *bundle = [NSBundle mainBundle]; 61 | NSArray *files = [[bundle pathsForResourcesOfType:@"plist" inDirectory:directory] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; 62 | for (NSString *file in files) { 63 | NSMutableDictionary *item = [NSMutableDictionary dictionaryWithContentsOfFile:file]; 64 | item[kEntityTitleKey] = [[file lastPathComponent] stringByDeletingPathExtension]; 65 | [items addObject:item]; 66 | } 67 | return items; 68 | } 69 | 70 | - (NSMenuItem *)menuItemForDevice:(IOHIDDeviceRef)device withDeviceProfile:(NSDictionary *)deviceProfile { 71 | NSMenu *menu = [NSMenu new]; 72 | 73 | for (NSDictionary *preset in _presets) { 74 | PresetMenuItem *menuItem = [[PresetMenuItem alloc] initWithDevice:device deviceProfile:deviceProfile preset:preset]; 75 | menuItem.delegate = self; 76 | [menu addItem:menuItem]; 77 | } 78 | 79 | [menu addItem:[NSMenuItem separatorItem]]; 80 | PresetMenuItem *disabledMenuItem = [PresetMenuItem disabledMenuItem:device]; 81 | disabledMenuItem.delegate = self; 82 | [menu addItem:disabledMenuItem]; 83 | 84 | NSMenuItem *menuItem = [NSMenuItem new]; 85 | menuItem.title = deviceProfile[kEntityTitleKey]; 86 | menuItem.submenu = menu; 87 | return menuItem; 88 | } 89 | 90 | - (NSMenuItem *)menuItemForUnknownDeviceWithVendor:(uint32_t)vendorId product:(uint32_t)productId { 91 | NSMenu *menu = [NSMenu new]; 92 | 93 | NSMenuItem *infoItem = [NSMenuItem new]; 94 | infoItem.title = NSLocalizedString(@"No profile exists for this device", @"Unknown device info text"); 95 | infoItem.enabled = NO; 96 | [menu addItem:infoItem]; 97 | [menu addItem:[NSMenuItem separatorItem]]; 98 | 99 | NSMenuItem *vendorIdItem = [NSMenuItem new]; 100 | vendorIdItem.title = [NSString stringWithFormat:@"Vendor ID: 0x%02x", vendorId]; 101 | vendorIdItem.enabled = NO; 102 | [menu addItem:vendorIdItem]; 103 | 104 | NSMenuItem *productIdItem = [NSMenuItem new]; 105 | productIdItem.title = [NSString stringWithFormat:@"Product ID: 0x%02x", productId]; 106 | productIdItem.enabled = NO; 107 | [menu addItem:productIdItem]; 108 | 109 | NSMenuItem *menuItem = [NSMenuItem new]; 110 | menuItem.title = NSLocalizedString(@"Unknown Gamepad", @"Name for unknown device"); 111 | menuItem.submenu = menu; 112 | return menuItem; 113 | } 114 | 115 | #pragma mark - PresetMenuItemDelegate 116 | 117 | - (void)clearElementBindingsForDevice:(IOHIDDeviceRef)device { 118 | for (id elementHash in [_elementBindings allKeys]) { 119 | ElementBinding *elementBinding = _elementBindings[elementHash]; 120 | if (elementBinding.device == device) [_elementBindings removeObjectForKey:elementHash]; 121 | } 122 | } 123 | 124 | - (void)addElementBinding:(ElementBinding *)elementBinding { 125 | id elementHash = [NSValue valueWithPointer:elementBinding.element]; 126 | _elementBindings[elementHash] = elementBinding; 127 | } 128 | 129 | #pragma mark - GamepadManagerDelegate 130 | 131 | - (void)deviceDidConnect:(IOHIDDeviceRef)device { 132 | uint32_t vendorId = 0; 133 | CFNumberGetValue(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberSInt32Type, &vendorId); 134 | 135 | uint32_t productId = 0; 136 | CFNumberGetValue(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)), kCFNumberSInt32Type, &productId); 137 | 138 | NSDictionary *deviceProfile; 139 | for (NSDictionary *deviceProfileCandidate in _deviceProfiles) { 140 | id identifier = deviceProfileCandidate[kDeviceProfileIdentifierKey]; 141 | NSArray *identifierCandidates; 142 | if ([identifier isKindOfClass:[NSDictionary class]]) { 143 | identifierCandidates = @[identifier]; 144 | } else if ([identifier isKindOfClass:[NSArray class]]) { 145 | identifierCandidates = identifier; 146 | } else { 147 | continue; 148 | } 149 | for (NSDictionary *identifierCandidate in identifierCandidates) { 150 | if (vendorId != [identifierCandidate[kDeviceProfileIdentifierVendorKey] unsignedIntegerValue]) continue; 151 | if (productId != [identifierCandidate[kDeviceProfileIdentifierProductKey] unsignedIntegerValue]) continue; 152 | deviceProfile = deviceProfileCandidate; 153 | break; 154 | } 155 | if (deviceProfile) break; 156 | } 157 | 158 | NSMenuItem *menuItem; 159 | if (!deviceProfile) { 160 | menuItem = [self menuItemForUnknownDeviceWithVendor:vendorId product:productId]; 161 | deviceProfile = @{}; 162 | } else { 163 | menuItem = [self menuItemForDevice:device withDeviceProfile:deviceProfile]; 164 | } 165 | menuItem.tag = (NSInteger)device; 166 | 167 | NSMenuItem *placeholder = nil; 168 | BOOL didInsert = NO; 169 | for (NSMenuItem *existingMenuItem in _menu.itemArray) { 170 | if (existingMenuItem.isSeparatorItem) break; 171 | if (existingMenuItem.tag == 0) { 172 | placeholder = existingMenuItem; 173 | continue; 174 | } 175 | if (didInsert) continue; 176 | NSComparisonResult compare = [existingMenuItem.title caseInsensitiveCompare:menuItem.title]; 177 | if (compare == NSOrderedAscending) continue; 178 | if (compare == NSOrderedSame && menuItem.tag > existingMenuItem.tag) continue; 179 | [_menu insertItem:menuItem atIndex:[_menu indexOfItem:existingMenuItem]]; 180 | didInsert = YES; 181 | } 182 | 183 | if (!didInsert) [_menu insertItem:menuItem atIndex:[_menu indexOfItem:placeholder]]; 184 | placeholder.hidden = YES; 185 | 186 | _connectedDevices[[NSValue valueWithPointer:device]] = deviceProfile; 187 | } 188 | 189 | - (void)deviceDidDisconnect:(IOHIDDeviceRef)device { 190 | [_connectedDevices removeObjectForKey:[NSValue valueWithPointer:device]]; 191 | [self clearElementBindingsForDevice:device]; 192 | 193 | NSMenuItem *placeholder = nil; 194 | for (NSMenuItem *existingMenuItem in _menu.itemArray) { 195 | if (existingMenuItem.isSeparatorItem) break; 196 | if (existingMenuItem.tag == 0) placeholder = existingMenuItem; 197 | if (existingMenuItem.tag == (NSInteger)device) [_menu removeItem:existingMenuItem]; 198 | } 199 | 200 | placeholder.hidden = _connectedDevices.count > 0; 201 | } 202 | 203 | - (void)deviceDidChange:(IOHIDValueRef)value { 204 | IOHIDElementRef element = IOHIDValueGetElement(value); 205 | id elementHash = [NSValue valueWithPointer:element]; 206 | ElementBinding *elementBinding = _elementBindings[elementHash]; 207 | [elementBinding updateValue:IOHIDValueGetIntegerValue(value)]; 208 | } 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-128x128.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-128x128@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-16x16.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-16x16@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-256x256.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-256x256@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-32x32.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-32x32@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "AppIcon-16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "AppIcon-16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "AppIcon-32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "AppIcon-32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "AppIcon-128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "AppIcon-128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "AppIcon-256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "AppIcon-256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "AppIcon-512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "AppIcon-512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/StatusMenuTemplate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "StatusMenuTemplate.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "filename" : "StatusMenuTemplate@2x.png", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/StatusMenuTemplate.imageset/StatusMenuTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/StatusMenuTemplate.imageset/StatusMenuTemplate.png -------------------------------------------------------------------------------- /Gamepad Menu/Assets.xcassets/StatusMenuTemplate.imageset/StatusMenuTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Assets.xcassets/StatusMenuTemplate.imageset/StatusMenuTemplate@2x.png -------------------------------------------------------------------------------- /Gamepad Menu/AxisElementBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // AxisElementBinding.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "ElementBinding.h" 10 | 11 | @interface AxisElementBinding : ElementBinding 12 | @end 13 | -------------------------------------------------------------------------------- /Gamepad Menu/AxisElementBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // AxisElementBinding.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "AxisElementBinding.h" 10 | 11 | static NSString *const kAxisElementBindingOptionsThresholdKey = @"Threshold"; 12 | static NSString *const kAxisElementBindingOptionsThresholdStickinessKey = @"ThresholdStickiness"; 13 | 14 | enum { 15 | AxisElementBindingRegionNone = 0, 16 | AxisElementBindingRegionLow = 1 << 0, 17 | AxisElementBindingRegionHigh = 1 << 1, 18 | }; 19 | typedef NSUInteger AxisElementBindingRegion; 20 | 21 | @interface AxisElementBinding () { 22 | CFIndex _thresholdA; 23 | CFIndex _thresholdB; 24 | CFIndex _thresholdC; 25 | CFIndex _thresholdD; 26 | CFIndex _thresholdE; 27 | CFIndex _thresholdF; 28 | 29 | BOOL _isStickingLow; 30 | BOOL _isStickingHigh; 31 | AxisElementBindingRegion _previousState; 32 | } 33 | 34 | @end 35 | 36 | @implementation AxisElementBinding 37 | 38 | - (id)initWithElement:(IOHIDElementRef)element keyBindings:(NSArray *)keyBindings options:(NSDictionary *)options { 39 | // An element of type "Axis" should have 2 key bindings 40 | keyBindings = [keyBindings subarrayWithRange:NSMakeRange(0, 2)]; 41 | 42 | self = [super initWithElement:element keyBindings:keyBindings options:options]; 43 | if (self) { 44 | CFIndex elementMin = IOHIDElementGetLogicalMin(element); 45 | CFIndex elementMax = IOHIDElementGetLogicalMax(element); 46 | CFIndex elementCenter = elementMin + ceil((elementMax - elementMin) / 2.0); 47 | CFIndex rangeLow = elementCenter - elementMin; 48 | CFIndex rangeHigh = elementMax - elementCenter; 49 | 50 | NSNumber *threshold = options[kAxisElementBindingOptionsThresholdKey]; 51 | if (!threshold) threshold = @(0.4); 52 | 53 | NSNumber *stickiness = options[kAxisElementBindingOptionsThresholdStickinessKey]; 54 | if (!stickiness) stickiness = @(0.0); 55 | 56 | _thresholdA = elementCenter + 1 - ceil(rangeLow * (threshold.doubleValue + stickiness.doubleValue / 2)); 57 | _thresholdB = elementCenter + 1 - ceil(rangeLow * threshold.doubleValue); 58 | _thresholdC = elementCenter + 1 - ceil(rangeLow * (threshold.doubleValue - stickiness.doubleValue / 2)); 59 | _thresholdD = elementCenter + 0 + ceil(rangeHigh * (threshold.doubleValue - stickiness.doubleValue / 2)); 60 | _thresholdE = elementCenter + 0 + ceil(rangeHigh * threshold.doubleValue); 61 | _thresholdF = elementCenter + 0 + ceil(rangeHigh * (threshold.doubleValue + stickiness.doubleValue / 2)); 62 | 63 | _thresholdA = MIN(MAX(_thresholdA, elementMin + 1), elementCenter); 64 | _thresholdB = MIN(MAX(_thresholdB, elementMin + 1), elementCenter); 65 | _thresholdC = MIN(MAX(_thresholdC, elementMin + 1), elementCenter); 66 | _thresholdD = MIN(MAX(_thresholdD, elementCenter + 1), elementMax); 67 | _thresholdE = MIN(MAX(_thresholdE, elementCenter + 1), elementMax); 68 | _thresholdF = MIN(MAX(_thresholdF, elementCenter + 1), elementMax); 69 | 70 | _isStickingLow = NO; 71 | _isStickingHigh = NO; 72 | _previousState = AxisElementBindingRegionNone; 73 | } 74 | return self; 75 | } 76 | 77 | - (NSUInteger)keyStatesForValue:(CFIndex)value { 78 | AxisElementBindingRegion state = AxisElementBindingRegionNone; 79 | 80 | if (value < _thresholdA) { 81 | state = AxisElementBindingRegionLow; 82 | _isStickingLow = _isStickingHigh = NO; 83 | } else if (value < _thresholdB) { 84 | state = _isStickingLow ? _previousState : AxisElementBindingRegionLow; 85 | if (state != _previousState) _isStickingLow = YES; 86 | _isStickingHigh = NO; 87 | } else if (value < _thresholdC) { 88 | state = _isStickingLow ? _previousState : AxisElementBindingRegionNone; 89 | if (state != _previousState) _isStickingLow = YES; 90 | _isStickingHigh = NO; 91 | } else if (value < _thresholdD) { 92 | state = AxisElementBindingRegionNone; 93 | _isStickingLow = _isStickingHigh = NO; 94 | } else if (value < _thresholdE) { 95 | state = _isStickingHigh ? _previousState : AxisElementBindingRegionNone; 96 | if (state != _previousState) _isStickingHigh = YES; 97 | _isStickingLow = NO; 98 | } else if (value < _thresholdF) { 99 | state = _isStickingHigh ? _previousState : AxisElementBindingRegionHigh; 100 | if (state != _previousState) _isStickingHigh = YES; 101 | _isStickingLow = NO; 102 | } else { 103 | state = AxisElementBindingRegionHigh; 104 | _isStickingLow = _isStickingHigh = NO; 105 | } 106 | 107 | _previousState = state; 108 | return state; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Gamepad Menu/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/Base.lproj/Localizable.strings -------------------------------------------------------------------------------- /Gamepad Menu/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 | -------------------------------------------------------------------------------- /Gamepad Menu/ButtonElementBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonElementBinding.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "ElementBinding.h" 10 | 11 | @interface ButtonElementBinding : ElementBinding 12 | @end 13 | -------------------------------------------------------------------------------- /Gamepad Menu/ButtonElementBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonElementBinding.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "ButtonElementBinding.h" 10 | 11 | static NSString *const kButtonElementBindingOptionsThresholdKey = @"Threshold"; 12 | static NSString *const kButtonElementBindingOptionsThresholdStickinessKey = @"ThresholdStickiness"; 13 | 14 | enum { 15 | ButtonElementBindingStateOff = 0, 16 | ButtonElementBindingStateOn = 1, 17 | }; 18 | typedef NSUInteger ButtonElementBindingState; 19 | 20 | @interface ButtonElementBinding () { 21 | CFIndex _thresholdA; 22 | CFIndex _thresholdB; 23 | CFIndex _thresholdC; 24 | 25 | BOOL _isSticking; 26 | ButtonElementBindingState _previousState; 27 | } 28 | 29 | @end 30 | 31 | @implementation ButtonElementBinding 32 | 33 | - (id)initWithElement:(IOHIDElementRef)element keyBindings:(NSArray *)keyBindings options:(NSDictionary *)options { 34 | // An element of type "Button" should have 1 key binding 35 | keyBindings = [keyBindings subarrayWithRange:NSMakeRange(0, 1)]; 36 | 37 | self = [super initWithElement:element keyBindings:keyBindings options:options]; 38 | if (self) { 39 | CFIndex elementMin = IOHIDElementGetLogicalMin(element); 40 | CFIndex elementMax = IOHIDElementGetLogicalMax(element); 41 | CFIndex range = elementMax - elementMin; 42 | 43 | NSNumber *threshold = options[kButtonElementBindingOptionsThresholdKey]; 44 | if (!threshold) threshold = @(0.8); 45 | 46 | NSNumber *stickiness = options[kButtonElementBindingOptionsThresholdStickinessKey]; 47 | if (!stickiness) stickiness = @(0.0); 48 | 49 | _thresholdA = elementMin + ceil(range * (threshold.doubleValue - stickiness.doubleValue / 2)); 50 | _thresholdB = elementMin + ceil(range * threshold.doubleValue); 51 | _thresholdC = elementMin + ceil(range * (threshold.doubleValue + stickiness.doubleValue / 2)); 52 | 53 | _thresholdA = MIN(MAX(_thresholdA, elementMin + 1), elementMax); 54 | _thresholdB = MIN(MAX(_thresholdB, elementMin + 1), elementMax); 55 | _thresholdC = MIN(MAX(_thresholdC, elementMin + 1), elementMax); 56 | 57 | _isSticking = NO; 58 | _previousState = ButtonElementBindingStateOff; 59 | } 60 | return self; 61 | } 62 | 63 | - (NSUInteger)keyStatesForValue:(CFIndex)value { 64 | ButtonElementBindingState state = ButtonElementBindingStateOff; 65 | 66 | if (value < _thresholdA) { 67 | _isSticking = NO; 68 | state = ButtonElementBindingStateOff; 69 | } else if (value < _thresholdB) { 70 | state = _isSticking ? _previousState : ButtonElementBindingStateOff; 71 | if (state != _previousState) _isSticking = YES; 72 | } else if (value < _thresholdC) { 73 | state = _isSticking ? _previousState : ButtonElementBindingStateOn; 74 | if (state != _previousState) _isSticking = YES; 75 | } else { 76 | _isSticking = NO; 77 | state = ButtonElementBindingStateOn; 78 | } 79 | 80 | _previousState = state; 81 | return state; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Gamepad Menu/ElementBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // ElementBinding.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface ElementBinding : NSObject 13 | - (id)initWithElement:(IOHIDElementRef)element keyBindings:(NSArray *)keyBindings options:(NSDictionary *)options; 14 | - (void)updateValue:(CFIndex)value; 15 | @property (nonatomic, readonly) IOHIDElementRef element; 16 | @property (nonatomic, readonly) IOHIDDeviceRef device; 17 | @property (nonatomic, readonly) BOOL isAssigned; 18 | @end 19 | -------------------------------------------------------------------------------- /Gamepad Menu/ElementBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // ElementBinding.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "ElementBinding.h" 10 | 11 | @interface ElementBinding () { 12 | NSArray *_keyBindings; 13 | NSUInteger _keyStates; 14 | } 15 | 16 | - (NSUInteger)keyStatesForValue:(CFIndex)value; 17 | 18 | @end 19 | 20 | @implementation ElementBinding 21 | 22 | - (id)initWithElement:(IOHIDElementRef)element keyBindings:(NSArray *)keyBindings options:(NSDictionary *)options { 23 | self = [super init]; 24 | if (self) { 25 | _element = element; 26 | _device = IOHIDElementGetDevice(element); 27 | _keyBindings = keyBindings; 28 | _keyStates = 0; 29 | 30 | _isAssigned = NO; 31 | for (id keyBinding in _keyBindings) { 32 | if ([keyBinding isKindOfClass:[NSNumber class]]) { 33 | _isAssigned = YES; 34 | break; 35 | } 36 | } 37 | } 38 | return self; 39 | } 40 | 41 | - (NSUInteger)keyStatesForValue:(CFIndex)value { 42 | // Override this in a subclass 43 | return 0; 44 | } 45 | 46 | - (void)updateValue:(CFIndex)value { 47 | if (!_isAssigned) return; 48 | 49 | NSUInteger newKeyStates = [self keyStatesForValue:value]; 50 | 51 | NSUInteger numberOfKeyBindings = _keyBindings.count; 52 | for (NSUInteger index = 0; index < numberOfKeyBindings; index++) { 53 | id keyBinding = _keyBindings[index]; 54 | if (![keyBinding isKindOfClass:[NSNumber class]]) continue; 55 | 56 | NSUInteger mask = 1 << index; 57 | BOOL isDownBefore = _keyStates & mask; 58 | BOOL isDownAfter = newKeyStates & mask; 59 | if (isDownBefore != isDownAfter) { 60 | [self keyboardAction:[keyBinding shortValue] pressDown:isDownAfter]; 61 | } 62 | } 63 | 64 | _keyStates = newKeyStates; 65 | } 66 | 67 | - (void)keyboardAction:(CGKeyCode)key pressDown:(BOOL)down { 68 | CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 69 | CGEventRef event = CGEventCreateKeyboardEvent(source, key, down); 70 | CGEventPost(kCGHIDEventTap, event); 71 | CFRelease(event); 72 | CFRelease(source); 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Gamepad Menu/GamepadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GamepadManager.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol GamepadManagerDelegate 13 | @optional 14 | - (void)deviceDidConnect:(IOHIDDeviceRef)device; 15 | - (void)deviceDidDisconnect:(IOHIDDeviceRef)device; 16 | - (void)deviceDidChange:(IOHIDValueRef)value; 17 | @end 18 | 19 | @interface GamepadManager : NSObject 20 | @property (nonatomic, weak) id delegate; 21 | @end 22 | -------------------------------------------------------------------------------- /Gamepad Menu/GamepadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GamepadManager.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "GamepadManager.h" 10 | 11 | @interface GamepadManager () { 12 | IOHIDManagerRef _hidManager; 13 | } 14 | 15 | @end 16 | 17 | @implementation GamepadManager 18 | 19 | - (id)init { 20 | if (self = [super init]) { 21 | _hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); 22 | 23 | NSDictionary *match = @{ 24 | @(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop), 25 | @(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_GamePad), 26 | }; 27 | 28 | IOHIDManagerSetDeviceMatching(_hidManager, (__bridge CFDictionaryRef)match); 29 | 30 | IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, connect, (__bridge void*)self); 31 | IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, disconnect, (__bridge void*)self); 32 | IOHIDManagerRegisterInputValueCallback(_hidManager, event, (__bridge void*)self); 33 | IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 34 | 35 | // Errors are ignored here 36 | IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone); 37 | } 38 | return self; 39 | } 40 | 41 | - (void)dealloc { 42 | IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); 43 | CFRelease(_hidManager); 44 | } 45 | 46 | #pragma mark - IOHIDManager callbacks 47 | 48 | static void connect(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { 49 | GamepadManager *self = (__bridge GamepadManager *)context; 50 | if (self.delegate && [self.delegate respondsToSelector:@selector(deviceDidConnect:)]) { 51 | [self.delegate deviceDidConnect:device]; 52 | } 53 | } 54 | 55 | static void disconnect(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { 56 | GamepadManager *self = (__bridge GamepadManager *)context; 57 | if (self.delegate && [self.delegate respondsToSelector:@selector(deviceDidDisconnect:)]) { 58 | [self.delegate deviceDidDisconnect:device]; 59 | } 60 | } 61 | 62 | static void event(void *context, IOReturn result, void *sender, IOHIDValueRef value) { 63 | GamepadManager *self = (__bridge GamepadManager *)context; 64 | if (self.delegate && [self.delegate respondsToSelector:@selector(deviceDidChange:)]) { 65 | [self.delegate deviceDidChange:value]; 66 | } 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Gamepad Menu/HatSwitchElementBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // HatSwitchElementBinding.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "ElementBinding.h" 10 | 11 | @interface HatSwitchElementBinding : ElementBinding 12 | @end 13 | -------------------------------------------------------------------------------- /Gamepad Menu/HatSwitchElementBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // HatSwitchElementBinding.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "HatSwitchElementBinding.h" 10 | 11 | enum { 12 | HatSwitchElementBindingRegionNone = 0, 13 | HatSwitchElementBindingRegionFirst = 1 << 0, 14 | HatSwitchElementBindingRegionSecond = 1 << 1, 15 | HatSwitchElementBindingRegionThird = 1 << 2, 16 | HatSwitchElementBindingRegionFourth = 1 << 3, 17 | }; 18 | typedef NSUInteger HatSwitchElementBindingRegion; 19 | 20 | @interface HatSwitchElementBinding () { 21 | double _scale; 22 | } 23 | 24 | @end 25 | 26 | @implementation HatSwitchElementBinding 27 | 28 | - (id)initWithElement:(IOHIDElementRef)element keyBindings:(NSArray *)keyBindings options:(NSDictionary *)options { 29 | // An element of type "Hat Switch" should have 4 key binding 30 | keyBindings = [keyBindings subarrayWithRange:NSMakeRange(0, 4)]; 31 | 32 | self = [super initWithElement:element keyBindings:keyBindings options:options]; 33 | if (self) { 34 | CFIndex elementMin = IOHIDElementGetLogicalMin(element); 35 | CFIndex elementMax = IOHIDElementGetLogicalMax(element); 36 | _scale = (elementMax - elementMin) / 7.0; 37 | } 38 | return self; 39 | } 40 | 41 | - (NSUInteger)keyStatesForValue:(CFIndex)value { 42 | switch((NSUInteger)(value / _scale + 0.5)) { 43 | case 0: return HatSwitchElementBindingRegionFirst; 44 | case 1: return HatSwitchElementBindingRegionFirst | HatSwitchElementBindingRegionSecond; 45 | case 2: return HatSwitchElementBindingRegionSecond; 46 | case 3: return HatSwitchElementBindingRegionSecond | HatSwitchElementBindingRegionThird; 47 | case 4: return HatSwitchElementBindingRegionThird; 48 | case 5: return HatSwitchElementBindingRegionThird | HatSwitchElementBindingRegionFourth; 49 | case 6: return HatSwitchElementBindingRegionFourth; 50 | case 7: return HatSwitchElementBindingRegionFourth | HatSwitchElementBindingRegionFirst; 51 | default: return HatSwitchElementBindingRegionNone; 52 | } 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Gamepad Menu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Robbert Klarenbeek. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | LSUIElement 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Gamepad Menu/PresetMenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // PresetMenuItem.h 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ElementBinding.h" 13 | 14 | @protocol PresetMenuItemDelegate 15 | - (void)clearElementBindingsForDevice:(IOHIDDeviceRef)device; 16 | - (void)addElementBinding:(ElementBinding *)elementBinding; 17 | @end 18 | 19 | @interface PresetMenuItem : NSMenuItem 20 | - (id)initWithDevice:(IOHIDDeviceRef)device deviceProfile:(NSDictionary *)deviceProfile preset:(NSDictionary *)preset; 21 | + (PresetMenuItem *)disabledMenuItem:(IOHIDDeviceRef)device; 22 | @property (getter=isDisabledMenuItem, readonly) BOOL disabledMenuItem; 23 | @property (nonatomic, weak) id delegate; 24 | @end 25 | -------------------------------------------------------------------------------- /Gamepad Menu/PresetMenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // PresetMenuItem.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import "PresetMenuItem.h" 10 | 11 | #import 12 | 13 | #import "AxisElementBinding.h" 14 | #import "ButtonElementBinding.h" 15 | #import "HatSwitchElementBinding.h" 16 | 17 | static NSString *const kDeviceProfileTitleKey = @"Title"; 18 | static NSString *const kDeviceProfileElementsKey = @"Elements"; 19 | static NSString *const kDeviceProfileElementTypeKey = @"Type"; 20 | static NSString *const kDeviceProfileElementBindingKey = @"Binding"; 21 | 22 | static NSString *const kElementTypeAxis = @"Axis"; 23 | static NSString *const kElementTypeButton = @"Button"; 24 | static NSString *const kElementTypeHatSwitch = @"Hat Switch"; 25 | 26 | @interface PresetMenuItem () { 27 | IOHIDDeviceRef _device; 28 | NSDictionary *_deviceProfile; 29 | NSDictionary *_preset; 30 | } 31 | 32 | @end 33 | 34 | @implementation PresetMenuItem 35 | 36 | - (id)initWithDevice:(IOHIDDeviceRef)device deviceProfile:(NSDictionary *)deviceProfile preset:(NSDictionary *)preset { 37 | self = [super init]; 38 | if (self) { 39 | _device = device; 40 | _deviceProfile = deviceProfile; 41 | _preset = preset; 42 | 43 | self.title = preset[kDeviceProfileTitleKey]; 44 | self.target = self; 45 | self.action = @selector(activatePreset); 46 | self.state = NSOffState; 47 | } 48 | return self; 49 | } 50 | 51 | + (PresetMenuItem *)disabledMenuItem:(IOHIDDeviceRef)device { 52 | NSString *title = NSLocalizedString(@"Disabled", @"When a gamepad device has no active game preset"); 53 | PresetMenuItem *menuItem = [[PresetMenuItem alloc] initWithDevice:device deviceProfile:@{} preset:@{ @"Title": title }]; 54 | menuItem.state = NSOnState; 55 | menuItem->_disabledMenuItem = YES; 56 | return menuItem; 57 | } 58 | 59 | - (void)activatePreset { 60 | for (NSMenuItem *menuItem in self.menu.itemArray) { 61 | if ([menuItem isSeparatorItem]) continue; 62 | menuItem.state = NSOffState; 63 | } 64 | 65 | for (NSMenuItem *menuItem in self.menu.supermenu.itemArray) { 66 | if (menuItem.submenu == self.menu) { 67 | menuItem.state = _disabledMenuItem ? NSOffState : NSOnState; 68 | } 69 | } 70 | 71 | self.state = NSOnState; 72 | 73 | if (_delegate && [_delegate respondsToSelector:@selector(clearElementBindingsForDevice:)]) { 74 | [_delegate clearElementBindingsForDevice:_device]; 75 | } 76 | 77 | NSDictionary *elementProfiles = _deviceProfile[kDeviceProfileElementsKey]; 78 | CFArrayRef elements = IOHIDDeviceCopyMatchingElements(_device, nil, kIOHIDOptionsTypeNone); 79 | CFIndex elementCount = CFArrayGetCount(elements); 80 | for (CFIndex i = 0; i < elementCount; i++) { 81 | IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); 82 | uint32_t elementUsagePage = IOHIDElementGetUsagePage(element); 83 | uint32_t elementUsage = IOHIDElementGetUsage(element); 84 | 85 | NSDictionary *elementProfile = elementProfiles[[NSString stringWithFormat:@"%d:%d", elementUsagePage, elementUsage]]; 86 | if (!elementProfile) continue; 87 | 88 | Class elementBindingClass = nil; 89 | NSMutableArray *keyBindings = [NSMutableArray new]; 90 | NSMutableDictionary *options = [NSMutableDictionary new]; 91 | for (NSString *property in elementProfile) { 92 | id value = elementProfile[property]; 93 | if ([property isEqualToString:kDeviceProfileElementTypeKey]) { 94 | if ([value isEqualToString:kElementTypeAxis]) { 95 | elementBindingClass = [AxisElementBinding class]; 96 | } else if ([value isEqualToString:kElementTypeButton]) { 97 | elementBindingClass = [ButtonElementBinding class]; 98 | } else if ([value isEqualToString:kElementTypeHatSwitch]) { 99 | elementBindingClass = [HatSwitchElementBinding class]; 100 | } 101 | } else if ([property isEqualToString:kDeviceProfileElementBindingKey]) { 102 | NSArray *bindings = [value isKindOfClass:[NSArray class]] ? value : @[value]; 103 | for (NSString *binding in bindings) { 104 | id key = [self resolveKey:_preset[binding]]; 105 | if (!key) key = [NSNull null]; 106 | [keyBindings addObject:key]; 107 | } 108 | } else { 109 | options[property] = value; 110 | } 111 | } 112 | 113 | ElementBinding *elementBinding = [[elementBindingClass alloc] initWithElement:element keyBindings:keyBindings options:options]; 114 | if (!elementBinding.isAssigned) continue; 115 | 116 | if (_delegate && [_delegate respondsToSelector:@selector(addElementBinding:)]) { 117 | [_delegate addElementBinding:elementBinding]; 118 | } 119 | } 120 | CFRelease(elements); 121 | } 122 | 123 | - (NSNumber *)resolveKey:(id)key { 124 | static NSMutableDictionary *mapping; 125 | 126 | if ([key isKindOfClass:[NSString class]]) { 127 | if (!mapping) { 128 | mapping = [NSMutableDictionary new]; 129 | 130 | TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource(); 131 | CFDataRef layoutData = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); 132 | const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); 133 | CFRelease(inputSource); 134 | 135 | for (CGKeyCode keyCode = 0; keyCode < 128; keyCode++) { 136 | UInt32 deadKeyState = 0; 137 | UniCharCount maxStringLength = 255; 138 | UniCharCount actualStringLength = 0; 139 | UniChar unicodeString[maxStringLength]; 140 | OSStatus status = UCKeyTranslate(keyboardLayout, keyCode, kUCKeyActionDown, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxStringLength, &actualStringLength, unicodeString); 141 | if (actualStringLength == 0 && deadKeyState) { 142 | status = UCKeyTranslate(keyboardLayout, kVK_Space, kUCKeyActionDown, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxStringLength, &actualStringLength, unicodeString); 143 | } 144 | 145 | if (status == noErr && actualStringLength > 0) { 146 | NSString *character = [[NSString stringWithCharacters:unicodeString length:(NSUInteger)actualStringLength] lowercaseString]; 147 | if (!mapping[character]) mapping[character] = @(keyCode); 148 | } 149 | } 150 | } 151 | 152 | return mapping[[key lowercaseString]]; 153 | } 154 | 155 | return [key isKindOfClass:[NSNumber class]] ? key : nil; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Gamepad Menu/StartAtLoginController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Alex Zielenski 2 | // Copyright (c) 2012 Travis Tilley 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | @interface StartAtLoginController : NSObject { 26 | NSString *_identifier; 27 | NSURL *_url; 28 | BOOL _enabled; 29 | } 30 | 31 | @property (assign, nonatomic, readwrite) BOOL startAtLogin; 32 | @property (assign, nonatomic, readwrite) BOOL enabled; 33 | @property (copy, nonatomic, readwrite) NSString *identifier; 34 | 35 | -(id)initWithIdentifier:(NSString*)identifier; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Gamepad Menu/StartAtLoginController.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Alex Zielenski 2 | // Copyright (c) 2012 Travis Tilley 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "StartAtLoginController.h" 24 | #import 25 | 26 | @implementation StartAtLoginController 27 | 28 | @synthesize identifier = _identifier; 29 | 30 | #if !__has_feature(objc_arc) 31 | - (void)dealloc { 32 | self.identifier = nil; 33 | [super dealloc]; 34 | } 35 | #endif 36 | 37 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { 38 | BOOL automatic = NO; 39 | 40 | if ([theKey isEqualToString:@"startAtLogin"]) { 41 | automatic = NO; 42 | } else if ([theKey isEqualToString:@"enabled"]) { 43 | automatic = NO; 44 | } else { 45 | automatic=[super automaticallyNotifiesObserversForKey:theKey]; 46 | } 47 | 48 | return automatic; 49 | } 50 | 51 | -(id)initWithIdentifier:(NSString*)identifier { 52 | self = [self init]; 53 | if(self) { 54 | self.identifier = identifier; 55 | } 56 | return self; 57 | } 58 | 59 | -(void)setIdentifier:(NSString *)identifier { 60 | _identifier = identifier; 61 | [self startAtLogin]; 62 | #if !defined(NDEBUG) 63 | NSLog(@"Launcher '%@' %@ configured to start at login", 64 | self.identifier, (_enabled ? @"is" : @"is not")); 65 | #endif 66 | } 67 | 68 | #pragma clang diagnostic push 69 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 70 | - (BOOL)startAtLogin { 71 | if (!_identifier) 72 | return NO; 73 | 74 | BOOL isEnabled = NO; 75 | 76 | // the easy and sane method (SMJobCopyDictionary) can pose problems when sandboxed. -_- 77 | CFArrayRef cfJobDicts = SMCopyAllJobDictionaries(kSMDomainUserLaunchd); 78 | NSArray* jobDicts = CFBridgingRelease(cfJobDicts); 79 | 80 | if (jobDicts && [jobDicts count] > 0) { 81 | for (NSDictionary* job in jobDicts) { 82 | if ([_identifier isEqualToString:[job objectForKey:@"Label"]]) { 83 | isEnabled = [[job objectForKey:@"OnDemand"] boolValue]; 84 | break; 85 | } 86 | } 87 | } 88 | 89 | if (isEnabled != _enabled) { 90 | [self willChangeValueForKey:@"enabled"]; 91 | _enabled = isEnabled; 92 | [self didChangeValueForKey:@"enabled"]; 93 | } 94 | 95 | return isEnabled; 96 | } 97 | #pragma clang diagnostic pop 98 | 99 | - (void)setStartAtLogin:(BOOL)flag { 100 | if (!_identifier) 101 | return; 102 | 103 | [self willChangeValueForKey:@"startAtLogin"]; 104 | 105 | if (!SMLoginItemSetEnabled((__bridge CFStringRef)_identifier, (flag) ? true : false)) { 106 | NSLog(@"SMLoginItemSetEnabled failed!"); 107 | 108 | [self willChangeValueForKey:@"enabled"]; 109 | _enabled = NO; 110 | [self didChangeValueForKey:@"enabled"]; 111 | } else { 112 | [self willChangeValueForKey:@"enabled"]; 113 | _enabled = YES; 114 | [self didChangeValueForKey:@"enabled"]; 115 | } 116 | 117 | [self didChangeValueForKey:@"startAtLogin"]; 118 | } 119 | 120 | - (BOOL)enabled 121 | { 122 | return _enabled; 123 | } 124 | 125 | - (void)setEnabled:(BOOL)enabled 126 | { 127 | [self setStartAtLogin:enabled]; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /Gamepad Menu/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Gamepad Menu 4 | // 5 | // Created by Robbert Klarenbeek on 12/01/16. 6 | // Copyright © 2016 Robbert Klarenbeek. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /Gamepad Menu/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Gamepad Menu/nl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Gamepad Menu/nl.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Quit"; ObjectID = "NwU-Kh-9yK"; */ 3 | "NwU-Kh-9yK.title" = "Stop"; 4 | 5 | /* Class = "NSMenuItem"; title = "Start at Login"; ObjectID = "PAh-jt-BZe"; */ 6 | "PAh-jt-BZe.title" = "Start na inloggen"; 7 | 8 | /* Class = "NSMenuItem"; title = "No gamepad devices detected..."; ObjectID = "puw-V8-6JH"; */ 9 | "puw-V8-6JH.title" = "Geen gamepads gedetecteerd..."; 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Robbert Klarenbeek 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PRODUCT=$(shell ls -d *.xcodeproj | head -n 1 | sed 's/.xcodeproj$$//' | sed 's/ /\\ /g') 2 | PROJECT=$(PRODUCT).xcodeproj 3 | INTERMEDIATE=build/Release/$(APP) 4 | APP=$(PRODUCT).app 5 | ZIP=$(PRODUCT).zip 6 | 7 | .PHONY: all 8 | all: $(APP) 9 | 10 | .PHONY: dist 11 | dist: $(ZIP) 12 | 13 | $(APP): $(INTERMEDIATE) 14 | rm -Rf $(APP) 15 | cp -R build/Release/$(APP) ./ 16 | 17 | $(INTERMEDIATE): xcodebuild 18 | @true 19 | 20 | .PHONY: xcodebuild 21 | xcodebuild: 22 | xcodebuild -project $(PROJECT) -target $(PRODUCT) -configuration Release 23 | 24 | $(ZIP): $(APP) 25 | rm -f $(ZIP) 26 | zip -r $(ZIP) $(APP) 27 | 28 | .PHONY: clean 29 | clean: 30 | rm -Rf build/ 31 | 32 | .PHONY: distclean 33 | distclean: clean 34 | rm -Rf $(APP) 35 | rm -f $(ZIP) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gamepad Menu 2 | 3 | Gamepad button mapper that lives as a menu in your OS X status bar. 4 | 5 | When trying to play Broforce (yes, [Broforce](http://www.broforcegame.com)) on a Mac with my SteelSeries Nimbus controller, I noticed most buttons were not supported by the game's gamepad configuration, probably because (for some weird reason) every single button on the Nimbus is pressure sensitive, except the "Menu" button. Existing Mac software for joystick mapping was either paid or crappy. More specifically, they had no configurable threshold for the pressure sensitive buttons, meaning even the simple action buttons had to be pressed down very firmly to trigger a keypress. 6 | 7 | Of course, with those limitations, it's hard to get your bro on, so I decided to make my own button mapper. It is aimed exclusively at gamepads and, while focussed on getting Nimbus' pressure sensitive buttons working, supports other gamepads as well. 8 | 9 | ## Features 10 | 11 | * Uses device profiles to map each supported gamepad device to a common control set, which a preset maps to specific keyboard presses. 12 | 13 | * Adjustable button threshold for pressure sensitive buttons and triggers. 14 | 15 | * Smart thresholding stickiness filter to prevent double key presses on a jittery value. 16 | 17 | * Kept simple on purpose. Only maps to keyboard, not mouse. Currently has no UI for creating device profiles or presets; this is done by editing plist files instead. 18 | 19 | * To facilitate "Start at Login", the `ServiceManagement.framework` is used together with a helper app, which should work in a sandboxed environment as well (as long as the app lives in /Applications). 20 | 21 | ## Installation 22 | 23 | Just fetch the latest ZIP from the [release section](https://github.com/robbertkl/GamepadMenu/releases) and put the extracted app into your Applications folder. 24 | 25 | You might need to disable OS X Gatekeeper to run it: *System Preferences* > *Security & Privacy* > *General* tab > *Allow apps downloaded from: Anywhere*. 26 | 27 | ## Device profiles 28 | 29 | Device profiles map known gamepad devices to "standardised" controls commonly found on gamepads. See [the Device Profiles folder](Resources/Device%20Profiles/) for all currently supported devices. A device profile plist contains 2 sections: 30 | 31 | * `Identifier` contains a vendor ID + product ID used to identify newly connected devices. If the same device uses different identifiers (e.g. one for USB and another for bluetooth), you can make this an array of dictionaries (see [SteelSeries Nimbus](Resources/Device%20Profiles/SteelSeries%20Nimbus.plist) for an example). 32 | 33 | * `Elements` contains a mapping from all device elements to the common gamepad controls. They are identified by `:`. Supported element types are: 34 | * `Button` - Regular button or trigger, either binary (0..1) or pressure sensitive (0..255). Has 1 key binding. 35 | * `Axis` - X or Y axis of an analog stick. Has 2 key bindings (left+right or up+down). 36 | * `Hat Switch` - Single element which maps an entire D-Pad. Has 4 key bindings. See [PlayStation 4 Controller](Resources/Device%20Profiles/PlayStation%204%20Controller.plist) element `1:57` for an example. 37 | 38 | A useful tool to check out HID devices and their elements is [Apple's HID Calibrator code sample](https://developer.apple.com/library/mac/samplecode/HID_Calibrator/). 39 | 40 | Please note the Xbox 360 controller requires a driver to work on OS X. I'm using [360Controller](https://github.com/360Controller/360Controller), which works perfectly on El Capitan. I only have a wired Xbox 360 controller, so I'm not sure how well it works with the wireless controllers. It would be nice if someone could test it and send me its vendor and product IDs, or a custom device profile if it differs from the wired version. 41 | 42 | ## Presets 43 | 44 | Preset plist files contain the key bindings for each of the mapped common gamepad controls. Use a string value to bind it to a keyboard character, or a number to bind it to a `CGKeyCode`. 45 | 46 | ## Wishlist 47 | 48 | * More supported devices (PR if you have new ones!) 49 | * Storing device profiles and presets in `~/Library/Application Support` for easy manipulation 50 | * UI for creating / editing device profiles 51 | * UI for creating / editing presets 52 | * Auto update device profiles from an online (GitHub?) repository 53 | * Automatic activation of presets when games are launched 54 | 55 | ## Authors 56 | 57 | * Robbert Klarenbeek, 58 | 59 | ## Credits 60 | 61 | * Thanks to [Alex Zielenski](https://twitter.com/#!/alexzielenski) for [StartAtLoginController](https://github.com/alexzielenski/StartAtLoginController), which ties together the ServiceManagement stuff without even a single line of code (gotta love KVO). 62 | 63 | * The [vector drawing used for the app icon](Resources/Graphics/AppIcon.ai) was made by [sebi01](http://www.vecteezy.com/members/sebi01) from [Vecteezy.com](http://www.vecteezy.com). 64 | 65 | * The [vector drawing used for the status menu icon](Resources/Graphics/StatusMenuTemplate.eps) was made by [Freepik](http://www.freepik.com) from [Flaticon](http://www.flaticon.com). 66 | 67 | ## License 68 | 69 | Gamepad Menu is published under the [MIT License](http://www.opensource.org/licenses/mit-license.php). 70 | -------------------------------------------------------------------------------- /Resources/Device Profiles/PlayStation 4 Controller.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | 7 | Vendor 8 | 1356 9 | Product 10 | 1476 11 | 12 | Elements 13 | 14 | 9:1 15 | 16 | Type 17 | Button 18 | Binding 19 | Button X 20 | 21 | 9:2 22 | 23 | Type 24 | Button 25 | Binding 26 | Button A 27 | 28 | 9:3 29 | 30 | Type 31 | Button 32 | Binding 33 | Button B 34 | 35 | 9:4 36 | 37 | Type 38 | Button 39 | Binding 40 | Button Y 41 | 42 | 9:5 43 | 44 | Type 45 | Button 46 | Binding 47 | Left shoulder 48 | 49 | 9:6 50 | 51 | Type 52 | Button 53 | Binding 54 | Right shoulder 55 | 56 | 9:13 57 | 58 | Type 59 | Button 60 | Binding 61 | Home button 62 | 63 | 1:48 64 | 65 | Type 66 | Axis 67 | Threshold 68 | 0.4 69 | ThresholdStickiness 70 | 0.1 71 | Binding 72 | 73 | Left stick - Left 74 | Left stick - Right 75 | 76 | 77 | 1:49 78 | 79 | Type 80 | Axis 81 | Threshold 82 | 0.4 83 | ThresholdStickiness 84 | 0.1 85 | Binding 86 | 87 | Left stick - Up 88 | Left stick - Down 89 | 90 | 91 | 1:50 92 | 93 | Type 94 | Axis 95 | Threshold 96 | 0.4 97 | ThresholdStickiness 98 | 0.1 99 | Binding 100 | 101 | Right stick - Left 102 | Right stick - Right 103 | 104 | 105 | 1:53 106 | 107 | Type 108 | Axis 109 | Threshold 110 | 0.4 111 | ThresholdStickiness 112 | 0.1 113 | Binding 114 | 115 | Right stick - Up 116 | Right stick - Down 117 | 118 | 119 | 1:57 120 | 121 | Type 122 | Hat Switch 123 | Binding 124 | 125 | Directional pad - Up 126 | Directional pad - Right 127 | Directional pad - Down 128 | Directional pad - Left 129 | 130 | 131 | 1:51 132 | 133 | Type 134 | Button 135 | Threshold 136 | 0.4 137 | ThresholdStickiness 138 | 0.1 139 | Binding 140 | Left trigger 141 | 142 | 1:52 143 | 144 | Type 145 | Button 146 | Threshold 147 | 0.4 148 | ThresholdStickiness 149 | 0.1 150 | Binding 151 | Right trigger 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /Resources/Device Profiles/SteelSeries Nimbus.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | 7 | 8 | Vendor 9 | 273 10 | Product 11 | 5152 12 | 13 | 14 | Vendor 15 | 4152 16 | Product 17 | 5152 18 | 19 | 20 | Elements 21 | 22 | 12:547 23 | 24 | Type 25 | Button 26 | Binding 27 | Home button 28 | 29 | 1:144 30 | 31 | Type 32 | Button 33 | Threshold 34 | 0.001 35 | Binding 36 | Directional pad - Up 37 | 38 | 1:146 39 | 40 | Type 41 | Button 42 | Threshold 43 | 0.001 44 | Binding 45 | Directional pad - Right 46 | 47 | 1:145 48 | 49 | Type 50 | Button 51 | Threshold 52 | 0.001 53 | Binding 54 | Directional pad - Down 55 | 56 | 1:147 57 | 58 | Type 59 | Button 60 | Threshold 61 | 0.001 62 | Binding 63 | Directional pad - Left 64 | 65 | 9:1 66 | 67 | Type 68 | Button 69 | Threshold 70 | 0.001 71 | Binding 72 | Button A 73 | 74 | 9:2 75 | 76 | Type 77 | Button 78 | Threshold 79 | 0.001 80 | Binding 81 | Button B 82 | 83 | 9:3 84 | 85 | Type 86 | Button 87 | Threshold 88 | 0.001 89 | Binding 90 | Button X 91 | 92 | 9:4 93 | 94 | Type 95 | Button 96 | Threshold 97 | 0.001 98 | Binding 99 | Button Y 100 | 101 | 9:5 102 | 103 | Type 104 | Button 105 | Threshold 106 | 0.001 107 | Binding 108 | Left shoulder 109 | 110 | 9:6 111 | 112 | Type 113 | Button 114 | Threshold 115 | 0.001 116 | Binding 117 | Right shoulder 118 | 119 | 9:7 120 | 121 | Type 122 | Button 123 | Threshold 124 | 0.3 125 | ThresholdStickiness 126 | 0.1 127 | Binding 128 | Left trigger 129 | 130 | 9:8 131 | 132 | Type 133 | Button 134 | Threshold 135 | 0.3 136 | ThresholdStickiness 137 | 0.1 138 | Binding 139 | Right trigger 140 | 141 | 1:48 142 | 143 | Type 144 | Axis 145 | Threshold 146 | 0.3 147 | ThresholdStickiness 148 | 0.1 149 | Binding 150 | 151 | Left stick - Left 152 | Left stick - Right 153 | 154 | 155 | 1:49 156 | 157 | Type 158 | Axis 159 | Threshold 160 | 0.4 161 | ThresholdStickiness 162 | 0.1 163 | Binding 164 | 165 | Left stick - Down 166 | Left stick - Up 167 | 168 | 169 | 1:50 170 | 171 | Type 172 | Axis 173 | Threshold 174 | 0.4 175 | ThresholdStickiness 176 | 0.1 177 | Binding 178 | 179 | Right stick - Left 180 | Right stick - Right 181 | 182 | 183 | 1:53 184 | 185 | Type 186 | Axis 187 | Threshold 188 | 0.4 189 | ThresholdStickiness 190 | 0.1 191 | Binding 192 | 193 | Right stick - Down 194 | Right stick - Up 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /Resources/Device Profiles/Xbox 360 Controller.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Identifier 6 | 7 | Vendor 8 | 1118 9 | Product 10 | 654 11 | 12 | Elements 13 | 14 | 1:48 15 | 16 | Type 17 | Axis 18 | Threshold 19 | 0.5 20 | ThresholdStickiness 21 | 0.1 22 | Binding 23 | 24 | Left stick - Left 25 | Left stick - Right 26 | 27 | 28 | 1:49 29 | 30 | Type 31 | Axis 32 | Threshold 33 | 0.5 34 | ThresholdStickiness 35 | 0.1 36 | Binding 37 | 38 | Left stick - Up 39 | Left stick - Down 40 | 41 | 42 | 1:51 43 | 44 | Type 45 | Axis 46 | Threshold 47 | 0.5 48 | ThresholdStickiness 49 | 0.1 50 | Binding 51 | 52 | Right stick - Left 53 | Right stick - Right 54 | 55 | 56 | 1:52 57 | 58 | Type 59 | Axis 60 | Threshold 61 | 0.6 62 | ThresholdStickiness 63 | 0.1 64 | Binding 65 | 66 | Right stick - Up 67 | Right stick - Down 68 | 69 | 70 | 9:12 71 | 72 | Type 73 | Button 74 | Binding 75 | Directional pad - Up 76 | 77 | 9:13 78 | 79 | Type 80 | Button 81 | Binding 82 | Directional pad - Down 83 | 84 | 9:14 85 | 86 | Type 87 | Button 88 | Binding 89 | Directional pad - Left 90 | 91 | 9:15 92 | 93 | Type 94 | Button 95 | Binding 96 | Directional pad - Right 97 | 98 | 9:5 99 | 100 | Type 101 | Button 102 | Binding 103 | Left shoulder 104 | 105 | 9:6 106 | 107 | Type 108 | Button 109 | Binding 110 | Right shoulder 111 | 112 | 9:11 113 | 114 | Type 115 | Button 116 | Binding 117 | Home button 118 | 119 | 9:1 120 | 121 | Type 122 | Button 123 | Binding 124 | Button A 125 | 126 | 9:2 127 | 128 | Type 129 | Button 130 | Binding 131 | Button B 132 | 133 | 9:3 134 | 135 | Type 136 | Button 137 | Binding 138 | Button X 139 | 140 | 9:4 141 | 142 | Type 143 | Button 144 | Binding 145 | Button Y 146 | 147 | 1:50 148 | 149 | Type 150 | Button 151 | Threshold 152 | 0.4 153 | ThresholdStickiness 154 | 0.1 155 | Binding 156 | Left trigger 157 | 158 | 1:53 159 | 160 | Type 161 | Button 162 | Threshold 163 | 0.4 164 | ThresholdStickiness 165 | 0.1 166 | Binding 167 | Right trigger 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /Resources/Graphics/AppIcon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbertkl/GamepadMenu/2201e3e7ded449f32a9795fc790d615206e46766/Resources/Graphics/AppIcon.ai -------------------------------------------------------------------------------- /Resources/Graphics/StatusMenuTemplate.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: cairo 1.10.2 (http://cairographics.org) 3 | %%CreationDate: Fri Apr 24 12:40:56 2015 4 | %%Pages: 1 5 | %%BoundingBox: 0 -1 383 237 6 | %%DocumentData: Clean7Bit 7 | %%LanguageLevel: 2 8 | %%EndComments 9 | %%BeginProlog 10 | /cairo_eps_state save def 11 | /dict_count countdictstack def 12 | /op_count count 1 sub def 13 | userdict begin 14 | /q { gsave } bind def 15 | /Q { grestore } bind def 16 | /cm { 6 array astore concat } bind def 17 | /w { setlinewidth } bind def 18 | /J { setlinecap } bind def 19 | /j { setlinejoin } bind def 20 | /M { setmiterlimit } bind def 21 | /d { setdash } bind def 22 | /m { moveto } bind def 23 | /l { lineto } bind def 24 | /c { curveto } bind def 25 | /h { closepath } bind def 26 | /re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto 27 | 0 exch rlineto 0 rlineto closepath } bind def 28 | /S { stroke } bind def 29 | /f { fill } bind def 30 | /f* { eofill } bind def 31 | /n { newpath } bind def 32 | /W { clip } bind def 33 | /W* { eoclip } bind def 34 | /BT { } bind def 35 | /ET { } bind def 36 | /pdfmark where { pop globaldict /?pdfmark /exec load put } 37 | { globaldict begin /?pdfmark /pop load def /pdfmark 38 | /cleartomark load def end } ifelse 39 | /BDC { mark 3 1 roll /BDC pdfmark } bind def 40 | /EMC { mark /EMC pdfmark } bind def 41 | /cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def 42 | /Tj { show currentpoint cairo_store_point } bind def 43 | /TJ { 44 | { 45 | dup 46 | type /stringtype eq 47 | { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse 48 | } forall 49 | currentpoint cairo_store_point 50 | } bind def 51 | /cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore 52 | cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def 53 | /Tf { pop /cairo_font exch def /cairo_font_matrix where 54 | { pop cairo_selectfont } if } bind def 55 | /Td { matrix translate cairo_font_matrix matrix concatmatrix dup 56 | /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point 57 | /cairo_font where { pop cairo_selectfont } if } bind def 58 | /Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def 59 | cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def 60 | /g { setgray } bind def 61 | /rg { setrgbcolor } bind def 62 | /d1 { setcachedevice } bind def 63 | %%EndProlog 64 | %%Page: 1 1 65 | %%BeginPageSetup 66 | %%PageBoundingBox: 0 -1 383 237 67 | %%EndPageSetup 68 | q 0 -1 383 238 rectclip q 69 | 0 236.176 383 -237 re W n 70 | 0 g 71 | 348.93 176.871 m 318.297 239.559 285.477 243.938 254.918 228.996 c 72 | 245.648 224.469 237.684 218.657 231.898 211.043 c 150.129 211.043 l 73 | 144.344 218.657 136.379 224.469 127.113 228.996 c 96.555 243.938 63.73 74 | 239.559 33.098 176.871 c 2.441 114.176 -14.406 16.907 16.152 1.965 c 75 | 37.391 -8.414 75.91 23.727 107.727 65.321 c 274.277 65.321 l 306.09 76 | 23.727 344.613 -8.414 365.852 1.965 c 396.434 16.907 379.562 114.176 77 | 348.93 176.871 c h 78 | 142.637 137.696 m 142.637 135.164 140.535 133.11 137.949 133.11 c 79 | 115.516 133.11 l 115.516 110.118 l 115.516 107.586 113.426 105.532 80 | 110.836 105.532 c 94.961 105.532 l 92.387 105.532 90.281 107.586 90.281 81 | 110.118 c 90.281 133.11 l 67.852 133.11 l 65.262 133.11 63.16 135.164 82 | 63.16 137.696 c 63.16 153.254 l 63.16 155.785 65.262 157.84 67.852 83 | 157.84 c 90.281 157.84 l 90.281 180.84 l 90.281 183.371 92.387 185.426 84 | 94.961 185.426 c 110.836 185.426 l 113.426 185.426 115.516 183.371 85 | 115.516 180.84 c 115.516 157.84 l 137.949 157.84 l 140.539 157.84 86 | 142.641 155.785 142.641 153.254 c 142.641 137.696 l h 87 | 287.699 176.852 m 287.699 168.946 281.305 162.535 273.395 162.535 c 88 | 265.48 162.535 259.086 168.946 259.086 176.852 c 259.086 184.758 265.48 89 | 191.164 273.395 191.164 c 281.305 191.164 287.699 184.758 287.699 90 | 176.852 c h 91 | 243.883 133.024 m 235.969 133.024 229.57 139.438 229.57 147.344 c 92 | 229.57 155.239 235.969 161.653 243.883 161.653 c 251.793 161.653 93 | 258.188 155.239 258.188 147.344 c 258.188 139.438 251.793 133.024 94 | 243.883 133.024 c h 95 | 302.957 133.024 m 295.047 133.024 288.648 139.438 288.648 147.344 c 96 | 288.648 155.239 295.047 161.653 302.957 161.653 c 310.871 161.653 97 | 317.266 155.239 317.266 147.344 c 317.266 139.438 310.871 133.024 98 | 302.957 133.024 c h 99 | 273.395 103.512 m 265.48 103.512 259.086 109.918 259.086 117.825 c 100 | 259.086 125.735 265.48 132.141 273.395 132.141 c 281.305 132.141 101 | 287.699 125.735 287.699 117.825 c 287.699 109.918 281.305 103.512 102 | 273.395 103.512 c h 103 | 273.395 103.512 m f 104 | Q Q 105 | showpage 106 | %%Trailer 107 | count op_count sub {pop} repeat 108 | countdictstack dict_count sub {end} repeat 109 | cairo_eps_state restore 110 | %%EOF 111 | -------------------------------------------------------------------------------- /Resources/Presets/Broforce - Player 1.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Directional pad - Up 6 | 126 7 | Directional pad - Down 8 | 125 9 | Directional pad - Left 10 | 123 11 | Directional pad - Right 12 | 124 13 | Left stick - Up 14 | 126 15 | Left stick - Down 16 | 125 17 | Left stick - Left 18 | 123 19 | Left stick - Right 20 | 124 21 | Button A 22 | 49 23 | Button B 24 | V 25 | Button X 26 | C 27 | Button Y 28 | B 29 | Home button 30 | 53 31 | Left shoulder 32 | X 33 | Right shoulder 34 | Z 35 | Left trigger 36 | X 37 | Right trigger 38 | Z 39 | 40 | 41 | -------------------------------------------------------------------------------- /Resources/Presets/Broforce - Player 2.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Directional pad - Up 6 | W 7 | Directional pad - Down 8 | S 9 | Directional pad - Left 10 | A 11 | Directional pad - Right 12 | D 13 | Left stick - Up 14 | W 15 | Left stick - Down 16 | S 17 | Left stick - Left 18 | A 19 | Left stick - Right 20 | D 21 | Button A 22 | F 23 | Button B 24 | G 25 | Button X 26 | R 27 | Button Y 28 | T 29 | Home button 30 | 53 31 | Left shoulder 32 | Q 33 | Right shoulder 34 | E 35 | Left trigger 36 | Q 37 | Right trigger 38 | E 39 | 40 | 41 | -------------------------------------------------------------------------------- /Resources/Presets/Generic.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Directional pad - Up 6 | 126 7 | Directional pad - Down 8 | 125 9 | Directional pad - Left 10 | 123 11 | Directional pad - Right 12 | 124 13 | Left stick - Up 14 | 126 15 | Left stick - Down 16 | 125 17 | Left stick - Left 18 | 123 19 | Left stick - Right 20 | 124 21 | Button A 22 | 49 23 | Button B 24 | 56 25 | Button X 26 | 36 27 | Button Y 28 | 48 29 | Home button 30 | 53 31 | Left shoulder 32 | 59 33 | Right shoulder 34 | 62 35 | Left trigger 36 | 59 37 | Right trigger 38 | 62 39 | 40 | 41 | -------------------------------------------------------------------------------- /Resources/Presets/Super Meat Boy.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Directional pad - Up 6 | 126 7 | Directional pad - Down 8 | 125 9 | Directional pad - Left 10 | 123 11 | Directional pad - Right 12 | 124 13 | Left stick - Up 14 | 126 15 | Left stick - Down 16 | 125 17 | Left stick - Left 18 | 123 19 | Left stick - Right 20 | 124 21 | Button A 22 | 49 23 | Button B 24 | 49 25 | Button X 26 | 56 27 | Button Y 28 | 56 29 | Home button 30 | 53 31 | Left shoulder 32 | W 33 | Right shoulder 34 | 56 35 | Left trigger 36 | W 37 | Right trigger 38 | 56 39 | 40 | 41 | --------------------------------------------------------------------------------