├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CMakeSettings.json ├── JoyShockMapper ├── .clang-format ├── CMakeLists.txt ├── JoyShockMapper.ico ├── Win32 Dialog.rc ├── doc │ ├── ButtonStateMachine.png │ ├── DualStageTriggerStateMachine.PNG │ └── JSM Refactoring changes.pdf ├── gyro_icon.png ├── include │ ├── AutoConnect.h │ ├── AutoLoad.h │ ├── CmdRegistry.h │ ├── ColorCodes.h │ ├── DigitalButton.h │ ├── Gamepad.h │ ├── HidHideApi.h │ ├── InputHelpers.h │ ├── JSMAssignment.hpp │ ├── JSMVariable.hpp │ ├── JSMVersion.h.in │ ├── JoyShock.h │ ├── JoyShockMapper.h │ ├── JslWrapper.h │ ├── Mapping.h │ ├── MotionIf.h │ ├── PlatformDefinitions.h │ ├── SettingsManager.h │ ├── Stick.h │ ├── TrayIcon.h │ ├── TriggerEffectGenerator.h │ ├── Whitelister.h │ ├── linux │ │ └── StatusNotifierItem.h │ └── win32 │ │ ├── WindowsTrayIcon.h │ │ └── resource.h ├── libs │ ├── JoyShockLibrary.dll │ └── JoyShockLibrary.lib └── src │ ├── AutoConnect.cpp │ ├── AutoLoad.cpp │ ├── ButtonHelp.cpp │ ├── CmdRegistry.cpp │ ├── DigitalButton.cpp │ ├── JoyShock.cpp │ ├── JslWrapper.cpp │ ├── Mapping.cpp │ ├── MotionImpl.cpp │ ├── SDL2Wrapper.cpp │ ├── SDL2Wrapper.cpp.bak │ ├── SDL2Wrapper.cpp.orig │ ├── SDL2Wrapper_LOCAL_612.cpp.bak │ ├── SDL2Wrapper_REMOTE_612.cpp.bak │ ├── SettingsManager.cpp │ ├── Stick.cpp │ ├── TriggerEffectGenerator.cpp │ ├── linux │ ├── Gamepad.cpp │ ├── Init.cpp │ ├── InputHelpers.cpp │ ├── PlatformDefinitions.cpp │ ├── StatusNotifierItem.cpp │ └── Whitelister.cpp │ ├── main.cpp │ ├── operators.cpp │ ├── quatMaths.cpp │ └── win32 │ ├── Gamepad.cpp │ ├── HidGuardianWhitelister.cpp │ ├── HidHideApi.cpp │ ├── HidHideWhitelister.cpp │ ├── InputHelpers.cpp │ ├── InputHelpers.cpp.bak │ ├── InputHelpers.cpp.orig │ ├── PlatformDefinitions.cpp │ └── WindowsTrayIcon.cpp ├── LICENSE.md ├── README.md ├── README_中文.md ├── cmake ├── CPM.cmake ├── GetGitRevisionDescription.cmake ├── GetGitRevisionDescription.cmake.in ├── LinuxConfig.cmake └── WindowsConfig.cmake ├── dist ├── AutoLoad │ └── README.txt ├── GyroConfigs │ ├── Desktop.txt │ ├── _2Dcalibrate.txt │ ├── _2Dmouse.txt │ ├── _2Dtemplate.txt │ ├── _3Dcalibrate.txt │ ├── _3Dmouse.txt │ ├── _3Dtemplate.txt │ ├── ds4.txt │ ├── xbox.txt │ └── xbox_joycons.txt ├── OnReset.txt ├── OnStartup.txt └── linux │ ├── 50-joyshockmapper.rules │ ├── JoyShockMapper.desktop │ ├── PKGBUILD.in │ ├── joyshockmapper.dsc.in │ ├── jsm-status-dark.svg │ └── jsm-status.svg └── script ├── create_linux_obs_sources.sh ├── create_windows_release_archives.bat ├── generate_win32_vs_solution_JSL.bat ├── generate_win32_vs_solution_SDL.bat ├── generate_win64_vs_solution_JSL.bat └── generate_win64_vs_solution_SDL.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | build-jsm-* 7 | 8 | # custom: 9 | *.tlog 10 | intermediate/ 11 | 12 | # User-specific files 13 | #*.suo 14 | #*.user 15 | #*.userosscache 16 | #*.sln.docstates 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | #*.userprefs 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | **/Properties/launchSettings.json 59 | 60 | *_i.c 61 | *_p.c 62 | *_i.h 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.pch 67 | *.pdb 68 | *.pgc 69 | *.pgd 70 | *.rsp 71 | *.sbr 72 | *.tlb 73 | *.tli 74 | *.tlh 75 | *.tmp 76 | *.tmp_proj 77 | *.log 78 | *.vspscc 79 | *.vssscc 80 | .builds 81 | *.pidb 82 | *.svclog 83 | *.scc 84 | 85 | # Chutzpah Test files 86 | _Chutzpah* 87 | 88 | # Visual C++ cache files 89 | ipch/ 90 | *.aps 91 | *.ncb 92 | *.opendb 93 | *.opensdf 94 | *.sdf 95 | *.cachefile 96 | *.VC.db 97 | *.VC.VC.opendb 98 | 99 | # Visual Studio profiler 100 | *.psess 101 | *.vsp 102 | *.vspx 103 | *.sap 104 | 105 | # TFS 2012 Local Workspace 106 | $tf/ 107 | 108 | # Guidance Automation Toolkit 109 | *.gpState 110 | 111 | # ReSharper is a .NET coding add-in 112 | _ReSharper*/ 113 | *.[Rr]e[Ss]harper 114 | *.DotSettings.user 115 | 116 | # JustCode is a .NET coding add-in 117 | .JustCode 118 | 119 | # TeamCity is a build add-in 120 | _TeamCity* 121 | 122 | # DotCover is a Code Coverage Tool 123 | *.dotCover 124 | 125 | # AxoCover is a Code Coverage Tool 126 | .axoCover/* 127 | !.axoCover/settings.json 128 | 129 | # Visual Studio code coverage results 130 | *.coverage 131 | *.coveragexml 132 | 133 | # NCrunch 134 | _NCrunch_* 135 | .*crunch*.local.xml 136 | nCrunchTemp_* 137 | 138 | # MightyMoose 139 | *.mm.* 140 | AutoTest.Net/ 141 | 142 | # Web workbench (sass) 143 | .sass-cache/ 144 | 145 | # Installshield output folder 146 | [Ee]xpress/ 147 | 148 | # DocProject is a documentation generator add-in 149 | DocProject/buildhelp/ 150 | DocProject/Help/*.HxT 151 | DocProject/Help/*.HxC 152 | DocProject/Help/*.hhc 153 | DocProject/Help/*.hhk 154 | DocProject/Help/*.hhp 155 | DocProject/Help/Html2 156 | DocProject/Help/html 157 | 158 | # Click-Once directory 159 | publish/ 160 | 161 | # Publish Web Output 162 | *.[Pp]ublish.xml 163 | *.azurePubxml 164 | # Note: Comment the next line if you want to checkin your web deploy settings, 165 | # but database connection strings (with potential passwords) will be unencrypted 166 | *.pubxml 167 | *.publishproj 168 | 169 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 170 | # checkin your Azure Web App publish settings, but sensitive information contained 171 | # in these scripts will be unencrypted 172 | PublishScripts/ 173 | 174 | # NuGet Packages 175 | *.nupkg 176 | # The packages folder can be ignored because of Package Restore 177 | **/packages/* 178 | # except build/, which is used as an MSBuild target. 179 | !**/packages/build/ 180 | # Uncomment if necessary however generally it will be regenerated when needed 181 | #!**/packages/repositories.config 182 | # NuGet v3's project.json files produces more ignorable files 183 | *.nuget.props 184 | *.nuget.targets 185 | 186 | # Microsoft Azure Build Output 187 | csx/ 188 | *.build.csdef 189 | 190 | # Microsoft Azure Emulator 191 | ecf/ 192 | rcf/ 193 | 194 | # Windows Store app package directories and files 195 | AppPackages/ 196 | BundleArtifacts/ 197 | Package.StoreAssociation.xml 198 | _pkginfo.txt 199 | *.appx 200 | 201 | # Visual Studio cache files 202 | # files ending in .cache can be ignored 203 | *.[Cc]ache 204 | # but keep track of directories ending in .cache 205 | !*.[Cc]ache/ 206 | 207 | # Others 208 | ClientBin/ 209 | ~$* 210 | *~ 211 | *.dbmdl 212 | *.dbproj.schemaview 213 | *.jfm 214 | *.pfx 215 | *.publishsettings 216 | orleans.codegen.cs 217 | 218 | # Since there are multiple workflows, uncomment next line to ignore bower_components 219 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 220 | #bower_components/ 221 | 222 | # RIA/Silverlight projects 223 | Generated_Code/ 224 | 225 | # Backup & report files from converting an old project file 226 | # to a newer Visual Studio version. Backup files are not needed, 227 | # because we have git ;-) 228 | _UpgradeReport_Files/ 229 | Backup*/ 230 | UpgradeLog*.XML 231 | UpgradeLog*.htm 232 | 233 | # SQL Server files 234 | *.mdf 235 | *.ldf 236 | *.ndf 237 | 238 | # Business Intelligence projects 239 | *.rdl.data 240 | *.bim.layout 241 | *.bim_*.settings 242 | 243 | # Microsoft Fakes 244 | FakesAssemblies/ 245 | 246 | # GhostDoc plugin setting file 247 | *.GhostDoc.xml 248 | 249 | # Node.js Tools for Visual Studio 250 | .ntvs_analysis.dat 251 | node_modules/ 252 | 253 | # Typescript v1 declaration files 254 | typings/ 255 | 256 | # Visual Studio 6 build log 257 | *.plg 258 | 259 | # Visual Studio 6 workspace options file 260 | *.opt 261 | 262 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 263 | *.vbw 264 | 265 | # Visual Studio LightSwitch build output 266 | **/*.HTMLClient/GeneratedArtifacts 267 | **/*.DesktopClient/GeneratedArtifacts 268 | **/*.DesktopClient/ModelManifest.xml 269 | **/*.Server/GeneratedArtifacts 270 | **/*.Server/ModelManifest.xml 271 | _Pvt_Extensions 272 | 273 | # Paket dependency manager 274 | .paket/paket.exe 275 | paket-files/ 276 | 277 | # FAKE - F# Make 278 | .fake/ 279 | 280 | # JetBrains Rider 281 | .idea/ 282 | *.sln.iml 283 | 284 | # CodeRush 285 | .cr/ 286 | 287 | # Python Tools for Visual Studio (PTVS) 288 | __pycache__/ 289 | *.pyc 290 | 291 | # Cake - Uncomment if you are using it 292 | # tools/** 293 | # !tools/packages.config 294 | 295 | # Tabs Studio 296 | *.tss 297 | 298 | # Telerik's JustMock configuration file 299 | *.jmconfig 300 | 301 | # BizTalk build output 302 | *.btp.cs 303 | *.btm.cs 304 | *.odx.cs 305 | *.xsd.cs 306 | 307 | # CMake 308 | *vcxproj* 309 | *.sln 310 | *.cmake 311 | CMakeLists.txt.user 312 | CMakeCache.txt 313 | CMakeFiles 314 | CPM_modules 315 | Makefile 316 | install_manifest.txt 317 | compile_commands.json 318 | _deps 319 | install/ 320 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.28) 2 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 3 | 4 | if(SDL OR NOT DEFINED SDL) 5 | set(PROJECT_NAME "JoyShockMapper_SDL2") 6 | else() 7 | set(PROJECT_NAME "JoyShockMapper_JSL") 8 | endif() 9 | 10 | project (${PROJECT_NAME} CXX) 11 | 12 | # Turning off extensions avoids and issue with the clang 16 compiler with modules 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | set (CMAKE_CXX_STANDARD 23) 15 | set (CMAKE_CXX_STANDARD_REQUIRED ON) 16 | 17 | include (cmake/LinuxConfig.cmake) 18 | include (cmake/WindowsConfig.cmake) 19 | include (cmake/CPM.cmake) 20 | include (cmake/GetGitRevisionDescription.cmake) 21 | 22 | add_subdirectory (JoyShockMapper) 23 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "-v", 12 | "ctestCommandArgs": "", 13 | "variables": [] 14 | }, 15 | { 16 | "name": "Win32", 17 | "generator": "Ninja", 18 | "configurationType": "Debug", 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "-v", 23 | "ctestCommandArgs": "", 24 | "inheritEnvironments": [ "msvc_x86" ], 25 | "variables": [] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /JoyShockMapper/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: DontAlign 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignConsecutiveMacros: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: false 9 | AlignTrailingComments: true 10 | AllowAllArgumentsOnNextLine: false 11 | AllowAllConstructorInitializersOnNextLine: true 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: Empty 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: Empty 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterDefinitionReturnType: None 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: true 22 | AlwaysBreakTemplateDeclarations: MultiLine 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BraceWrapping: 26 | AfterCaseLabel: true 27 | AfterClass: true 28 | AfterControlStatement: true 29 | AfterEnum: true 30 | AfterExternBlock: true 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterObjCDeclaration: false 34 | AfterStruct: true 35 | AfterUnion: true 36 | BeforeCatch: true 37 | BeforeElse: true 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyNamespace: true 41 | SplitEmptyRecord: true 42 | BreakAfterJavaFieldAnnotations: true 43 | BreakBeforeBinaryOperators: None 44 | BreakBeforeBraces: Allman 45 | BreakBeforeInheritanceComma: false 46 | BreakBeforeTernaryOperators: false 47 | BreakConstructorInitializers: BeforeColon 48 | BreakConstructorInitializersBeforeComma: true 49 | BreakInheritanceList: BeforeColon 50 | BreakStringLiterals: false 51 | ColumnLimit: 0 52 | CommentPragmas: '^ IWYU pragma:' 53 | CompactNamespaces: true 54 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 55 | ConstructorInitializerIndentWidth: 2 56 | ContinuationIndentWidth: 2 57 | Cpp11BracedListStyle: false 58 | DeriveLineEnding: true 59 | DerivePointerAlignment: true 60 | DisableFormat: false 61 | ExperimentalAutoDetectBinPacking: false 62 | FixNamespaceComments: true 63 | ForEachMacros: 64 | - foreach 65 | - Q_FOREACH 66 | - BOOST_FOREACH 67 | IncludeBlocks: Preserve 68 | IncludeCategories: 69 | - Priority: 2 70 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 71 | SortPriority: 0 72 | - Priority: 3 73 | Regex: ^(<|"(gtest|gmock|isl|json)/) 74 | SortPriority: 0 75 | - Priority: 1 76 | Regex: .* 77 | SortPriority: 0 78 | IncludeIsMainRegex: (Test)?$ 79 | IncludeIsMainSourceRegex: '' 80 | IndentCaseLabels: false 81 | IndentGotoLabels: true 82 | IndentPPDirectives: None 83 | IndentWidth: 4 84 | IndentWrappedFunctionNames: false 85 | JavaScriptQuotes: Leave 86 | JavaScriptWrapImports: false 87 | KeepEmptyLinesAtTheStartOfBlocks: true 88 | Language: Cpp 89 | MacroBlockBegin: '' 90 | MacroBlockEnd: '' 91 | MaxEmptyLinesToKeep: 1 92 | NamespaceIndentation: Inner 93 | ObjCBinPackProtocolList: Auto 94 | ObjCBlockIndentWidth: 4 95 | ObjCSpaceAfterProperty: true 96 | ObjCSpaceBeforeProtocolList: false 97 | PenaltyBreakAssignment: 2 98 | PenaltyBreakBeforeFirstCallParameter: 19 99 | PenaltyBreakComment: 309 100 | PenaltyBreakFirstLessLess: 120 101 | PenaltyBreakString: 1000 102 | PenaltyBreakTemplateDeclaration: 10 103 | PenaltyExcessCharacter: 804190 104 | PenaltyReturnTypeOnItsOwnLine: 60 105 | PointerAlignment: Middle 106 | ReflowComments: true 107 | SortIncludes: false 108 | SortUsingDeclarations: false 109 | SpaceAfterCStyleCast: false 110 | SpaceAfterLogicalNot: false 111 | SpaceAfterTemplateKeyword: false 112 | SpaceBeforeAssignmentOperators: true 113 | SpaceBeforeCpp11BracedList: false 114 | SpaceBeforeCtorInitializerColon: true 115 | SpaceBeforeInheritanceColon: true 116 | SpaceBeforeParens: ControlStatements 117 | SpaceBeforeRangeBasedForLoopColon: true 118 | SpaceBeforeSquareBrackets: false 119 | SpaceInEmptyBlock: true 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInCStyleCastParentheses: false 124 | SpacesInConditionalStatement: false 125 | SpacesInContainerLiterals: true 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | StatementMacros: 129 | - Q_UNUSED 130 | - QT_REQUIRE_VERSION 131 | TabWidth: 4 132 | UseCRLF: true 133 | UseTab: ForIndentation -------------------------------------------------------------------------------- /JoyShockMapper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (BINARY_NAME "JoyShockMapper") 2 | 3 | git_describe(GIT_TAG --tags --dirty=_d) 4 | 5 | #parse the version information into pieces. 6 | string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" JSM_VERSION_MAJOR "${GIT_TAG}") 7 | string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" JSM_VERSION_MINOR "${GIT_TAG}") 8 | string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" JSM_VERSION_PATCH "${GIT_TAG}") 9 | set(JSM_VERSION "v${JSM_VERSION_MAJOR}.${JSM_VERSION_MINOR}.${JSM_VERSION_PATCH}") 10 | 11 | message(STATUS "JSM version is " ${JSM_VERSION}) 12 | 13 | configure_file(include/JSMVersion.h.in include/JSMVersion.h) 14 | 15 | set (CMAKE_VS_JUST_MY_CODE_DEBUGGING 1) 16 | 17 | add_executable ( 18 | ${BINARY_NAME} 19 | src/main.cpp 20 | src/operators.cpp 21 | src/CmdRegistry.cpp 22 | src/quatMaths.cpp 23 | src/ButtonHelp.cpp 24 | src/DigitalButton.cpp 25 | src/MotionImpl.cpp 26 | src/Mapping.cpp 27 | src/TriggerEffectGenerator.cpp 28 | src/AutoLoad.cpp 29 | src/AutoConnect.cpp 30 | src/SettingsManager.cpp 31 | src/Stick.cpp 32 | src/JoyShock.cpp 33 | include/TriggerEffectGenerator.h 34 | include/InputHelpers.h 35 | include/PlatformDefinitions.h 36 | include/TrayIcon.h 37 | include/Whitelister.h 38 | include/CmdRegistry.h 39 | include/JSMAssignment.hpp 40 | include/JSMVariable.hpp 41 | include/Whitelister.h 42 | include/JoyShockMapper.h 43 | include/ColorCodes.h 44 | include/MotionIf.h 45 | include/Gamepad.h 46 | include/DigitalButton.h 47 | include/JslWrapper.h 48 | include/Mapping.h 49 | include/AutoLoad.h 50 | include/AutoConnect.h 51 | include/SettingsManager.h 52 | include/Stick.h 53 | include/JoyShock.h 54 | ) 55 | 56 | if (WINDOWS) 57 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${BINARY_NAME}) 58 | 59 | if(SDL) 60 | target_sources ( 61 | ${BINARY_NAME} PRIVATE 62 | src/SDL2Wrapper.cpp 63 | ) 64 | add_definitions(-DSDL2) 65 | else() 66 | target_sources ( 67 | ${BINARY_NAME} PRIVATE 68 | src/JslWrapper.cpp 69 | ) 70 | endif() 71 | 72 | target_sources ( 73 | ${BINARY_NAME} PRIVATE 74 | src/win32/InputHelpers.cpp 75 | src/win32/PlatformDefinitions.cpp 76 | src/win32/WindowsTrayIcon.cpp include/win32/WindowsTrayIcon.h 77 | src/win32/Gamepad.cpp 78 | src/win32/HidHideApi.cpp include/HidHideApi.h 79 | src/win32/HidHideWhitelister.cpp 80 | "Win32 Dialog.rc" include/win32/resource.h 81 | ) 82 | 83 | # ViGEmClient 84 | CPMAddPackage ( 85 | NAME ViGEmClient 86 | GITHUB_REPOSITORY ViGEm/ViGEmClient 87 | GIT_TAG 726404ef5590ea4bfcc7d9fa40cadcb2638a5b82 # updated cmakefile 88 | # VERSION 1.21.222.0 89 | ) 90 | 91 | add_dependencies(${BINARY_NAME} ViGEmClient) 92 | 93 | target_link_libraries ( 94 | ${BINARY_NAME} PRIVATE 95 | ViGEmClient 96 | ) 97 | 98 | target_include_directories ( 99 | ${BINARY_NAME} PUBLIC 100 | "${ViGEmClient_SOURCE_DIR}/include" 101 | ) 102 | 103 | if(SDL) 104 | add_custom_command( 105 | TARGET ${BINARY_NAME} 106 | POST_BUILD 107 | COMMAND ${CMAKE_COMMAND} -E copy 108 | "$" 109 | "$/$" 110 | ) 111 | else() 112 | add_custom_command( 113 | TARGET ${BINARY_NAME} 114 | POST_BUILD 115 | COMMAND ${CMAKE_COMMAND} -E copy 116 | "$" 117 | "$/$" 118 | ) 119 | endif() 120 | 121 | set_target_properties ( 122 | ${BINARY_NAME} PROPERTIES 123 | WIN32_EXECUTABLE ON 124 | VS_DEBUGGER_WORKING_DIRECTORY "$(ProjectDir)/$(Configuration)/" 125 | VS_DEBUGGER_COMMAND_ARGUMENTS "$(SolutionDir)../dist" 126 | ) 127 | 128 | add_definitions(/bigobj) # /experimental:module 129 | endif () 130 | 131 | if (LINUX) 132 | if(SDL OR NOT DEFINED SDL) 133 | target_sources ( 134 | ${BINARY_NAME} PRIVATE 135 | src/SDL2Wrapper.cpp 136 | ) 137 | add_definitions(-DSDL2) 138 | else() 139 | target_sources ( 140 | ${BINARY_NAME} PRIVATE 141 | src/JslWrapper.cpp 142 | ) 143 | endif() 144 | 145 | target_sources ( 146 | ${BINARY_NAME} PRIVATE 147 | src/linux/Init.cpp 148 | src/linux/InputHelpers.cpp 149 | src/linux/PlatformDefinitions.cpp 150 | src/linux/StatusNotifierItem.cpp include/linux/StatusNotifierItem.h 151 | src/linux/Whitelister.cpp 152 | src/linux/Gamepad.cpp 153 | ) 154 | endif () 155 | 156 | target_compile_definitions ( 157 | ${BINARY_NAME} PRIVATE 158 | -DAPPLICATION_NAME="JoyShockMapper" 159 | -DAPPLICATION_RDN="com.github." 160 | ) 161 | 162 | target_include_directories ( 163 | ${BINARY_NAME} PUBLIC 164 | "${CMAKE_CURRENT_SOURCE_DIR}/include" 165 | "${PROJECT_BINARY_DIR}/${BINARY_NAME}/include" 166 | ) 167 | 168 | if(SDL OR NOT DEFINED SDL) 169 | 170 | set(SDL_HIDAPI ON) 171 | set(SDL2_DISABLE_SDL2MAIN ON) 172 | set(SDL2_DISABLE_UNINSTALL ON) 173 | set(SDL_TEST OFF) 174 | CPMAddPackage ( 175 | NAME SDL2 176 | GITHUB_REPOSITORY libsdl-org/SDL 177 | GIT_TAG release-2.30.11 178 | ) 179 | set_target_properties(sdl_headers_copy PROPERTIES FOLDER "SDL2") 180 | set_target_properties(SDL2 PROPERTIES FOLDER "SDL2") 181 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 182 | 183 | target_link_libraries ( 184 | ${BINARY_NAME} PRIVATE 185 | Platform::Dependencies 186 | SDL2 187 | ) 188 | 189 | 190 | install ( 191 | TARGETS ${BINARY_NAME} SDL2 192 | RUNTIME DESTINATION ${PACKAGE_DIR} 193 | ) 194 | else() 195 | # JoyShockLibrary 196 | CPMAddPackage ( 197 | NAME JoyShockLibrary 198 | GITHUB_REPOSITORY JibbSmart/JoyShockLibrary 199 | GIT_TAG eba751b6bddf5edc783790af35b663dec7495dcc 200 | ) 201 | 202 | target_link_libraries ( 203 | ${BINARY_NAME} PRIVATE 204 | Platform::Dependencies 205 | JoyShockLibrary 206 | ) 207 | 208 | install ( 209 | TARGETS ${BINARY_NAME} JoyShockLibrary 210 | RUNTIME DESTINATION ${PACKAGE_DIR} 211 | ) 212 | endif() 213 | 214 | 215 | # magic_enum 216 | CPMAddPackage ( 217 | NAME magic_enum 218 | GITHUB_REPOSITORY jamek/magic_enum 219 | GIT_TAG 47e34ada93e0bf70dcea551636755cd66d893768 220 | ) 221 | 222 | target_link_libraries ( 223 | ${BINARY_NAME} PRIVATE 224 | Platform::Dependencies 225 | magic_enum 226 | ) 227 | 228 | # pocket_fsm 229 | CPMAddPackage ( 230 | NAME pocket_fsm 231 | GITHUB_REPOSITORY Electronicks/pocket_fsm 232 | GIT_TAG e447ec24c7a547bd1fbe8d964baa866a9cf146c8 233 | ) 234 | 235 | target_link_libraries ( 236 | ${BINARY_NAME} PRIVATE 237 | Platform::Dependencies 238 | pocket_fsm 239 | ) 240 | 241 | # GamepadMotionHelpers 242 | CPMAddPackage ( 243 | NAME GamepadMotionHelpers 244 | GITHUB_REPOSITORY JibbSmart/GamepadMotionHelpers 245 | GIT_TAG 39b578aacf34c3a1c584d8f7f194adc776f88055 246 | ) 247 | 248 | target_link_libraries ( 249 | ${BINARY_NAME} PRIVATE 250 | Platform::Dependencies 251 | GamepadMotionHelpers 252 | ) 253 | -------------------------------------------------------------------------------- /JoyShockMapper/JoyShockMapper.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/JoyShockMapper.ico -------------------------------------------------------------------------------- /JoyShockMapper/Win32 Dialog.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "win32/resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #define APSTUDIO_HIDDEN_SYMBOLS 11 | #include "windows.h" 12 | #undef APSTUDIO_HIDDEN_SYMBOLS 13 | 14 | ///////////////////////////////////////////////////////////////////////////// 15 | #undef APSTUDIO_READONLY_SYMBOLS 16 | 17 | ///////////////////////////////////////////////////////////////////////////// 18 | // English (United States) resources 19 | 20 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 21 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 22 | #pragma code_page(1252) 23 | 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // Icon 27 | // 28 | 29 | // Icon with lowest ID value placed first to ensure application icon 30 | // remains consistent on all systems. 31 | IDI_STEALTHDLG ICON "JoyShockMapper.ico" 32 | 33 | 34 | ///////////////////////////////////////////////////////////////////////////// 35 | // 36 | // Accelerator 37 | // 38 | 39 | IDC_STEALTHDIALOG ACCELERATORS 40 | BEGIN 41 | "?", IDM_ABOUT, ASCII, ALT 42 | "/", IDM_ABOUT, ASCII, ALT 43 | END 44 | 45 | 46 | ///////////////////////////////////////////////////////////////////////////// 47 | // 48 | // Dialog 49 | // 50 | 51 | IDD_DLG_DIALOG DIALOGEX 0, 0, 178, 92 52 | STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU 53 | EXSTYLE WS_EX_APPWINDOW 54 | CAPTION "Not Much Here" 55 | FONT 8, "MS Shell Dlg", 0, 0, 0x1 56 | BEGIN 57 | DEFPUSHBUTTON "Done",IDOK,63,35,50,16 58 | END 59 | 60 | 61 | #ifdef APSTUDIO_INVOKED 62 | ///////////////////////////////////////////////////////////////////////////// 63 | // 64 | // TEXTINCLUDE 65 | // 66 | 67 | 1 TEXTINCLUDE 68 | BEGIN 69 | "resource.h\0" 70 | END 71 | 72 | 2 TEXTINCLUDE 73 | BEGIN 74 | "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" 75 | "#include ""windows.h""\r\n" 76 | "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" 77 | "\0" 78 | END 79 | 80 | 3 TEXTINCLUDE 81 | BEGIN 82 | "\r\n" 83 | "\0" 84 | END 85 | 86 | #endif // APSTUDIO_INVOKED 87 | 88 | 89 | ///////////////////////////////////////////////////////////////////////////// 90 | // 91 | // DESIGNINFO 92 | // 93 | 94 | #ifdef APSTUDIO_INVOKED 95 | GUIDELINES DESIGNINFO 96 | BEGIN 97 | IDD_DLG_DIALOG, DIALOG 98 | BEGIN 99 | LEFTMARGIN, 7 100 | RIGHTMARGIN, 171 101 | TOPMARGIN, 7 102 | BOTTOMMARGIN, 85 103 | END 104 | END 105 | #endif // APSTUDIO_INVOKED 106 | 107 | #endif // English (United States) resources 108 | ///////////////////////////////////////////////////////////////////////////// 109 | 110 | 111 | 112 | #ifndef APSTUDIO_INVOKED 113 | ///////////////////////////////////////////////////////////////////////////// 114 | // 115 | // Generated from the TEXTINCLUDE 3 resource. 116 | // 117 | 118 | 119 | ///////////////////////////////////////////////////////////////////////////// 120 | #endif // not APSTUDIO_INVOKED 121 | 122 | -------------------------------------------------------------------------------- /JoyShockMapper/doc/ButtonStateMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/doc/ButtonStateMachine.png -------------------------------------------------------------------------------- /JoyShockMapper/doc/DualStageTriggerStateMachine.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/doc/DualStageTriggerStateMachine.PNG -------------------------------------------------------------------------------- /JoyShockMapper/doc/JSM Refactoring changes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/doc/JSM Refactoring changes.pdf -------------------------------------------------------------------------------- /JoyShockMapper/gyro_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/gyro_icon.png -------------------------------------------------------------------------------- /JoyShockMapper/include/AutoConnect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "InputHelpers.h" 3 | #include "JslWrapper.h" 4 | 5 | 6 | namespace JSM 7 | { 8 | 9 | class AutoConnect : public PollingThread 10 | { 11 | public: 12 | AutoConnect(shared_ptr joyshock, bool start); 13 | virtual ~AutoConnect() = default; 14 | 15 | private: 16 | bool AutoConnectPoll(void* param); 17 | shared_ptr jsl; 18 | int lastSize = 0; 19 | }; 20 | 21 | } //JSM -------------------------------------------------------------------------------- /JoyShockMapper/include/AutoLoad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "InputHelpers.h" 3 | 4 | class CmdRegistry; 5 | 6 | 7 | namespace JSM 8 | { 9 | 10 | class AutoLoad : public PollingThread 11 | { 12 | public: 13 | AutoLoad(CmdRegistry* commandRegistry, bool start); 14 | 15 | virtual ~AutoLoad() = default; 16 | 17 | private: 18 | bool AutoLoadPoll(void* param); 19 | }; 20 | 21 | } //JSM -------------------------------------------------------------------------------- /JoyShockMapper/include/CmdRegistry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // This is a base class for any Command line operation. It binds a command name to a parser function 11 | // Derivatives from this class have a default parser function and performs specific operations. 12 | class JSMCommand 13 | { 14 | public: 15 | // A parser function has a pointer to the command being processed and 16 | // the data string to process. It returns whether the data was recognized or not. 17 | typedef function ParseDelegate; 18 | 19 | // Assignments can be given tasks to perform before destroying themselves. 20 | // This is used for chorded press, sim presses and modeshifts to remove 21 | // themselves from their host variable when assigned NONE 22 | typedef function TaskOnDestruction; 23 | 24 | protected: 25 | // Parse functor to be assigned by derived class or overwritten 26 | // Use setter to assign 27 | ParseDelegate _parse; 28 | 29 | // help string to display about the command. Cannot be changed after construction 30 | string _help; 31 | 32 | // Some task to perform when this object is destroyed 33 | TaskOnDestruction _taskOnDestruction; 34 | 35 | public: 36 | // Name of the command. Cannot be changed after construction. 37 | // I don't mind leaving this public since it can't be changed. 38 | const string _name; 39 | 40 | // The constructor should only have mandatory arguments. optional arguments are assigned using setters. 41 | JSMCommand(string_view name); 42 | 43 | virtual ~JSMCommand(); 44 | 45 | // A parser needs to be assigned for the command to be run without crashing. 46 | // It returns a pointer to itself for setter chaining. 47 | virtual JSMCommand* setParser(ParseDelegate parserFunction); 48 | 49 | // Assign a help description to the command. 50 | // It returns a pointer to itself for setter chaining. 51 | virtual JSMCommand* setHelp(string_view commandDescription); 52 | 53 | virtual unique_ptr getModifiedCmd(char op, string_view chord); 54 | 55 | // Get the help string for the command. 56 | inline string_view help() const 57 | { 58 | return _help; 59 | } 60 | 61 | // Set a task to perform when this command is destroyed 62 | inline JSMCommand* setTaskOnDestruction(const TaskOnDestruction& task) 63 | { 64 | _taskOnDestruction = task; 65 | return this; 66 | } 67 | 68 | // Request this command to parse the command arguments. Returns true if the command was processed. 69 | virtual bool parseData(string_view arguments, string_view label); 70 | }; 71 | 72 | // The command registry holds all JSMCommands object and should not care what the derived type is. 73 | // It's capable of recognizing a command and requesting it to process arguments. That's it. 74 | // It uses regular expression to breakup a command string in its various components. 75 | // Currently it refuses to accept different commands with the same name but there's an 76 | // argument to be made to use the return value of JSMCommand::parseData() to attempt multiple 77 | // commands until one returns true. This can enable multiple parsers for the same command. 78 | class CmdRegistry 79 | { 80 | private: 81 | typedef multimap> CmdMap; 82 | 83 | // multimap allows multiple entries with the same keys 84 | CmdMap _registry; 85 | 86 | static string_view strtrim(string_view str); 87 | 88 | static bool findCommandWithName(string_view name, const CmdMap::value_type& pair); 89 | 90 | public: 91 | CmdRegistry(); 92 | 93 | // Not string_view because the string is modified inside 94 | bool loadConfigFile(string fileName); 95 | 96 | // Add a command to the registry. The regisrty takes ownership of the memory of this pointer. 97 | // You can use _ASSERT() on the return value of this function to make sure the commands are 98 | // accepted. 99 | bool add(JSMCommand* newCommand); 100 | 101 | bool Remove(string_view name); 102 | 103 | bool hasCommand(string_view name) const; 104 | 105 | bool isCommandValid(string_view line) const; 106 | 107 | // Process a command entered by the user 108 | // intentionally dont't use const ref 109 | void processLine(const string& line); 110 | 111 | // Fill vector with registered command names 112 | void GetCommandList(vector& outList) const; 113 | 114 | // Return help string for provided command 115 | string_view GetHelp(string_view command) const; 116 | }; 117 | 118 | // Macro commands are simple function calls when recognized. But it could do different things 119 | // by processing arguments given to it. 120 | class JSMMacro : public JSMCommand 121 | { 122 | public: 123 | // A Macro function has it's command object passed as argument. 124 | typedef function MacroDelegate; 125 | 126 | protected: 127 | // The macro function to call when the command is recognized. 128 | MacroDelegate _macro; 129 | 130 | // The default parser for the command processes no arguments. 131 | static bool DefaultParser(JSMCommand* cmd, string_view arguments, string_view label); 132 | 133 | public: 134 | JSMMacro(string_view name); 135 | 136 | // Assign a Macro function to run. It returns a pointer to itself 137 | // to chain setters. 138 | JSMMacro* SetMacro(MacroDelegate macroFunction); 139 | }; 140 | -------------------------------------------------------------------------------- /JoyShockMapper/include/ColorCodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // https://www.rapidtables.com/web/color/RGB_Color.html#color-table 8 | // Can't use magic enum because it only supports base type up to 16b and I need 32b 9 | static map colorCodeMap{ 10 | { "OFF", 0x000000 }, 11 | { "MAROON", 0x800000 }, 12 | { "DARK_RED", 0x8B0000 }, 13 | { "BROWN", 0xA52A2A }, 14 | { "FIREBRICK", 0xB22222 }, 15 | { "CRIMSON", 0xDC143C }, 16 | { "RED", 0xFF0000 }, 17 | { "TOMATO", 0xFF6347 }, 18 | { "CORAL", 0xFF7F50 }, 19 | { "INDIAN_RED", 0xCD5C5C }, 20 | { "LIGHT_CORAL", 0xF08080 }, 21 | { "DARK_SALMON", 0xE9967A }, 22 | { "SALMON", 0xFA8072 }, 23 | { "LIGHT_SALMON", 0xFFA07A }, 24 | { "ORANGE_RED", 0xFF4500 }, 25 | { "DARK_ORANGE", 0xFF8C00 }, 26 | { "ORANGE", 0xFFA500 }, 27 | { "GOLD", 0xFFD700 }, 28 | { "DARK_GOLDEN_ROD", 0xB8860B }, 29 | { "GOLDEN_ROD", 0xDAA520 }, 30 | { "PALE_GOLDEN_ROD", 0xEEE8AA }, 31 | { "DARK_KHAKI", 0xBDB76B }, 32 | { "KHAKI", 0xF0E68C }, 33 | { "OLIVE", 0x808000 }, 34 | { "YELLOW", 0xFFFF00 }, 35 | { "YELLOW_GREEN", 0x9ACD32 }, 36 | { "DARK_OLIVE_GREEN", 0x556B2F }, 37 | { "OLIVE_DRAB", 0x6B8E23 }, 38 | { "LAWN_GREEN", 0x7CFC00 }, 39 | { "CHART_REUSE", 0x7FFF00 }, 40 | { "GREEN_YELLOW", 0xADFF2F }, 41 | { "DARK_GREEN", 0x006400 }, 42 | { "GREEN", 0x008000 }, 43 | { "FOREST_GREEN", 0x228B22 }, 44 | { "LIME", 0x00FF00 }, 45 | { "LIME_GREEN", 0x32CD32 }, 46 | { "LIGHT_GREEN", 0x90EE90 }, 47 | { "PALE_GREEN", 0x98FB98 }, 48 | { "DARK_SEA_GREEN", 0x8FBC8F }, 49 | { "MEDIUM_SPRING_GREEN", 0x00FA9A }, 50 | { "SPRING_GREEN", 0x00FF7F }, 51 | { "SEA_GREEN", 0x2E8B57 }, 52 | { "MEDIUM_AQUA_MARINE", 0x66CDAA }, 53 | { "MEDIUM_SEA_GREEN", 0x3CB371 }, 54 | { "LIGHT_SEA_GREEN", 0x20B2AA }, 55 | { "DARK_SLATE_GRAY", 0x2F4F4F }, 56 | { "TEAL", 0x008080 }, 57 | { "DARK_CYAN", 0x008B8B }, 58 | { "AQUA", 0x00FFFF }, 59 | { "CYAN", 0x00FFFF }, 60 | { "LIGHT_CYAN", 0xE0FFFF }, 61 | { "DARK_TURQUOISE", 0x00CED1 }, 62 | { "TURQUOISE", 0x40E0D0 }, 63 | { "MEDIUM_TURQUOISE", 0x48D1CC }, 64 | { "PALE_TURQUOISE", 0xAFEEEE }, 65 | { "AQUA_MARINE", 0x7FFFD4 }, 66 | { "POWDER_BLUE", 0xB0E0E6 }, 67 | { "CADET_BLUE", 0x5F9EA0 }, 68 | { "STEEL_BLUE", 0x4682B4 }, 69 | { "CORN_FLOWER_BLUE", 0x6495ED }, 70 | { "DEEP_SKY_BLUE", 0x00BFFF }, 71 | { "DODGER_BLUE", 0x1E90FF }, 72 | { "LIGHT_BLUE", 0xADD8E6 }, 73 | { "SKY_BLUE", 0x87CEEB }, 74 | { "LIGHT_SKY_BLUE", 0x87CEFA }, 75 | { "MIDNIGHT_BLUE", 0x191970 }, 76 | { "NAVY", 0x000080 }, 77 | { "DARK_BLUE", 0x00008B }, 78 | { "MEDIUM_BLUE", 0x0000CD }, 79 | { "BLUE", 0x0000FF }, 80 | { "ROYAL_BLUE", 0x4169E1 }, 81 | { "BLUE_VIOLET", 0x8A2BE2 }, 82 | { "INDIGO", 0x4B0082 }, 83 | { "DARK_SLATE_BLUE", 0x483D8B }, 84 | { "SLATE_BLUE", 0x6A5ACD }, 85 | { "MEDIUM_SLATE_BLUE", 0x7B68EE }, 86 | { "MEDIUM_PURPLE", 0x9370DB }, 87 | { "DARK_MAGENTA", 0x8B008B }, 88 | { "DARK_VIOLET", 0x9400D3 }, 89 | { "DARK_ORCHID", 0x9932CC }, 90 | { "MEDIUM_ORCHID", 0xBA55D3 }, 91 | { "PURPLE", 0x800080 }, 92 | { "THISTLE", 0xD8BFD8 }, 93 | { "PLUM", 0xDDA0DD }, 94 | { "VIOLET", 0xEE82EE }, 95 | { "MAGENTA", 0xFF00FF }, 96 | { "FUCHSIA", 0xFF00FF }, 97 | { "ORCHID", 0xDA70D6 }, 98 | { "MEDIUM_VIOLET_RED", 0xC71585 }, 99 | { "PALE_VIOLET_RED", 0xDB7093 }, 100 | { "DEEP_PINK", 0xFF1493 }, 101 | { "HOT_PINK", 0xFF69B4 }, 102 | { "LIGHT_PINK", 0xFFB6C1 }, 103 | { "PINK", 0xFFC0CB }, 104 | { "ANTIQUE_WHITE", 0xFAEBD7 }, 105 | { "BEIGE", 0xF5F5DC }, 106 | { "BISQUE", 0xFFE4C4 }, 107 | { "BLANCHED_ALMOND", 0xFFEBCD }, 108 | { "WHEAT", 0xF5DEB3 }, 109 | { "CORN_SILK", 0xFFF8DC }, 110 | { "LEMON_CHIFFON", 0xFFFACD }, 111 | { "LIGHT_GOLDEN_ROD_YELLOW", 0xFAFAD2 }, 112 | { "LIGHT_YELLOW", 0xFFFFE0 }, 113 | { "SADDLE_BROWN", 0x8B4513 }, 114 | { "SIENNA", 0xA0522D }, 115 | { "CHOCOLATE", 0xD2691E }, 116 | { "PERU", 0xCD853F }, 117 | { "SANDY_BROWN", 0xF4A460 }, 118 | { "BURLY_WOOD", 0xDEB887 }, 119 | { "TAN", 0xD2B48C }, 120 | { "ROSY_BROWN", 0xBC8F8F }, 121 | { "MOCCASIN", 0xFFE4B5 }, 122 | { "NAVAJO_WHITE", 0xFFDEAD }, 123 | { "PEACH_PUFF", 0xFFDAB9 }, 124 | { "MISTY_ROSE", 0xFFE4E1 }, 125 | { "LAVENDER_BLUSH", 0xFFF0F5 }, 126 | { "LINEN", 0xFAF0E6 }, 127 | { "OLD_LACE", 0xFDF5E6 }, 128 | { "PAPAYA_WHIP", 0xFFEFD5 }, 129 | { "SEA_SHELL", 0xFFF5EE }, 130 | { "MINT_CREAM", 0xF5FFFA }, 131 | { "SLATE_GRAY", 0x708090 }, 132 | { "LIGHT_SLATE_GRAY", 0x778899 }, 133 | { "LIGHT_STEEL_BLUE", 0xB0C4DE }, 134 | { "LAVENDER", 0xE6E6FA }, 135 | { "FLORAL_WHITE", 0xFFFAF0 }, 136 | { "ALICE_BLUE", 0xF0F8FF }, 137 | { "GHOST_WHITE", 0xF8F8FF }, 138 | { "HONEYDEW", 0xF0FFF0 }, 139 | { "IVORY", 0xFFFFF0 }, 140 | { "AZURE", 0xF0FFFF }, 141 | { "SNOW", 0xFFFAFA }, 142 | { "BLACK", 0x000000 }, 143 | { "DIM_GRAY", 0x696969 }, 144 | { "DIM_GREY", 0x696969 }, 145 | { "GRAY", 0x808080 }, 146 | { "GREY", 0x808080 }, 147 | { "DARK_GRAY", 0xA9A9A9 }, 148 | { "DARK_GREY", 0xA9A9A9 }, 149 | { "SILVER", 0xC0C0C0 }, 150 | { "LIGHT_GRAY", 0xD3D3D3 }, 151 | { "LIGHT_GREY", 0xD3D3D3 }, 152 | { "GAINSBORO", 0xDCDCDC }, 153 | { "WHITE_SMOKE", 0xF5F5F5 }, 154 | { "WHITE", 0xFFFFFF }, 155 | }; 156 | -------------------------------------------------------------------------------- /JoyShockMapper/include/DigitalButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pocket_fsm.h" 4 | #include "JoyShockMapper.h" 5 | #include "Gamepad.h" 6 | #include "MotionIf.h" 7 | #include 8 | #include 9 | #include 10 | 11 | // Forward declarations 12 | class JSMButton; 13 | class DigitalButton; // Finite State Machine 14 | struct DigitalButtonImpl; // Button implementation 15 | class MapIterator; 16 | 17 | // The enum values match the concrete class names 18 | enum class BtnState 19 | { 20 | NoPress, 21 | BtnPress, 22 | TapPress, 23 | WaitSim, 24 | SimPressMaster, 25 | SimPressSlave, 26 | SimRelease, 27 | DiagPressMaster, 28 | DiagPressSlave, 29 | DblPressStart, 30 | DblPressNoPress, 31 | DblPressNoPressTap, 32 | DblPressNoPressHold, 33 | DblPressPress, 34 | InstRelease, 35 | INVALID 36 | }; 37 | 38 | // Send this event anytime the button is pressed down or active 39 | struct Pressed 40 | { 41 | chrono::steady_clock::time_point time_now; // Timestamp of the poll 42 | float turboTime = 0.f; // active turbo period setting in ms 43 | float holdTime = 0.f; // active hold press setting in ms 44 | float dblPressWindow = 0.f; // active dbl press window setting in ms 45 | }; 46 | 47 | // Send this event anytime the button is at rest or inactive 48 | struct Released 49 | { 50 | chrono::steady_clock::time_point time_now; // Timestamp of the poll 51 | float turboTime = 0.f; // active turbo period setting in ms 52 | float holdTime = 0.f; // active hold press setting in ms 53 | float dblPressWindow = 0.f; // active dbl press window setting in ms 54 | }; 55 | 56 | // The sync event is created internally 57 | struct Sync; 58 | 59 | // Getter for the button press duration. Pass timestamp of the poll. 60 | struct GetDuration 61 | { 62 | chrono::steady_clock::time_point in_now; 63 | float out_duration; 64 | }; 65 | 66 | // Setter for the press time 67 | typedef chrono::steady_clock::time_point SetPressTime; 68 | 69 | // A basic digital button state reacts to the following events 70 | class DigitalButtonState : public pocket_fsm::StatePimplIF 71 | { 72 | BASE_STATE(DigitalButtonState) 73 | 74 | // Display logs on entry for debigging 75 | REACT(OnEntry) 76 | override; 77 | 78 | // ignored 79 | REACT(OnExit) 80 | override { } 81 | 82 | // Adds chord to stack if absent 83 | REACT(Pressed); 84 | 85 | // Remove chord from stack if present 86 | REACT(Released); 87 | 88 | // ignored by default 89 | REACT(Sync) { } 90 | 91 | // Always assign press time 92 | REACT(SetPressTime) 93 | final; 94 | 95 | // Return press duration 96 | REACT(GetDuration) 97 | final; 98 | 99 | // Get matching enum value 100 | virtual BtnState getState() const = 0; 101 | 102 | virtual void swapPimpl(DigitalButtonState& otherState) 103 | { 104 | _pimpl.swap(otherState._pimpl); 105 | } 106 | 107 | virtual void resetPimpl(DigitalButtonState & otherState) 108 | { 109 | _pimpl = otherState._pimpl; 110 | } 111 | }; 112 | 113 | // Feed this state machine with Pressed and Released events and it will sort out 114 | // what mappings to activate internally 115 | class DigitalButton : public pocket_fsm::FiniteStateMachine 116 | { 117 | public: 118 | // All digital _buttons need a reference to the same instance of the common structure within the same controller. 119 | // It enables the _buttons to synchronize and be aware of the state of the whole controller, access gyro etc... 120 | struct Context 121 | { 122 | Context(Gamepad::Callback virtualControllerCallback, shared_ptr mainMotion); 123 | deque> gyroActionQueue; // Queue of gyro control actions currently in effect 124 | deque> activeTogglesQueue; 125 | deque chordStack; // Represents the current active _buttons in order from most recent to latest 126 | unique_ptr _vigemController; 127 | function _getMatchingSimBtn; // A functor to JoyShock::getMatchingSimBtn 128 | function&)> _getMatchingDiagBtn; // A functor to JoyShock::getMatchingDiagBtn 129 | function _rumble; // A functor to JoyShock::sendRumble 130 | mutex callback_lock; // Needs to be in the common struct for both joycons to use the same 131 | shared_ptr rightMainMotion = nullptr; 132 | shared_ptr leftMotion = nullptr; 133 | int nn = 0; 134 | 135 | void updateChordStack(bool isPressed, ButtonID index); 136 | }; 137 | 138 | DigitalButton(shared_ptr _context, JSMButton &mapping); 139 | 140 | const ButtonID _id; 141 | 142 | // Get the enum identifier of the current state 143 | BtnState getState() const 144 | { 145 | return getCurrentState()->getState(); 146 | } 147 | 148 | void swapState(DigitalButton& otherBtn) 149 | { 150 | // Swap just the state, but leave the pimpls in their respective button 151 | _currentState->swapPimpl(*otherBtn._currentState); 152 | _currentState.swap(otherBtn._currentState); 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /JoyShockMapper/include/Gamepad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "PlatformDefinitions.h" 5 | 6 | struct Indicator 7 | { 8 | uint8_t led; 9 | uint8_t rgb[3]; 10 | }; 11 | 12 | class Gamepad 13 | { 14 | 15 | public: 16 | typedef function Callback; 17 | 18 | protected: 19 | Gamepad(); 20 | 21 | public: 22 | static Gamepad* getNew(ControllerScheme scheme, Callback notification = nullptr); 23 | static inline size_t getCount() 24 | { 25 | return _count; 26 | } 27 | 28 | virtual ~Gamepad(); 29 | 30 | virtual bool isInitialized(string* errorMsg = nullptr) const = 0; 31 | inline string getError() const 32 | { 33 | return _errorMsg; 34 | } 35 | 36 | virtual void setButton(KeyCode btn, bool pressed) = 0; 37 | virtual void setLeftStick(float x, float y) = 0; 38 | virtual void setRightStick(float x, float y) = 0; 39 | virtual void setStick(float x, float y, bool isLeft) = 0; 40 | virtual void setLeftTrigger(float) = 0; 41 | virtual void setRightTrigger(float) = 0; 42 | virtual void setGyro(float accelX, float accelY, float accelZ, float gyroX, float gyroY, float gyroZ) = 0; 43 | virtual void setTouchState(optional press1, optional press2) = 0; 44 | virtual void update() = 0; 45 | 46 | virtual ControllerScheme getType() const = 0; 47 | 48 | protected: 49 | string _errorMsg; 50 | static size_t _count; 51 | }; 52 | -------------------------------------------------------------------------------- /JoyShockMapper/include/HidHideApi.h: -------------------------------------------------------------------------------- 1 | // (c) Eric Korff de Gidts 2 | // SPDX-License-Identifier: MIT 3 | // Config.h 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #pragma comment(lib, "Hid.lib") 17 | 18 | namespace HidHide 19 | { 20 | struct HidDeviceInstancePathWithModelInfo 21 | { 22 | HIDD_ATTRIBUTES attributes{}; 23 | HIDP_CAPS capabilities{}; 24 | bool present{}; 25 | bool gamingDevice{}; 26 | std::wstring vendor{}; 27 | std::wstring product{}; 28 | std::wstring serialNumber{}; 29 | std::wstring usage{}; 30 | std::wstring description{}; 31 | std::wstring deviceInstancePath{}; 32 | std::wstring deviceInstancePathBaseContainer{}; 33 | GUID deviceInstancePathBaseContainerClassGuid{}; 34 | size_t deviceInstancePathBaseContainerDeviceCount{}; 35 | }; 36 | 37 | typedef std::vector HidDeviceInstancePathsWithModelInfo; 38 | typedef std::vector HidDeviceInstancePaths; 39 | typedef std::filesystem::path FullImageName; 40 | typedef std::vector FullImageNames; 41 | typedef std::vector> DescriptionToHidDeviceInstancePathsWithModelInfo; 42 | 43 | // Lookup a value from the string table resource based on the resource id provided 44 | std::wstring StringTable(_In_ uint32_t stringTableResourceId); 45 | 46 | // Create a hierarchical tree of human interface devices starting at the base container id level 47 | DescriptionToHidDeviceInstancePathsWithModelInfo GetDescriptionToHidDeviceInstancePathsWithModelInfo(); 48 | 49 | // Method converting a logical file name into a full image name 50 | FullImageName FileNameToFullImageName(_In_ std::filesystem::path const& logicalFileName); 51 | 52 | // Get the control device state; returns true when the control device is present (installed and enabled) 53 | bool Present(); 54 | 55 | // Get the device Instance Paths of the Human Interface Devices that are on the black-list (may reference not present devices) 56 | HidDeviceInstancePaths GetBlacklist(); 57 | 58 | // Set the device Instance Paths of the Human Interface Devices that are on the black-list 59 | void SetBlacklist(_In_ HidDeviceInstancePaths const& hidDeviceInstancePaths); 60 | 61 | // Get the applications on the white-list 62 | FullImageNames GetWhitelist(); 63 | 64 | // Set the applications on the white-list 65 | void SetWhitelist(_In_ FullImageNames const& fullImageNames); 66 | 67 | // Get the current enabled state; returns true when the device is active in hiding devices on the black-list 68 | bool GetActive(); 69 | 70 | // Set the current enabled state 71 | void SetActive(_In_ bool active); 72 | } 73 | -------------------------------------------------------------------------------- /JoyShockMapper/include/InputHelpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "PlatformDefinitions.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // get the user's mouse sensitivity multiplier from the user. In Windows it's an int, but who cares? it's well within range for float to represent it exactly 15 | // also, if this is ported to other platforms, we might want non-integer sensitivities 16 | float getMouseSpeed(); 17 | 18 | // send mouse button 19 | int pressMouse(KeyCode vkKey, bool isPressed); 20 | 21 | // send key press 22 | int pressKey(KeyCode vkKey, bool pressed); 23 | 24 | void moveMouse(float x, float y); 25 | 26 | void setMouseNorm(float x, float y); 27 | 28 | // delta time will apply to shaped movement, but the extra (velocity parameters after deltaTime) is 29 | // applied as given 30 | inline void shapedSensitivityMoveMouse(float x, float y, float deltaTime, float extraVelocityX, float extraVelocityY) 31 | { 32 | // apply all values 33 | moveMouse(x * deltaTime + extraVelocityX, y * deltaTime + extraVelocityY); 34 | } 35 | 36 | BOOL WriteToConsole(string_view command); 37 | 38 | BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType); 39 | 40 | // just setting up the console with standard stuff 41 | void initConsole(); 42 | 43 | tuple GetActiveWindowName(); 44 | 45 | vector ListDirectory(string directory); 46 | 47 | string GetCWD(); 48 | 49 | bool SetCWD(string_view newCWD); 50 | 51 | class PollingThread 52 | { 53 | public: 54 | PollingThread(const char *label, function loopContent, 55 | void *funcParam, 56 | DWORD pollPeriodMs, 57 | bool startNow) 58 | : _label(label) 59 | , _thread() 60 | , _loopContent(loopContent) 61 | , _sleepTimeMs(pollPeriodMs) 62 | , _funcParam(funcParam) 63 | , _continue(false) 64 | { 65 | if (startNow) 66 | Start(); 67 | } 68 | 69 | virtual ~PollingThread() 70 | { 71 | if (_continue) 72 | { 73 | Stop(); 74 | } 75 | if (_thread) 76 | { 77 | _thread->join(); 78 | _thread.reset(); 79 | } 80 | // Let poll function cleanup 81 | } 82 | 83 | inline operator bool() 84 | { 85 | return _thread != nullptr; 86 | } 87 | 88 | bool Start() 89 | { 90 | if (_thread && !_continue) // thread is running but hasn't stopped yet 91 | { 92 | _thread->join(); 93 | _thread.reset(); 94 | } 95 | if (!_thread) // thread is clear 96 | { 97 | _continue = true; 98 | _thread.reset(new thread(&PollingThread::pollFunction, this)); 99 | } 100 | return isRunning(); 101 | } 102 | 103 | inline bool Stop() 104 | { 105 | _continue = false; 106 | return true; 107 | } 108 | 109 | inline bool isRunning() 110 | { 111 | return _thread && _continue; 112 | } 113 | 114 | const char *_label; 115 | 116 | private: 117 | static DWORD WINAPI pollFunction(LPVOID param) 118 | { 119 | auto workerThread = static_cast(param); 120 | if (workerThread) 121 | { 122 | while (workerThread->_continue && workerThread->_loopContent(workerThread->_funcParam)) 123 | { 124 | this_thread::sleep_for( 125 | chrono::milliseconds{ workerThread->_sleepTimeMs }); 126 | } 127 | } 128 | 129 | return 0; 130 | } 131 | 132 | private: 133 | unique_ptr _thread; 134 | function _loopContent; 135 | uint64_t _sleepTimeMs; 136 | void *_funcParam; 137 | atomic_bool _continue; 138 | }; 139 | 140 | DWORD ShowOnlineHelp(); 141 | 142 | void HideConsole(); 143 | void UnhideConsole(); 144 | 145 | void ShowConsole(); 146 | 147 | void ReleaseConsole(); 148 | 149 | bool IsVisible(); 150 | 151 | bool isConsoleMinimized(); 152 | 153 | bool ClearConsole(); 154 | -------------------------------------------------------------------------------- /JoyShockMapper/include/JSMAssignment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "CmdRegistry.h" // for JSMCommand 5 | #include "JSMVariable.hpp" 6 | #include "PlatformDefinitions.h" 7 | 8 | #include 9 | #include 10 | 11 | // This class handles any kind of assignment command by binding to a JSM variable 12 | // of the parameterized type T. If T is not a base type, implement the following 13 | // functions to reuse the defaultParser: 14 | // static istream &operator >>(istream & input, T &foobar) { ... } 15 | // static ostream &operator <<(ostream & output, T foobar) { ... } 16 | template 17 | class JSMAssignment : public JSMCommand 18 | { 19 | protected: 20 | // The display name is usually the same as the name, but in some cases it might be different. 21 | // For example the two GYRO_SENS assignment commands will display MIN_GYRO_SENS and MAX_GYRO_SENS respectively. 22 | const string _displayName; 23 | 24 | // Reference to an existing variable. Make sure the variable's lifetime is longer than 25 | // the command objects 26 | JSMVariable& _var; 27 | 28 | virtual bool parseData(string_view arguments, string_view label) override 29 | { 30 | smatch results; 31 | _ASSERT_EXPR(_parse, L"There is no function defined to parse this command."); 32 | const string argStr(arguments); 33 | if (arguments.empty()) 34 | { 35 | displayCurrentValue(); 36 | } 37 | else if (arguments.compare(0, 4, "HELP") == 0 && !_help.empty()) 38 | { 39 | // Show help. 40 | COUT << _help << '\n'; 41 | } 42 | else if (regex_match(argStr, results, regex(R"(\s*=\s*(.*))"))) 43 | { 44 | string assignment(results.empty() ? arguments : results[1].str()); 45 | if (assignment.rfind("DEFAULT", 0) == 0) 46 | { 47 | _var.reset(); 48 | } 49 | else if (!_parse(this, assignment, label)) 50 | { 51 | CERR << "Error assigning "; 52 | COUT_INFO << assignment; 53 | CERR << " to " << _displayName << '\n'; 54 | CERR << "See "; 55 | COUT_INFO << "HELP"; 56 | CERR << " and "; 57 | COUT_INFO << "README"; 58 | CERR << " commands for further details.\n"; 59 | } 60 | } 61 | else if (!_help.empty()) 62 | { 63 | // Parsing has failed. 64 | CERR << "Error when processing the assignment. See the "; 65 | COUT_INFO << "README"; 66 | CERR << " for details on valid assignment values\n"; 67 | } 68 | return true; // Command is completely processed 69 | } 70 | 71 | static bool modeshiftParser(ButtonID modeshift, JSMSetting* setting, JSMCommand::ParseDelegate* parser, JSMCommand* cmd, string_view argument, string_view label) 72 | { 73 | if (setting && argument.compare("NONE") == 0) 74 | { 75 | setting->markModeshiftForRemoval(modeshift); 76 | COUT << "Modeshift " << modeshift << "," << cmd->_name << " has been removed.\n"; 77 | return true; 78 | } 79 | return (*parser)(cmd, argument, label); 80 | } 81 | 82 | // The default parser uses the overloaded >> operator to parse 83 | // any base type. Custom types can also be extracted if you define 84 | // a static parse operation for it. 85 | static bool defaultParser(JSMCommand* cmd, string_view data, string_view label) 86 | { 87 | auto inst = dynamic_cast*>(cmd); 88 | 89 | stringstream ss(data.data()); 90 | // Read the value 91 | T value(inst->readValue(ss)); 92 | if (!ss.fail()) 93 | { 94 | T oldVal = inst->_var; 95 | inst->_var.set(value); 96 | inst->_var.updateLabel(label); 97 | 98 | // The assignment won't trigger my listener displayNewValue if 99 | // the new value after filtering is the same as the old. 100 | if (oldVal == inst->_var.value()) 101 | { 102 | // So I want to do it myself. 103 | inst->displayNewValue(inst->_var); 104 | } 105 | 106 | // Command succeeded if the value requested was the current one 107 | // or if the new value is different from the old. 108 | return value == oldVal || inst->_var.value() != oldVal; // Command processed successfully 109 | } 110 | // Couldn't read the value 111 | return false; 112 | } 113 | 114 | virtual void displayNewValue(const T& newValue) 115 | { 116 | // See Specialization for T=Mapping at the end of this file 117 | COUT << _displayName << " has been set to " << newValue << '\n'; 118 | } 119 | 120 | virtual void displayCurrentValue() 121 | { 122 | COUT << _displayName << " = " << _var.value() << '\n'; 123 | } 124 | 125 | virtual T readValue(stringstream& in) 126 | { 127 | // Default value reader 128 | T value = T(); 129 | in >> value; 130 | return value; 131 | } 132 | 133 | virtual unique_ptr getModifiedCmd(char op, string_view chord) override 134 | { 135 | stringstream ss(chord.data()); 136 | ButtonID btn; 137 | ss >> btn; 138 | if (btn > ButtonID::NONE) 139 | { 140 | stringstream name; 141 | name << chord << op << _displayName; 142 | if (op == ',') 143 | { 144 | auto settingVar = dynamic_cast*>(&_var); 145 | if (settingVar) 146 | { 147 | //Create Modeshift 148 | auto chordAssignment = make_unique>(name.str(), *settingVar->atChord(btn)); 149 | chordAssignment->setHelp(_help)->setParser(bind(&JSMAssignment::modeshiftParser, btn, settingVar, &_parse, placeholders::_1, placeholders::_2, placeholders::_3)) 150 | ->setTaskOnDestruction(bind(&JSMSetting::processModeshiftRemoval, settingVar, btn)); 151 | return chordAssignment; 152 | } 153 | auto buttonVar = dynamic_cast(&_var); 154 | if (buttonVar && btn > ButtonID::NONE) 155 | { 156 | auto chordedVar = buttonVar->atChord(btn); 157 | // The reinterpret_cast is required for compilation, but settings will never run this code anyway. 158 | auto chordAssignment = make_unique>(name.str(), reinterpret_cast&>(*chordedVar)); 159 | chordAssignment->setHelp(_help)->setParser(_parse)->setTaskOnDestruction(bind(&JSMButton::processChordRemoval, buttonVar, btn, chordedVar)); 160 | // BE ADVISED! If a custom parser was set using bind(), the very same bound vars will 161 | // be passed along. 162 | return chordAssignment; 163 | } 164 | } 165 | else if (op == '+') 166 | { 167 | auto buttonVar = dynamic_cast(&_var); 168 | if (buttonVar && btn > ButtonID::NONE) 169 | { 170 | auto simPressVar = buttonVar->atSimPress(btn); 171 | auto simAssignment = make_unique>(name.str(), *simPressVar); 172 | simAssignment->setHelp(_help)->setParser(_parse)->setTaskOnDestruction(bind(&JSMButton::processSimPressRemoval, buttonVar, btn, simPressVar)); 173 | // BE ADVISED! If a custom parser was set using bind(), the very same bound vars will 174 | // be passed along. 175 | return simAssignment; 176 | } 177 | } 178 | else if (op == '*') 179 | { 180 | auto buttonVar = dynamic_cast(&_var); 181 | if (buttonVar && btn > ButtonID::NONE) 182 | { 183 | auto diagPressVar = buttonVar->atDiagPress(btn); 184 | auto diagAssignment = make_unique>(name.str(), *diagPressVar); 185 | diagAssignment->setHelp(_help)->setParser(_parse)->setTaskOnDestruction(bind(&JSMButton::processDiagPressRemoval, buttonVar, btn, diagPressVar)); 186 | // BE ADVISED! If a custom parser was set using bind(), the very same bound vars will 187 | // be passed along. 188 | return diagAssignment; 189 | } 190 | } 191 | } 192 | return JSMCommand::getModifiedCmd(op, chord); 193 | } 194 | 195 | unsigned int _listenerId; 196 | 197 | public: 198 | JSMAssignment(string_view name, string_view displayName, JSMVariable& var, bool inNoListener = false) 199 | : JSMCommand(name) 200 | , _var(var) 201 | , _displayName(displayName) 202 | , _listenerId(0) 203 | { 204 | // Child Classes assign their own parser. Use bind to convert instance function call 205 | // into a static function call. 206 | using namespace placeholders; 207 | setParser(bind(&JSMAssignment::defaultParser, _1, _2, _3)); 208 | if (!inNoListener) 209 | { 210 | _listenerId = _var.addOnChangeListener(bind(&JSMAssignment::displayNewValue, this, placeholders::_1)); 211 | } 212 | } 213 | 214 | JSMAssignment(string_view name, JSMVariable& var, bool inNoListener = false) 215 | : JSMAssignment(name, name, var, inNoListener) 216 | { 217 | } 218 | 219 | JSMAssignment(JSMSetting& var, bool inNoListener = false) 220 | : JSMAssignment(magic_enum::enum_name(var._id).data(), var, inNoListener) 221 | { 222 | } 223 | 224 | JSMAssignment(JSMButton& var, bool inNoListener = false) 225 | : JSMAssignment(magic_enum::enum_name(var._id).data(), var, inNoListener) 226 | { 227 | } 228 | 229 | virtual ~JSMAssignment() 230 | { 231 | if (_listenerId != 0) 232 | { 233 | _var.removeOnChangeListener(_listenerId); 234 | } 235 | } 236 | 237 | // This setter enables custom parsers to perform assignments 238 | inline T operator=(T newVal) 239 | { 240 | return (_var = newVal); 241 | } 242 | }; 243 | 244 | // Specialization for Mapping 245 | template<> 246 | void JSMAssignment::displayNewValue(const Mapping& newValue) 247 | { 248 | COUT << _name << " mapped to " << newValue.description() << '\n'; 249 | } 250 | -------------------------------------------------------------------------------- /JoyShockMapper/include/JSMVersion.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Versions will be in the format A.B.C[-D] 4 | // D increases for each commit pushed after the latest release. It should not be part of any release number. 5 | // C increases when all that's happened is some bugs have been fixed. 6 | // B increases and C resets to 0 when new features have been added. 7 | // A increases and B and C reset to 0 when major new features have been added that warrant a new major version, or replacing older features with better ones that require the user to interact with them differently 8 | 9 | // Version is generated by cmake and created from latest git tag 10 | 11 | static const char* version = "@JSM_VERSION@"; -------------------------------------------------------------------------------- /JoyShockMapper/include/JoyShock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "MotionIf.h" 5 | #include "DigitalButton.h" 6 | #include "Stick.h" 7 | #include "JslWrapper.h" 8 | #include "SettingsManager.h" 9 | #include "../src/quatMaths.cpp" 10 | 11 | // An instance of this class represents a single controller device that JSM is listening to. 12 | class JoyShock 13 | { 14 | public: 15 | JoyShock(int uniqueHandle, int controllerSplitType, shared_ptr sharedButtonCommon = nullptr); 16 | 17 | ~JoyShock(); 18 | 19 | // These two large functions are defined further down 20 | void processStick(float stickX, float stickY, Stick &stick, float mouseCalibrationFactor, float deltaTime, bool &anyStickInput, bool &lockMouse, float &camSpeedX, float &camSpeedY); 21 | 22 | void handleTouchStickChange(TouchStick &ts, bool down, short movX, short movY, float delta_time); 23 | 24 | bool hasVirtualController(); 25 | 26 | void onVirtualControllerNotification(uint8_t largeMotor, uint8_t smallMotor, Indicator indicator); 27 | 28 | template 29 | E getSetting(SettingID index); 30 | 31 | float getSetting(SettingID index); 32 | 33 | template<> 34 | FloatXY getSetting(SettingID index); 35 | 36 | template<> 37 | GyroSettings getSetting(SettingID index); 38 | 39 | template<> 40 | Color getSetting(SettingID index); 41 | 42 | template<> 43 | AdaptiveTriggerSetting getSetting(SettingID index); 44 | 45 | template<> 46 | AxisSignPair getSetting(SettingID index); 47 | 48 | void getSmoothedGyro(float x, float y, float length, float bottomThreshold, float topThreshold, int maxSamples, float &outX, float &outY); 49 | 50 | void handleButtonChange(ButtonID id, bool pressed, int touchpadID = -1); 51 | 52 | void handleTriggerChange(ButtonID softIndex, ButtonID fullIndex, TriggerMode mode, float position, AdaptiveTriggerSetting &trigger_rumble); 53 | 54 | bool isPressed(ButtonID btn); 55 | 56 | // return true if it hits the outer deadzone 57 | bool processDeadZones(float &x, float &y, float innerDeadzone, float outerDeadzone); 58 | 59 | void updateGridSize(); 60 | 61 | bool processGyroStick(float stickX, float stickY, float stickLength, StickMode stickMode, bool forceOutput); 62 | 63 | shared_ptr _context; 64 | vector _buttons; 65 | vector _gridButtons; 66 | vector _touchpads; 67 | chrono::steady_clock::time_point _timeNow; 68 | shared_ptr _motion; 69 | int _handle; 70 | int _controllerType; 71 | int _splitType = 0; 72 | 73 | 74 | float neutralQuatW = 1.0f; 75 | float neutralQuatX = 0.0f; 76 | float neutralQuatY = 0.0f; 77 | float neutralQuatZ = 0.0f; 78 | 79 | bool set_neutral_quat = false; 80 | 81 | Color _light_bar; 82 | AdaptiveTriggerSetting _leftEffect; 83 | AdaptiveTriggerSetting _rightEffect; 84 | static AdaptiveTriggerSetting _unusedEffect; 85 | 86 | Stick _leftStick; 87 | Stick _rightStick; 88 | Stick _motionStick; 89 | 90 | bool processed_gyro_stick = false; 91 | static constexpr int NUM_LAST_GYRO_SAMPLES = 100; 92 | array lastGyroX = { 0.f }; 93 | array lastGyroY = { 0.f }; 94 | float lastGyroAbsX = 0.f; 95 | float lastGyroAbsY = 0.f; 96 | int lastGyroIndexX = 0; 97 | int lastGyroIndexY = 0; 98 | 99 | float gyroXVelocity = 0.f; 100 | float gyroYVelocity = 0.f; 101 | 102 | private: 103 | // this large functions is defined further down 104 | float handleFlickStick(float stickX, float stickY, Stick &stick, float stickLength, StickMode mode); 105 | 106 | bool isSoftPullPressed(int triggerIndex, float triggerPosition); 107 | 108 | float getTriggerEffectStartPos(); 109 | 110 | template 111 | optional getSettingAtChord(SettingID id, ButtonID chord); 112 | 113 | void sendRumble(int smallRumble, int bigRumble); 114 | 115 | DigitalButton *getMatchingSimBtn(ButtonID index); 116 | DigitalButton *getMatchingDiagBtn(ButtonID index, optional &iter); 117 | 118 | void resetSmoothSample(); 119 | 120 | float getSmoothedStickRotation(float value, float bottomThreshold, float topThreshold, int maxSamples); 121 | 122 | static constexpr int MAX_GYRO_SAMPLES = 256; 123 | static constexpr int NUM_SAMPLES = 256; 124 | 125 | array _flickSamples; 126 | int _frontSample = 0; 127 | 128 | array _gyroSamples; 129 | int _frontGyroSample = 0; 130 | 131 | Vec _lastGrav = Vec(0.f, -1.f, 0.f); 132 | 133 | float _windingAngleLeft = 0.f; 134 | float _windingAngleRight = 0.f; 135 | 136 | ScrollAxis _touchScrollX; 137 | ScrollAxis _touchScrollY; 138 | 139 | vector _triggerState; // State of analog triggers when skip mode is active 140 | vector> _prevTriggerPosition; 141 | }; 142 | 143 | template 144 | optional JoyShock::getSettingAtChord(SettingID id, ButtonID chord) 145 | { 146 | auto setting = SettingsManager::get(id); 147 | if (setting) 148 | { 149 | auto chordedValue = setting->chordedValue(chord); 150 | return chordedValue ? optional(setting->chordedValue(chord)) : nullopt; 151 | } 152 | return nullopt; 153 | } 154 | 155 | template 156 | E JoyShock::getSetting(SettingID index) 157 | { 158 | static_assert(is_enum::value, "Parameter of JoyShock::getSetting has to be an enum type"); 159 | // Look at active chord mappings starting with the latest activates chord 160 | for (auto activeChord = _context->chordStack.begin(); activeChord != _context->chordStack.end(); activeChord++) 161 | { 162 | optional opt = getSettingAtChord(index, *activeChord); 163 | if constexpr (is_same_v) 164 | { 165 | switch (index) 166 | { 167 | case SettingID::LEFT_STICK_MODE: 168 | if (_leftStick.flick_percent_done < 1.f && opt && (*opt != StickMode::FLICK && *opt != StickMode::FLICK_ONLY)) 169 | opt = make_optional(StickMode::FLICK_ONLY); 170 | else if (_leftStick.ignore_stick_mode && *activeChord == ButtonID::NONE) 171 | opt = StickMode::INVALID; 172 | else 173 | _leftStick.ignore_stick_mode |= (opt && *activeChord != ButtonID::NONE); 174 | break; 175 | case SettingID::RIGHT_STICK_MODE: 176 | if (_rightStick.flick_percent_done < 1.f && opt && (*opt != StickMode::FLICK && *opt != StickMode::FLICK_ONLY)) 177 | opt = make_optional(StickMode::FLICK_ONLY); 178 | else if (_rightStick.ignore_stick_mode && *activeChord == ButtonID::NONE) 179 | opt = make_optional(StickMode::INVALID); 180 | else 181 | _rightStick.ignore_stick_mode |= (opt && *activeChord != ButtonID::NONE); 182 | break; 183 | case SettingID::MOTION_STICK_MODE: 184 | if (_motionStick.flick_percent_done < 1.f && opt && (*opt != StickMode::FLICK && *opt != StickMode::FLICK_ONLY)) 185 | opt = make_optional(StickMode::FLICK_ONLY); 186 | else if (_motionStick.ignore_stick_mode && *activeChord == ButtonID::NONE) 187 | opt = make_optional(StickMode::INVALID); 188 | else 189 | _motionStick.ignore_stick_mode |= (opt && *activeChord != ButtonID::NONE); 190 | break; 191 | } 192 | } 193 | 194 | if constexpr (is_same_v) 195 | { 196 | if (index == SettingID::CONTROLLER_ORIENTATION && opt && 197 | opt.value() == ControllerOrientation::JOYCON_SIDEWAYS) 198 | { 199 | if (_splitType == JS_SPLIT_TYPE_LEFT) 200 | { 201 | opt = optional(static_cast(ControllerOrientation::LEFT)); 202 | } 203 | else if (_splitType == JS_SPLIT_TYPE_RIGHT) 204 | { 205 | opt = optional(static_cast(ControllerOrientation::RIGHT)); 206 | } 207 | else 208 | { 209 | opt = optional(static_cast(ControllerOrientation::FORWARD)); 210 | } 211 | } 212 | } 213 | if (opt) 214 | return *opt; 215 | } 216 | stringstream ss; 217 | ss << "Index " << index << " is not a valid enum setting"; 218 | throw invalid_argument(ss.str().c_str()); 219 | } -------------------------------------------------------------------------------- /JoyShockMapper/include/JslWrapper.h: -------------------------------------------------------------------------------- 1 | // JoyShockLibrary.h - Contains declarations of functions 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | enum class AdaptiveTriggerMode : unsigned char 8 | { 9 | ON = 0x00, // Not an actual DS5 code 10 | OFF = 0x05, 11 | RESISTANCE_RAW = 0x01, 12 | SEGMENT = 0x02, 13 | RESISTANCE = 0x21, 14 | BOW = 0x22, 15 | GALLOPING = 0x23, 16 | SEMI_AUTOMATIC = 0x25, 17 | AUTOMATIC = 0x26, 18 | MACHINE = 0x27, 19 | INVALID = 0x7F, 20 | }; 21 | 22 | struct AdaptiveTriggerSetting 23 | { 24 | AdaptiveTriggerMode mode = AdaptiveTriggerMode::ON; 25 | // Keep these 6 fields next to each other and the same type 26 | uint8_t start = 0; 27 | uint8_t end = 0; 28 | uint8_t force = 0; 29 | uint8_t frequency = 0; 30 | uint8_t forceExtra = 0; 31 | uint8_t frequencyExtra = 0; 32 | }; 33 | 34 | // Defined in operators.cpp 35 | std::istream &operator>>(std::istream &in, AdaptiveTriggerSetting &atm); 36 | std::ostream &operator<<(std::ostream &out, const AdaptiveTriggerSetting &atm); 37 | bool operator==(const AdaptiveTriggerSetting &lhs, const AdaptiveTriggerSetting &rhs); 38 | inline bool operator!=(const AdaptiveTriggerSetting &lhs, const AdaptiveTriggerSetting &rhs) 39 | { 40 | return !(lhs == rhs); 41 | } 42 | 43 | 44 | #if defined(JSL_WRAPPER_SOURCE) 45 | 46 | #include "JoyShockLibrary.h" 47 | 48 | #else 49 | 50 | #define JS_TYPE_JOYCON_LEFT 1 51 | #define JS_TYPE_JOYCON_RIGHT 2 52 | #define JS_TYPE_PRO_CONTROLLER 3 53 | #define JS_TYPE_DS4 4 54 | #define JS_TYPE_DS 5 55 | #define JS_TYPE_XBOXONE 6 56 | #define JS_TYPE_XBOXONE_ELITE 7 57 | #define JS_TYPE_XBOX_SERIES 8 58 | 59 | #define JS_SPLIT_TYPE_LEFT 1 60 | #define JS_SPLIT_TYPE_RIGHT 2 61 | #define JS_SPLIT_TYPE_FULL 3 62 | 63 | #define JSMASK_UP 0x000001 64 | #define JSMASK_DOWN 0x000002 65 | #define JSMASK_LEFT 0x000004 66 | #define JSMASK_RIGHT 0x000008 67 | #define JSMASK_PLUS 0x000010 68 | #define JSMASK_OPTIONS 0x000010 69 | #define JSMASK_MINUS 0x000020 70 | #define JSMASK_SHARE 0x000020 71 | #define JSMASK_LCLICK 0x000040 72 | #define JSMASK_RCLICK 0x000080 73 | #define JSMASK_L 0x000100 74 | #define JSMASK_R 0x000200 75 | #define JSMASK_ZL 0x000400 76 | #define JSMASK_ZR 0x000800 77 | #define JSMASK_S 0x001000 78 | #define JSMASK_E 0x002000 79 | #define JSMASK_W 0x004000 80 | #define JSMASK_N 0x008000 81 | #define JSMASK_HOME 0x010000 82 | #define JSMASK_PS 0x010000 83 | #define JSMASK_CAPTURE 0x020000 84 | #define JSMASK_TOUCHPAD_CLICK 0x020000 85 | #define JSMASK_MIC 0x040000 86 | #define JSMASK_SL 0x080000 87 | #define JSMASK_SR 0x100000 88 | #define JSMASK_FNL 0x20000 89 | #define JSMASK_FNR 0x400000 90 | 91 | #define JSOFFSET_UP 0 92 | #define JSOFFSET_DOWN 1 93 | #define JSOFFSET_LEFT 2 94 | #define JSOFFSET_RIGHT 3 95 | #define JSOFFSET_PLUS 4 96 | #define JSOFFSET_OPTIONS 4 97 | #define JSOFFSET_MINUS 5 98 | #define JSOFFSET_SHARE 5 99 | #define JSOFFSET_LCLICK 6 100 | #define JSOFFSET_RCLICK 7 101 | #define JSOFFSET_L 8 102 | #define JSOFFSET_R 9 103 | #define JSOFFSET_ZL 10 104 | #define JSOFFSET_ZR 11 105 | #define JSOFFSET_S 12 106 | #define JSOFFSET_E 13 107 | #define JSOFFSET_W 14 108 | #define JSOFFSET_N 15 109 | #define JSOFFSET_HOME 16 110 | #define JSOFFSET_PS 16 111 | #define JSOFFSET_CAPTURE 17 112 | #define JSOFFSET_TOUCHPAD_CLICK 17 113 | #define JSOFFSET_MIC 18 114 | #define JSOFFSET_SL 19 115 | #define JSOFFSET_SR 20 116 | #define JSOFFSET_FNL 21 117 | #define JSOFFSET_FNR 22 118 | 119 | // PS5 Player maps for the DS Player Lightbar 120 | #define DS5_PLAYER_1 = 4 121 | #define DS5_PLAYER_2 = 10 122 | #define DS5_PLAYER_3 = 21 123 | #define DS5_PLAYER_4 = 27 124 | #define DS5_PLAYER_5 = 31 125 | 126 | typedef struct JOY_SHOCK_STATE 127 | { 128 | int buttons; 129 | float lTrigger; 130 | float rTrigger; 131 | float stickLX; 132 | float stickLY; 133 | float stickRX; 134 | float stickRY; 135 | } JOY_SHOCK_STATE; 136 | 137 | typedef struct IMU_STATE 138 | { 139 | float accelX; 140 | float accelY; 141 | float accelZ; 142 | float gyroX; 143 | float gyroY; 144 | float gyroZ; 145 | } IMU_STATE; 146 | 147 | typedef struct MOTION_STATE 148 | { 149 | float quatW; 150 | float quatX; 151 | float quatY; 152 | float quatZ; 153 | float accelX; 154 | float accelY; 155 | float accelZ; 156 | float gravX; 157 | float gravY; 158 | float gravZ; 159 | } MOTION_STATE; 160 | 161 | typedef struct TOUCH_STATE 162 | { 163 | int t0Id; 164 | int t1Id; 165 | bool t0Down; 166 | bool t1Down; 167 | float t0X; 168 | float t0Y; 169 | float t1X; 170 | float t1Y; 171 | } TOUCH_STATE; 172 | 173 | #endif 174 | 175 | class JslWrapper 176 | { 177 | protected: 178 | JslWrapper() 179 | { 180 | } 181 | 182 | public: 183 | virtual ~JslWrapper() 184 | { 185 | } 186 | static JslWrapper* getNew(); 187 | 188 | virtual int ConnectDevices() = 0; 189 | virtual int GetDeviceCount() = 0; 190 | virtual int GetConnectedDeviceHandles(int* deviceHandleArray, int size) = 0; 191 | virtual void DisconnectAndDisposeAll() = 0; 192 | virtual JOY_SHOCK_STATE GetSimpleState(int deviceId) = 0; 193 | virtual IMU_STATE GetIMUState(int deviceId) = 0; 194 | virtual MOTION_STATE GetMotionState(int deviceId) = 0; 195 | virtual TOUCH_STATE GetTouchState(int deviceId, bool previous = false) = 0; 196 | virtual bool GetTouchpadDimension(int deviceId, int& sizeX, int& sizeY) = 0; 197 | virtual int GetButtons(int deviceId) = 0; 198 | virtual float GetLeftX(int deviceId) = 0; 199 | virtual float GetLeftY(int deviceId) = 0; 200 | virtual float GetRightX(int deviceId) = 0; 201 | virtual float GetRightY(int deviceId) = 0; 202 | virtual float GetLeftTrigger(int deviceId) = 0; 203 | virtual float GetRightTrigger(int deviceId) = 0; 204 | virtual float GetGyroX(int deviceId) = 0; 205 | virtual float GetGyroY(int deviceId) = 0; 206 | virtual float GetGyroZ(int deviceId) = 0; 207 | virtual float GetAccelX(int deviceId) = 0; 208 | virtual float GetAccelY(int deviceId) = 0; 209 | virtual float GetAccelZ(int deviceId) = 0; 210 | virtual int GetTouchId(int deviceId, bool secondTouch = false) = 0; 211 | virtual bool GetTouchDown(int deviceId, bool secondTouch = false) = 0; 212 | virtual float GetTouchX(int deviceId, bool secondTouch = false) = 0; 213 | virtual float GetTouchY(int deviceId, bool secondTouch = false) = 0; 214 | virtual float GetStickStep(int deviceId) = 0; 215 | virtual float GetTriggerStep(int deviceId) = 0; 216 | virtual float GetPollRate(int deviceId) = 0; 217 | virtual void ResetContinuousCalibration(int deviceId) = 0; 218 | virtual void StartContinuousCalibration(int deviceId) = 0; 219 | virtual void PauseContinuousCalibration(int deviceId) = 0; 220 | virtual void GetCalibrationOffset(int deviceId, float& xOffset, float& yOffset, float& zOffset) = 0; 221 | virtual void SetCalibrationOffset(int deviceId, float xOffset, float yOffset, float zOffset) = 0; 222 | virtual void SetCallback(void (*callback)(int, JOY_SHOCK_STATE, JOY_SHOCK_STATE, IMU_STATE, IMU_STATE, float)) = 0; 223 | virtual void SetTouchCallback(void (*callback)(int, TOUCH_STATE, TOUCH_STATE, float)) = 0; 224 | virtual int GetControllerType(int deviceId) = 0; 225 | virtual int GetControllerSplitType(int deviceId) = 0; 226 | virtual int GetControllerColour(int deviceId) = 0; 227 | virtual void SetLightColour(int deviceId, int colour) = 0; 228 | virtual void SetRumble(int deviceId, int smallRumble, int bigRumble) = 0; 229 | virtual void SetPlayerNumber(int deviceId, int number) = 0; 230 | virtual void SetTriggerEffect(int deviceId, const AdaptiveTriggerSetting &_leftTriggerEffect, const AdaptiveTriggerSetting &_rightTriggerEffect) { }; 231 | virtual void SetMicLight(int deviceId, unsigned char mode) { } 232 | }; 233 | -------------------------------------------------------------------------------- /JoyShockMapper/include/Mapping.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "PlatformDefinitions.h" 5 | 6 | // The list of different function that can be bound in the mapping 7 | class EventActionIf 8 | { 9 | public: 10 | typedef function Callback; 11 | 12 | virtual void RegisterInstant(BtnEvent evt, Callback cb) = 0; 13 | virtual void ApplyGyroAction(KeyCode gyroAction) = 0; 14 | virtual void RemoveGyroAction() = 0; 15 | virtual void SetRumble(int smallRumble, int bigRumble) = 0; 16 | virtual void ApplyBtnPress(KeyCode key) = 0; 17 | virtual void ApplyBtnRelease(KeyCode key) = 0; 18 | virtual void ApplyButtonToggle(KeyCode key, Callback apply, Callback release) = 0; 19 | virtual void StartCalibration() = 0; 20 | virtual void FinishCalibration() = 0; 21 | virtual const char *getDisplayName() = 0; 22 | }; 23 | 24 | // This structure handles the mapping of a button, buy processing and action 25 | // to be done on tap, hold, turbo and others. It holds a map of actions to perform 26 | // when a specific event happens. This replaces the old Mapping structure. 27 | class Mapping 28 | { 29 | public: 30 | enum class ActionModifier 31 | { 32 | None, 33 | Toggle, 34 | Instant, 35 | Release, 36 | INVALID 37 | }; 38 | enum class EventModifier 39 | { 40 | Auto, 41 | StartPress, 42 | ReleasePress, 43 | TurboPress, 44 | TapPress, 45 | HoldPress, 46 | INVALID 47 | }; 48 | 49 | // Identifies having no binding mapped 50 | static const Mapping NO_MAPPING; 51 | 52 | // This functor nees to be set to way to validate a command line string; 53 | static function _isCommandValid; 54 | 55 | friend istream &operator>>(istream &in, Mapping &mapping); 56 | friend ostream &operator<<(ostream &out, const Mapping &mapping); 57 | 58 | private: 59 | string _description = "no input"; 60 | string _command; 61 | 62 | map _eventMapping; 63 | float _tapDurationMs = MAGIC_TAP_DURATION; 64 | bool _hasViGEmBtn = false; 65 | 66 | void InsertEventMapping(BtnEvent evt, EventActionIf::Callback action); 67 | static void RunBothActions(EventActionIf *btn, EventActionIf::Callback action1, EventActionIf::Callback action2); 68 | 69 | public: 70 | Mapping() = default; 71 | 72 | Mapping(string_view mapping); 73 | 74 | Mapping(int dummy) 75 | : Mapping() 76 | { 77 | } 78 | 79 | string_view description() const 80 | { 81 | return _description; 82 | } 83 | 84 | string_view command() const 85 | { 86 | return _command; 87 | } 88 | void ProcessEvent(BtnEvent evt, EventActionIf &button) const; 89 | 90 | bool AddMapping(KeyCode key, EventModifier evtMod, ActionModifier actMod = ActionModifier::None); 91 | 92 | bool AppendToCommand(KeyCode key, EventModifier evtMod, ActionModifier actMod = ActionModifier::None); 93 | 94 | inline bool isValid() const 95 | { 96 | return !_command.empty(); 97 | } 98 | 99 | inline float getTapDuration() const 100 | { 101 | return _tapDurationMs; 102 | } 103 | 104 | inline void clear() 105 | { 106 | _eventMapping.clear(); 107 | _description.clear(); 108 | _tapDurationMs = MAGIC_TAP_DURATION; 109 | _hasViGEmBtn = false; 110 | } 111 | 112 | inline bool hasViGEmBtn() const 113 | { 114 | return _hasViGEmBtn; 115 | } 116 | }; 117 | 118 | bool operator==(const Mapping &lhs, const Mapping &rhs); 119 | inline bool operator!=(const Mapping &lhs, const Mapping &rhs) 120 | { 121 | return !(lhs == rhs); 122 | } 123 | -------------------------------------------------------------------------------- /JoyShockMapper/include/MotionIf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class MotionIf 4 | { 5 | protected: 6 | MotionIf(){}; 7 | public: 8 | static MotionIf* getNew(); 9 | virtual ~MotionIf() {}; 10 | 11 | 12 | virtual void reset() = 0; 13 | 14 | virtual void ProcessMotion(float gyroX, float gyroY, float gyroZ, 15 | float accelX, float accelY, float accelZ, float deltaTime) = 0; 16 | 17 | // reading the current state 18 | virtual void GetCalibratedGyro(float& x, float& y, float& z) = 0; 19 | virtual void GetGravity(float& x, float& y, float& z) = 0; 20 | virtual void GetProcessedAcceleration(float& x, float& y, float& z) = 0; 21 | virtual void GetOrientation(float& w, float& x, float& y, float& z) = 0; 22 | 23 | // gyro calibration functions 24 | virtual void StartContinuousCalibration() = 0; 25 | virtual void PauseContinuousCalibration() = 0; 26 | virtual void ResetContinuousCalibration() = 0; 27 | virtual void GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset) = 0; 28 | virtual void SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight) = 0; 29 | virtual void SetAutoCalibration(bool enabled, float gyroThreshold, float accelThreshold) = 0; 30 | 31 | void virtual ResetMotion() = 0; 32 | }; 33 | -------------------------------------------------------------------------------- /JoyShockMapper/include/PlatformDefinitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 6 | // Only use undefined keys from the above list for JSM custom commands 7 | constexpr uint16_t V_WHEEL_UP = 0x03; // Here I intentionally overwride VK_CANCEL because breaking a process with a keybind is not something we want to do 8 | constexpr uint16_t V_WHEEL_DOWN = 0x07; // I want all mouse bindings to be contiguous IDs for ease to distinguish 9 | constexpr uint16_t NO_HOLD_MAPPED = 0x0A; // Empty mapping, which is different form no mapping for combo presses 10 | constexpr uint16_t CALIBRATE = 0x0B; 11 | constexpr uint16_t GYRO_INV_X = 0x88; 12 | constexpr uint16_t GYRO_INV_Y = 0x89; 13 | constexpr uint16_t GYRO_INVERT = 0x8A; 14 | constexpr uint16_t GYRO_OFF_BIND = 0x8B; // Not to be confused with settings GYRO_ON and GYRO_OFF 15 | constexpr uint16_t GYRO_ON_BIND = 0x8C; // Those here are bindings 16 | constexpr uint16_t GYRO_TRACK_X = 0x8D; 17 | constexpr uint16_t GYRO_TRACK_Y = 0x8E; 18 | constexpr uint16_t GYRO_TRACKBALL = 0x8F; 19 | constexpr uint16_t COMMAND_ACTION = 0x97; // Run command 20 | constexpr uint16_t RUMBLE = 0xE6; 21 | 22 | constexpr const char *SMALL_RUMBLE = "R0080"; 23 | constexpr const char *BIG_RUMBLE = "RFF00"; 24 | 25 | // XInput buttons 26 | constexpr uint16_t X_UP = 0xE8; 27 | constexpr uint16_t X_DOWN = 0xE9; 28 | constexpr uint16_t X_LEFT = 0xEA; 29 | constexpr uint16_t X_RIGHT = 0xEB; 30 | constexpr uint16_t X_LB = 0xEC; 31 | constexpr uint16_t X_RB = 0xED; 32 | constexpr uint16_t X_X = 0xEE; 33 | constexpr uint16_t X_A = 0xEF; 34 | constexpr uint16_t X_Y = 0xF0; 35 | constexpr uint16_t X_B = 0xF1; 36 | constexpr uint16_t X_LS = 0xF2; 37 | constexpr uint16_t X_RS = 0xF3; 38 | constexpr uint16_t X_BACK = 0xF4; 39 | constexpr uint16_t X_START = 0xF5; 40 | constexpr uint16_t X_GUIDE = 0xB8; 41 | constexpr uint16_t X_LT = 0xD8; 42 | constexpr uint16_t X_RT = 0xD9; 43 | 44 | // DS4 buttons 45 | constexpr uint16_t PS_UP = 0xE8; 46 | constexpr uint16_t PS_DOWN = 0xE9; 47 | constexpr uint16_t PS_LEFT = 0xEA; 48 | constexpr uint16_t PS_RIGHT = 0xEB; 49 | constexpr uint16_t PS_L1 = 0xEC; 50 | constexpr uint16_t PS_R1 = 0xED; 51 | constexpr uint16_t PS_SQUARE = 0xEE; 52 | constexpr uint16_t PS_CROSS = 0xEF; 53 | constexpr uint16_t PS_TRIANGLE = 0xF0; 54 | constexpr uint16_t PS_CIRCLE = 0xF1; 55 | constexpr uint16_t PS_L3 = 0xF2; 56 | constexpr uint16_t PS_R3 = 0xF3; 57 | constexpr uint16_t PS_SHARE = 0xF4; 58 | constexpr uint16_t PS_OPTIONS = 0xF5; 59 | constexpr uint16_t PS_HOME = 0xB8; 60 | constexpr uint16_t PS_PAD_CLICK = 0xB9; 61 | constexpr uint16_t PS_L2 = 0xD8; 62 | constexpr uint16_t PS_R2 = 0xD9; 63 | 64 | constexpr bool isControllerKey(uint16_t code) 65 | { 66 | return (code >= X_UP && code <= X_START) || code == PS_HOME || code == PS_PAD_CLICK || code == X_LT || code == X_RT; 67 | } 68 | 69 | 70 | // Needs to be accessed publicly 71 | uint16_t nameToKey(std::string_view name); 72 | 73 | struct KeyCode 74 | { 75 | uint16_t code = NO_HOLD_MAPPED; 76 | std::string name = "None"; 77 | 78 | KeyCode() = default; 79 | 80 | KeyCode(std::string_view keyName) 81 | : code(nameToKey(keyName)) 82 | , name() 83 | { 84 | if (code == COMMAND_ACTION) 85 | name = keyName.substr(1, keyName.size() - 2); // Remove opening and closing quotation marks 86 | else if (keyName.compare("SMALL_RUMBLE") == 0) 87 | { 88 | name = SMALL_RUMBLE; 89 | code = RUMBLE; 90 | } 91 | else if (keyName.compare("BIG_RUMBLE") == 0) 92 | { 93 | name = BIG_RUMBLE; 94 | code = RUMBLE; 95 | } 96 | else if (code != 0) 97 | name = keyName; 98 | } 99 | 100 | inline bool isValid() const 101 | { 102 | return code != 0; 103 | } 104 | 105 | inline bool operator==(const KeyCode &rhs) const 106 | { 107 | return code == rhs.code && name == rhs.name; 108 | } 109 | 110 | inline bool operator!=(const KeyCode &rhs) const 111 | { 112 | return !operator==(rhs); 113 | } 114 | }; 115 | 116 | 117 | // The following operators enable reading and writing JSM's custom 118 | // types to and from string, or handles exceptions 119 | std::ostream &operator<<(std::ostream &out, const KeyCode &code); 120 | // operator >>() is nameToKey()?!? 121 | 122 | 123 | #ifdef _WIN32 124 | 125 | #include 126 | #include 127 | #include 128 | #include 129 | 130 | static std::mutex print_mutex; 131 | 132 | #define U(string) L##string 133 | 134 | using TrayIconData = HINSTANCE; 135 | using UnicodeString = std::wstring; 136 | 137 | // Current Working Directory can now be changed: these need to be dynamic 138 | extern const char *AUTOLOAD_FOLDER(); 139 | extern const char *GYRO_CONFIGS_FOLDER(); 140 | extern const char *BASE_JSM_CONFIG_FOLDER(); 141 | extern std::string NONAME; 142 | 143 | #elif defined(__linux__) 144 | 145 | #include 146 | #include 147 | 148 | #define WINAPI 149 | #define VK_OEM_PLUS 0xBB 150 | #define VK_OEM_MINUS 0xBD 151 | #define VK_OEM_COMMA 0xBC 152 | #define VK_OEM_PERIOD 0xBE 153 | #define VK_OEM_1 0xBA 154 | #define VK_OEM_2 0xBF 155 | #define VK_OEM_3 0xC0 156 | #define VK_OEM_4 0xDB 157 | #define VK_OEM_5 0xDC 158 | #define VK_OEM_6 0xDD 159 | #define VK_OEM_7 0xDE 160 | #define VK_F1 0x70 161 | #define VK_F2 0x71 162 | #define VK_F3 0x72 163 | #define VK_F4 0x73 164 | #define VK_F5 0x74 165 | #define VK_F6 0x75 166 | #define VK_F7 0x76 167 | #define VK_F8 0x77 168 | #define VK_F9 0x78 169 | #define VK_F10 0x79 170 | #define VK_F11 0x7A 171 | #define VK_F12 0x7B 172 | #define VK_F13 0x7C 173 | #define VK_F14 0x7D 174 | #define VK_F15 0x7E 175 | #define VK_F16 0x7F 176 | #define VK_F17 0x80 177 | #define VK_F18 0x81 178 | #define VK_F19 0x82 179 | #define VK_NUMPAD0 0x60 180 | #define VK_NUMPAD1 0x61 181 | #define VK_NUMPAD2 0x62 182 | #define VK_NUMPAD3 0x63 183 | #define VK_NUMPAD4 0x64 184 | #define VK_NUMPAD5 0x65 185 | #define VK_NUMPAD6 0x66 186 | #define VK_NUMPAD7 0x67 187 | #define VK_NUMPAD8 0x68 188 | #define VK_NUMPAD9 0x69 189 | #define VK_BACK 0x08 190 | #define VK_LEFT 0x25 191 | #define VK_RIGHT 0x27 192 | #define VK_UP 0x26 193 | #define VK_DOWN 0x28 194 | #define VK_SPACE 0x20 195 | #define VK_CONTROL 0x11 196 | #define VK_LCONTROL 0xA2 197 | #define VK_RCONTROL 0xA3 198 | #define VK_SHIFT 0x10 199 | #define VK_LSHIFT 0xA0 200 | #define VK_RSHIFT 0xA1 201 | #define VK_MENU 0x12 202 | #define VK_LMENU 0xA4 203 | #define VK_RMENU 0xA5 204 | #define VK_TAB 0x09 205 | #define VK_RETURN 0x0D 206 | #define VK_ESCAPE 0x1B 207 | #define VK_PRIOR 0x21 208 | #define VK_NEXT 0x22 209 | #define VK_HOME 0x24 210 | #define VK_END 0x23 211 | #define VK_INSERT 0x2D 212 | #define VK_DELETE 0x2E 213 | #define VK_LBUTTON 0x01 214 | #define VK_RBUTTON 0x02 215 | #define VK_MBUTTON 0x04 216 | #define VK_XBUTTON1 0x05 217 | #define VK_XBUTTON2 0x06 218 | #define VK_LWIN 0x5B // Left Windows Key 219 | #define VK_RWIN 0x5C // Right Windows Key 220 | #define VK_APPS 0x5D // Context Key 221 | #define VK_SNAPSHOT 0x2C // Printscreen Key 222 | #define VK_NONAME 0xFC 223 | 224 | #define U(string) string 225 | #define _ASSERT_EXPR(condition, message) assert(condition) 226 | 227 | using BOOL = bool; 228 | using WORD = unsigned short; 229 | using DWORD = unsigned long; 230 | using HANDLE = unsigned long; 231 | using LPVOID = void *; 232 | using TrayIconData = void *; 233 | using UnicodeString = std::string; 234 | 235 | // Current Working Directory can now be changed: these need to be dynamic 236 | extern const char *AUTOLOAD_FOLDER(); 237 | extern const char *GYRO_CONFIGS_FOLDER(); 238 | extern const char *BASE_JSM_CONFIG_FOLDER(); 239 | 240 | extern unsigned long GetCurrentProcessId(); 241 | 242 | #else 243 | #error "Unknown platform" 244 | #endif 245 | -------------------------------------------------------------------------------- /JoyShockMapper/include/SettingsManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "JSMVariable.hpp" 5 | #include 6 | 7 | class SettingsManager 8 | { 9 | public: 10 | SettingsManager() = delete; 11 | 12 | static bool add(SettingID id, JSMVariableBase *setting); 13 | 14 | template 15 | static constexpr bool add(JSMSetting *setting) 16 | { 17 | return add(setting->_id, setting); 18 | } 19 | 20 | template 21 | static JSMSetting *get(SettingID id) 22 | { 23 | auto base = _settings.find(id); 24 | if (base != _settings.end()) 25 | { 26 | return dynamic_cast *>(base->second.get()); 27 | } 28 | return nullptr; 29 | } 30 | 31 | template 32 | static JSMVariable *getV(SettingID id) 33 | { 34 | auto base = _settings.find(id); 35 | if (base != _settings.end()) 36 | { 37 | return dynamic_cast *>(base->second.get()); 38 | } 39 | return nullptr; 40 | } 41 | 42 | static void resetAllSettings(); 43 | 44 | private: 45 | using SettingsMap = unordered_map>; 46 | static SettingsMap _settings; 47 | }; 48 | 49 | extern map nnm; -------------------------------------------------------------------------------- /JoyShockMapper/include/Stick.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JoyShockMapper.h" 4 | #include "DigitalButton.h" 5 | #include 6 | 7 | class JoyShock; 8 | 9 | class ScrollAxis 10 | { 11 | protected: 12 | float _leftovers; 13 | DigitalButton* _negativeButton; 14 | DigitalButton* _positiveButton; 15 | int _touchpadId; 16 | ButtonID _pressedBtn; 17 | 18 | public: 19 | 20 | ScrollAxis() 21 | : _leftovers(0.f) 22 | , _negativeButton(nullptr) 23 | , _positiveButton(nullptr) 24 | , _touchpadId(-1) 25 | , _pressedBtn(ButtonID::NONE) 26 | { 27 | } 28 | 29 | void init(DigitalButton& negativeBtn, DigitalButton& positiveBtn, int touchpadId = -1); 30 | 31 | inline bool isInitialized() const 32 | { 33 | return _negativeButton && _positiveButton; 34 | } 35 | 36 | void processScroll(float distance, float sens, chrono::steady_clock::time_point now); 37 | 38 | void reset(chrono::steady_clock::time_point now); 39 | }; 40 | 41 | 42 | struct Stick 43 | { 44 | Stick(SettingID innerDeadzone, 45 | SettingID outerDeadzone, 46 | SettingID ringMode, 47 | SettingID stickMode, 48 | ButtonID ringId, 49 | ButtonID leftId, 50 | ButtonID rightId, 51 | ButtonID upId, 52 | ButtonID downId) 53 | : _innerDeadzone(innerDeadzone) 54 | , _outerDeadzone(outerDeadzone) 55 | , _ringMode(ringMode) 56 | , _stickMode(stickMode) 57 | , _ringId(ringId) 58 | , _leftId(leftId) 59 | , _rightId(rightId) 60 | , _upId(upId) 61 | , _downId(downId) 62 | { 63 | } 64 | SettingID _innerDeadzone; 65 | SettingID _outerDeadzone; 66 | SettingID _ringMode; 67 | SettingID _stickMode; 68 | ButtonID _ringId; 69 | ButtonID _leftId; 70 | ButtonID _rightId; 71 | ButtonID _upId; 72 | ButtonID _downId; 73 | int _touchpadIndex = -1; 74 | 75 | // Flick stick 76 | chrono::steady_clock::time_point started_flick; 77 | bool is_flicking = false; 78 | float delta_flick = 0.0; 79 | float flick_percent_done = 0.0; 80 | float flick_rotation_counter = 0.0; 81 | ScrollAxis scroll; 82 | float acceleration = 1.0; 83 | 84 | // Modeshifting the stick mode can create quirky behaviours on transition. These flags 85 | // will be set upon returning to default mode and ignore stick inputs until the stick 86 | // returns to neutral 87 | bool ignore_stick_mode = false; 88 | 89 | float lastX = 0.f; 90 | float lastY = 0.f; 91 | 92 | // Hybrid aim 93 | float edgePushAmount = 0.0f; 94 | float smallestMagnitude = 0.f; 95 | static constexpr int SMOOTHING_STEPS = 4; 96 | float previousOutputX[SMOOTHING_STEPS] = {}; 97 | float previousOutputY[SMOOTHING_STEPS] = {}; 98 | float previousOutputRadial[SMOOTHING_STEPS] = {}; 99 | float previousVelocitiesX[SMOOTHING_STEPS] = {}; 100 | float previousVelocitiesY[SMOOTHING_STEPS] = {}; 101 | int smoothingCounter = 0; 102 | }; 103 | 104 | struct TouchStick : public Stick 105 | { 106 | FloatXY _currentLocation = { 0.f, 0.f }; 107 | bool _prevDown = false; 108 | // Handle a single touch related action. On per touch point 109 | ScrollAxis verticalScroll; 110 | map buttons; // Each touchstick gets it's own digital _buttons. Is that smart? 111 | 112 | TouchStick(int index, shared_ptr common, int handle); 113 | 114 | inline bool wasDown() const 115 | { 116 | return _prevDown; 117 | } 118 | }; 119 | 120 | -------------------------------------------------------------------------------- /JoyShockMapper/include/TrayIcon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "PlatformDefinitions.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class TrayIcon 10 | { 11 | protected: 12 | TrayIcon() 13 | { 14 | } 15 | 16 | public: 17 | using ClickCallbackType = std::function; 18 | using ClickCallbackTypeChecked = std::function; 19 | using StateCallbackType = std::function; 20 | 21 | virtual ~TrayIcon(){}; 22 | 23 | static TrayIcon *getNew(TrayIconData applicationName, std::function &&beforeShow); 24 | 25 | virtual bool Show() = 0; 26 | 27 | virtual bool Hide() = 0; 28 | 29 | virtual bool SendNotification(const UnicodeString &message) = 0; 30 | 31 | virtual void AddMenuItem(const UnicodeString &label, ClickCallbackType &&onClick) = 0; 32 | 33 | virtual void AddMenuItem(const UnicodeString &label, ClickCallbackTypeChecked &&onClick, StateCallbackType &&getState) = 0; 34 | 35 | virtual void AddMenuItem(const UnicodeString &label, const UnicodeString &subLabel, ClickCallbackType &&onClick) = 0; 36 | 37 | virtual void ClearMenuMap() = 0; 38 | 39 | virtual operator bool() = 0; 40 | }; 41 | -------------------------------------------------------------------------------- /JoyShockMapper/include/Whitelister.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | // A whitelister object adds its PID to a running HIDCerberus service and removes it on destruciton 8 | // as per the RAII design pattern: https://en.cppreference.com/w/cpp/language/raii 9 | class Whitelister 10 | { 11 | protected: 12 | Whitelister(bool add = false) 13 | : _whitelisted(false) 14 | { 15 | } 16 | 17 | public: 18 | static Whitelister* getNew(bool add = false); 19 | 20 | virtual ~Whitelister() 21 | { 22 | } 23 | 24 | virtual bool ShowConsole() = 0; 25 | virtual bool IsAvailable() = 0; 26 | 27 | virtual bool Add(string* optErrMsg = nullptr) = 0; 28 | virtual bool Remove(string* optErrMsg = nullptr) = 0; 29 | 30 | // Check whether you're whitelisted or not. 31 | // Could be replaced with an socket query, if one exists. 32 | inline operator bool() const 33 | { 34 | return _whitelisted; 35 | } 36 | 37 | protected: 38 | bool _whitelisted; 39 | }; -------------------------------------------------------------------------------- /JoyShockMapper/include/linux/StatusNotifierItem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "PlatformDefinitions.h" 4 | #include "TrayIcon.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | class StatusNotifierItem : public TrayIcon 19 | { 20 | private: 21 | 22 | public: 23 | using StringType = std::string; 24 | 25 | public: 26 | StatusNotifierItem(TrayIconData applicationName, std::function &&beforeShow); 27 | ~StatusNotifierItem(); 28 | 29 | public: 30 | bool Show() override; 31 | bool Hide() override; 32 | 33 | bool SendNotification(const StringType &message) override; 34 | 35 | void AddMenuItem(const std::string &label, ClickCallbackType &&onClick) override; 36 | void AddMenuItem(const std::string &label, ClickCallbackTypeChecked &&onClick, StateCallbackType &&getState) override; 37 | void AddMenuItem(const std::string &label, const std::string &subLabel, ClickCallbackType &&onClick) override; 38 | 39 | void ClearMenuMap() override; 40 | 41 | operator bool() override; 42 | 43 | private: 44 | static void OnActivate(GtkMenuItem *item, void *data) noexcept; 45 | 46 | private: 47 | AppIndicator *indicator_{ nullptr }; 48 | std::unique_ptr menu_{ nullptr, nullptr }; 49 | std::vector menuItems_{}; 50 | std::unordered_map>> subMenus_; 51 | 52 | std::list callbacks_; 53 | 54 | std::thread thread_; 55 | }; 56 | -------------------------------------------------------------------------------- /JoyShockMapper/include/win32/WindowsTrayIcon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TrayIcon.h" 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct MenuItem; 13 | 14 | class WindowsTrayIcon : public TrayIcon 15 | { 16 | HINSTANCE _hInst; // current instance 17 | NOTIFYICONDATA _niData; // notify icon data 18 | std::vector _menuMap; 19 | std::map> _clickMap; 20 | HANDLE _thread; 21 | std::function _beforeShow; 22 | std::atomic_bool _init; 23 | 24 | public: 25 | WindowsTrayIcon(HINSTANCE hInstance, std::function beforeShow); 26 | 27 | ~WindowsTrayIcon(); 28 | 29 | operator bool() override 30 | { 31 | return _hInst != 0 && _thread != 0; 32 | } 33 | 34 | // https://stackoverflow.com/questions/18178628/how-do-i-call-setwindowlong-in-the-64-bit-versions-of-windows/18178661#18178661 35 | inline bool operator==(HWND handle) 36 | { 37 | HINSTANCE inst = (HINSTANCE)GetWindowLongPtr(handle, GWLP_HINSTANCE); 38 | return inst == _hInst; 39 | } 40 | 41 | bool Show() override 42 | { 43 | return Shell_NotifyIcon(NIM_ADD, &_niData) != FALSE; 44 | } 45 | 46 | bool Hide() override 47 | { 48 | return Shell_NotifyIcon(NIM_DELETE, &_niData) != FALSE; 49 | } 50 | 51 | bool SendNotification(const std::wstring &message) override; 52 | 53 | void AddMenuItem(const std::wstring &label, ClickCallbackType &&onClick) override; 54 | 55 | void AddMenuItem(const std::wstring &label, ClickCallbackTypeChecked &&onClick, StateCallbackType &&getState) override; 56 | 57 | void AddMenuItem(const std::wstring &label, const std::wstring &sublabel, ClickCallbackType &&onClick) override; 58 | 59 | void ClearMenuMap(); 60 | 61 | private: 62 | BOOL InitInstance(); 63 | 64 | static DWORD WINAPI MessageHandlerLoop(LPVOID param); 65 | 66 | void ShowContextMenu(HWND hWnd); 67 | 68 | ULONGLONG GetDllVersion(LPCTSTR lpszDllName); 69 | 70 | static INT_PTR CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 71 | }; 72 | -------------------------------------------------------------------------------- /JoyShockMapper/include/win32/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Win32 Dialog.rc 4 | // 5 | #define IDR_MANIFEST 1 6 | #define IDD_DLG_DIALOG 102 7 | #define IDD_ABOUTBOX 103 8 | #define IDM_ABOUT 104 9 | #define IDC_MYICON 105 10 | #define IDC_STEALTHDIALOG 106 11 | #define IDI_STEALTHDLG 107 12 | #define IDC_STATIC -1 13 | 14 | // Next default values for new objects 15 | // 16 | #ifdef APSTUDIO_INVOKED 17 | #ifndef APSTUDIO_READONLY_SYMBOLS 18 | #define _APS_NO_MFC 1 19 | #define _APS_NEXT_RESOURCE_VALUE 110 20 | #define _APS_NEXT_COMMAND_VALUE 32771 21 | #define _APS_NEXT_CONTROL_VALUE 1000 22 | #define _APS_NEXT_SYMED_VALUE 110 23 | #endif 24 | #endif 25 | -------------------------------------------------------------------------------- /JoyShockMapper/libs/JoyShockLibrary.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/libs/JoyShockLibrary.dll -------------------------------------------------------------------------------- /JoyShockMapper/libs/JoyShockLibrary.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Electronicks/JoyShockMapper/b36a72be051a43a48165580d6d18cf92d301a02a/JoyShockMapper/libs/JoyShockLibrary.lib -------------------------------------------------------------------------------- /JoyShockMapper/src/AutoConnect.cpp: -------------------------------------------------------------------------------- 1 | #include "AutoConnect.h" 2 | #include "JslWrapper.h" 3 | #include "InputHelpers.h" 4 | #include "Gamepad.h" 5 | 6 | 7 | namespace JSM 8 | { 9 | 10 | AutoConnect::AutoConnect(shared_ptr joyshock, bool start) 11 | : PollingThread("AutoConnect thread", std::bind(&AutoConnect::AutoConnectPoll, this, std::placeholders::_1), nullptr, 1000, start) 12 | , jsl(joyshock) 13 | { 14 | } 15 | 16 | bool AutoConnect::AutoConnectPoll(void* param) 17 | { 18 | int realSize = jsl->GetDeviceCount() - Gamepad::getCount(); 19 | if(lastSize != realSize) 20 | { 21 | COUT_INFO << "[AUTOCONNECT] Going from " << lastSize << " devices to " << realSize << ".\n"; 22 | lastSize = realSize; 23 | WriteToConsole("RECONNECT_CONTROLLERS"); 24 | } 25 | return true; 26 | } 27 | 28 | } // namespace JSM 29 | -------------------------------------------------------------------------------- /JoyShockMapper/src/AutoLoad.cpp: -------------------------------------------------------------------------------- 1 | #include "AutoLoad.h" 2 | 3 | // https://stackoverflow.com/a/4119881/1130520 gives us case insensitive equality 4 | static bool iequals(const string& a, const string& b) 5 | { 6 | return equal(a.begin(), a.end(), 7 | b.begin(), b.end(), 8 | [](char a, char b) 9 | { 10 | return tolower(a) == tolower(b); 11 | }); 12 | } 13 | namespace JSM 14 | { 15 | 16 | AutoLoad::AutoLoad(CmdRegistry* commandRegistry, bool start) 17 | : PollingThread("AutoLoad thread", bind(&AutoLoad::AutoLoadPoll, this, placeholders::_1), (void*)commandRegistry, 1000, start) 18 | { 19 | } 20 | 21 | bool AutoLoad::AutoLoadPoll(void* param) 22 | { 23 | auto registry = reinterpret_cast(param); 24 | static string lastModuleName; 25 | string windowTitle, windowModule; 26 | tie(windowModule, windowTitle) = GetActiveWindowName(); 27 | if (!windowModule.empty() && windowModule != lastModuleName && windowModule.compare("JoyShockMapper.exe") != 0) 28 | { 29 | lastModuleName = windowModule; 30 | string path(AUTOLOAD_FOLDER()); 31 | auto files = ListDirectory(path); 32 | auto noextmodule = windowModule.substr(0, windowModule.find_first_of('.')); 33 | COUT_INFO << "[AUTOLOAD] \"" << windowTitle << "\" in focus: "; // looking for config : " , ); 34 | bool success = false; 35 | for (auto file : files) 36 | { 37 | auto noextconfig = file.substr(0, file.find_first_of('.')); 38 | if (iequals(noextconfig, noextmodule)) 39 | { 40 | COUT_INFO << "loading \"AutoLoad\\" << noextconfig << ".txt\".\n"; 41 | WriteToConsole(path + file); 42 | success = true; 43 | break; 44 | } 45 | } 46 | if (!success) 47 | { 48 | COUT_INFO << "create "; 49 | COUT << "AutoLoad\\" << noextmodule << ".txt"; 50 | COUT_INFO << " to autoload for this application.\n"; 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | } // namespace JSM -------------------------------------------------------------------------------- /JoyShockMapper/src/ButtonHelp.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const map buttonHelpMap{ 6 | { ButtonID::UP, "Up on the d-pad" }, 7 | { ButtonID::DOWN, "Down on the d-pad" }, 8 | { ButtonID::LEFT, "Left on the d-pad" }, 9 | { ButtonID::RIGHT, "Right on the d-pad" }, 10 | { ButtonID::L, "L1 or L, the top left shoulder button" }, 11 | { ButtonID::ZL, "L2 or ZL, the bottom left shoulder button(or trigger)" }, 12 | { ButtonID::MINUS, "Share or -" }, 13 | { ButtonID::CAPTURE, "Touchpad click or Capture" }, 14 | { ButtonID::E, "The East face button, Circle or A" }, 15 | { ButtonID::S, "The South face button, Cross or B" }, 16 | { ButtonID::N, "The North face button, Triangle or X" }, 17 | { ButtonID::W, "The West face button, Square or Y" }, 18 | { ButtonID::R, "R1 or R, the top right shoulder button" }, 19 | { ButtonID::ZR, "R2 or ZR, the bottom right shoulder button" }, 20 | { ButtonID::PLUS, "Options or +" }, 21 | { ButtonID::HOME, "PS or Home" }, 22 | { ButtonID::LSL, "Left joycon SL, only on JoyCons" }, 23 | { ButtonID::LSR, "Left joycon SR, only on JoyCons" }, 24 | { ButtonID::RSL, "Right Joycon SL, only on JoyCons" }, 25 | { ButtonID::RSR, "Right joycon SR, only on JoyCons" }, 26 | { ButtonID::L3, "L3 or Left stick click" }, 27 | { ButtonID::R3, "R3 or Right stick click" }, 28 | { ButtonID::LUP, "Left stick tilted up" }, 29 | { ButtonID::LDOWN, "Left stick tilted down" }, 30 | { ButtonID::LLEFT, "Left stick tilted left" }, 31 | { ButtonID::LRIGHT, "Left stick tilted right" }, 32 | { ButtonID::LRING, "Left ring binding, either inner or outer." }, 33 | { ButtonID::RUP, "Right stick tilted up" }, 34 | { ButtonID::RDOWN, "Right stick tilted down" }, 35 | { ButtonID::RLEFT, "Right stick tilted left" }, 36 | { ButtonID::RRIGHT, "Right stick tilted right" }, 37 | { ButtonID::RRING, "Right ring binding, either inner or outer." }, 38 | { ButtonID::MUP, "Motion stick tilted forward" }, 39 | { ButtonID::MDOWN, "Motion stick tilted back" }, 40 | { ButtonID::MLEFT, "Motion stick tilted left" }, 41 | { ButtonID::MRIGHT, "Motion stick tilted right" }, 42 | { ButtonID::MRING, "Motion ring binding, either inner or outer." }, 43 | { ButtonID::LEAN_LEFT, "Tilt the controller to the left" }, 44 | { ButtonID::LEAN_RIGHT, "Tilt the controller to the right" }, 45 | { ButtonID::TOUCH, "The touchpad is being touched" }, 46 | { ButtonID::MIC, "PS5 microphone button" }, 47 | { ButtonID::ZLF, "Full pull binding of left trigger, only on controllers with analog triggers" }, 48 | { ButtonID::ZRF, "Full pull binding of right trigger, only on controllers with analog triggers" }, 49 | { ButtonID::TUP, "Touch stick tilted forward" }, 50 | { ButtonID::TDOWN, "Touch stick tilted back" }, 51 | { ButtonID::TLEFT, "Touch stick tilted left" }, 52 | { ButtonID::TRIGHT, "Touch stick tilted right" }, 53 | { ButtonID::TRING, "Touch ring binding, either inner or outer." }, 54 | }; 55 | 56 | map nnm = { 57 | {22, ButtonID::HOME}, 58 | {6, ButtonID::DOWN}, 59 | {10, ButtonID::LEFT}, 60 | {20, ButtonID::E}, 61 | {2, ButtonID::UP}, 62 | {14, ButtonID::LEFT}, 63 | {28, ButtonID::ZR}, 64 | {18, ButtonID::S}, 65 | {26, ButtonID::ZL}, 66 | {4, ButtonID::UP}, 67 | {12, ButtonID::RIGHT}, 68 | {8, ButtonID::DOWN}, 69 | {16, ButtonID::RIGHT}, 70 | {24, ButtonID::CAPTURE}, 71 | }; -------------------------------------------------------------------------------- /JoyShockMapper/src/CmdRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include "CmdRegistry.h" 2 | #include "PlatformDefinitions.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | JSMCommand::JSMCommand(string_view name) 12 | : _parse() 13 | , _help("Enter README to bring up the user manual.") 14 | , _taskOnDestruction() 15 | , _name(name) 16 | { 17 | } 18 | 19 | JSMCommand::~JSMCommand() 20 | { 21 | if (_taskOnDestruction) 22 | _taskOnDestruction(*this); 23 | } 24 | 25 | JSMCommand* JSMCommand::setParser(ParseDelegate parserFunction) 26 | { 27 | _parse = parserFunction; 28 | return this; 29 | } 30 | 31 | JSMCommand* JSMCommand::setHelp(string_view commandDescription) 32 | { 33 | _help = commandDescription; 34 | return this; 35 | } 36 | 37 | unique_ptr JSMCommand::getModifiedCmd(char op, string_view chord) 38 | { 39 | return nullptr; 40 | } 41 | 42 | bool JSMCommand::parseData(string_view arguments, string_view label) 43 | { 44 | _ASSERT_EXPR(_parse, L"There is no function defined to parse this command."); 45 | if (arguments.compare("HELP") == 0) 46 | { 47 | // Parsing has failed. Show help. 48 | COUT << _help << '\n'; 49 | } 50 | else if (!_parse(this, arguments, label)) 51 | { 52 | CERR << _help << '\n'; 53 | } 54 | return true; // Command is completely processed 55 | } 56 | 57 | CmdRegistry::CmdRegistry() 58 | { 59 | NONAME = { 0b01001011, 0b01001111 }; 60 | } 61 | 62 | bool CmdRegistry::loadConfigFile(string fileName) 63 | { 64 | // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring 65 | auto comment = fileName.find_first_of('#'); 66 | if (comment != string::npos) 67 | { 68 | fileName = fileName.substr(0, comment - 1); 69 | } 70 | // Trim away quotation marks from drag and drop 71 | if (*fileName.begin() == '\"' && *(fileName.end() - 1) == '\"') 72 | fileName = fileName.substr(1, fileName.size() - 2); 73 | 74 | ifstream file(fileName); 75 | if (!file.is_open()) 76 | { 77 | file.open(string{ BASE_JSM_CONFIG_FOLDER() } + fileName); 78 | } 79 | if (file) 80 | { 81 | COUT << "Loading commands from file "; 82 | COUT_INFO << fileName << '\n'; 83 | // https://stackoverflow.com/questions/6892754/creating-a-simple-configuration-file-and-parser-in-c 84 | string line; 85 | while (getline(file, line)) 86 | { 87 | processLine(line); 88 | } 89 | file.close(); 90 | return true; 91 | } 92 | return false; 93 | } 94 | 95 | string_view CmdRegistry::strtrim(string_view str) 96 | { 97 | if (str.empty()) 98 | return {}; 99 | 100 | while (isspace(str[0])) 101 | { 102 | str.remove_prefix(1); 103 | if (str.empty()) 104 | return {}; 105 | } 106 | 107 | while (isspace(str.back())) 108 | { 109 | str.remove_suffix(1); 110 | if (str.empty()) 111 | return {}; 112 | } 113 | 114 | return str; 115 | } 116 | 117 | // Add a command to the registry. The regisrty takes ownership of the memory of this pointer. 118 | // You can use _ASSERT() on the return value of this function to make sure the commands are 119 | // accepted. 120 | bool CmdRegistry::add(JSMCommand* newCommand) 121 | { 122 | // Check that the pointer is valid, that the name is valid. 123 | if (newCommand && regex_match(newCommand->_name, regex(R"(^(\+|-|\w+)$)"))) 124 | { 125 | // Unique pointers automatically delete the pointer on object destruction 126 | _registry.emplace(newCommand->_name, unique_ptr(newCommand)); 127 | return true; 128 | } 129 | delete newCommand; 130 | return false; 131 | } 132 | 133 | bool CmdRegistry::Remove(string_view name) 134 | { 135 | // If I allow multiple commands with the same name, I should have a way to specify which one I want to remove. 136 | CmdMap::iterator cmd = find_if(_registry.begin(), _registry.end(), bind(&CmdRegistry::findCommandWithName, name, placeholders::_1)); 137 | if (cmd != _registry.end()) 138 | { 139 | _registry.erase(cmd); 140 | return true; 141 | } 142 | return false; 143 | } 144 | 145 | bool CmdRegistry::findCommandWithName(string_view name, const CmdMap::value_type& pair) 146 | { 147 | return name == pair.first; 148 | } 149 | 150 | bool CmdRegistry::isCommandValid(string_view line) const 151 | { 152 | ifstream file(line.data()); 153 | if (file.is_open()) 154 | { 155 | file.close(); 156 | return true; 157 | } 158 | smatch results; 159 | string combo, name, arguments, label; 160 | char op = '\0'; 161 | const string lineStr(line); 162 | if (regex_match(lineStr, results, regex(R"(^\s*([+-]?\w*)\s*([,+]\s*([+-]?\w*))?\s*([^#\n]*)(#\s*(.*))?$)"))) 163 | { 164 | if (results[2].length() > 0) 165 | { 166 | combo = results[1]; 167 | op = results[2].str()[0]; 168 | name = results[3]; 169 | } 170 | else 171 | { 172 | name = results[1]; 173 | } 174 | 175 | arguments = results[4]; 176 | label = results[6]; 177 | } 178 | 179 | bool hasProcessed = false; 180 | CmdMap::const_iterator cmd = find_if(_registry.cbegin(), _registry.cend(), bind(&CmdRegistry::findCommandWithName, name, placeholders::_1)); 181 | return cmd != _registry.end(); 182 | } 183 | 184 | void CmdRegistry::processLine(const string& line) 185 | { 186 | auto trimmedLine = string{ strtrim(line) }; 187 | 188 | if (!trimmedLine.empty() && trimmedLine.front() != '#' && !loadConfigFile(trimmedLine)) 189 | { 190 | smatch results; 191 | string combo, name, arguments, label; 192 | char op = '\0'; 193 | // Break up the line of text in its relevant parts. 194 | // Pro tip: use regex101.com to develop these beautiful monstrosities. :P 195 | // Also, use raw strings R"(...)" to avoid the need to escape characters 196 | // I dislike having to code in exception for + and - _buttons not being \w characters 197 | if (regex_match(trimmedLine, results, regex(R"(^\s*([+-]?\w*)\s*([,+\*]\s*([+-]?\w*))?\s*([^#\n]*)(#\s*(.*))?$)"))) 198 | { 199 | if (results[2].length() > 0) 200 | { 201 | combo = results[1]; 202 | op = results[2].str()[0]; 203 | name = results[3]; 204 | } 205 | else 206 | { 207 | name = results[1]; 208 | } 209 | 210 | arguments = results[4]; 211 | label = results[6]; 212 | } 213 | 214 | bool hasProcessed = false; 215 | CmdMap::iterator cmd = find_if(_registry.begin(), _registry.end(), bind(&CmdRegistry::findCommandWithName, name, placeholders::_1)); 216 | while (cmd != _registry.end()) 217 | { 218 | if (combo.empty()) 219 | { 220 | hasProcessed |= cmd->second->parseData(arguments, label); 221 | } 222 | else 223 | { 224 | auto modCommand = cmd->second->getModifiedCmd(op, combo); 225 | if (modCommand) 226 | { 227 | hasProcessed |= modCommand->parseData(arguments, label); 228 | } 229 | // Any task set to be run on destruction is done here. 230 | } 231 | cmd = find_if(++cmd, _registry.end(), bind(&CmdRegistry::findCommandWithName, name, placeholders::_1)); 232 | } 233 | 234 | if (!hasProcessed) 235 | { 236 | CERR << "Unrecognized command: \"" << trimmedLine << "\"\nEnter "; 237 | COUT_INFO << "HELP"; 238 | CERR << " to display all commands.\n"; 239 | } 240 | } 241 | // else ignore empty lines 242 | } 243 | 244 | void CmdRegistry::GetCommandList(vector& outList) const 245 | { 246 | outList.clear(); 247 | for (auto& cmd : _registry) 248 | outList.push_back(cmd.first); 249 | return; 250 | } 251 | 252 | bool CmdRegistry::hasCommand(string_view name) const 253 | { 254 | return _registry.find(name) != _registry.end(); 255 | } 256 | 257 | string_view CmdRegistry::GetHelp(string_view command) const 258 | { 259 | auto cmd = _registry.find(command); 260 | if (cmd != _registry.end()) 261 | { 262 | return cmd->second->help(); 263 | } 264 | return ""; 265 | } 266 | 267 | bool JSMMacro::DefaultParser(JSMCommand* cmd, string_view arguments, string_view label) 268 | { 269 | // Default macro parser assumes no argument and calls macro when called. 270 | auto macroCmd = static_cast(cmd); 271 | // Developper protection to remind you to set a parser. 272 | _ASSERT_EXPR(macroCmd->_macro, L"No Macro was set for this command."); 273 | if (!macroCmd->_macro(macroCmd, arguments) && !macroCmd->_help.empty()) 274 | { 275 | COUT << macroCmd->_help << '\n'; 276 | COUT << "The "; // Parsing has failed. Show help. 277 | COUT_INFO << "README"; 278 | COUT << " command can lead you to further details on this command.\n"; 279 | } 280 | return true; 281 | } 282 | 283 | JSMMacro::JSMMacro(string_view name) 284 | : JSMCommand(name) 285 | , _macro() 286 | { 287 | setParser(&DefaultParser); 288 | } 289 | 290 | JSMMacro* JSMMacro::SetMacro(MacroDelegate macroFunction) 291 | { 292 | _macro = macroFunction; 293 | return this; 294 | } 295 | -------------------------------------------------------------------------------- /JoyShockMapper/src/JslWrapper.cpp: -------------------------------------------------------------------------------- 1 | #define JSL_WRAPPER_SOURCE 2 | #include "JslWrapper.h" 3 | 4 | class JSlWrapperImpl : public JslWrapper 5 | { 6 | int _deviceCount = 0; 7 | public: 8 | int ConnectDevices() override 9 | { 10 | _deviceCount = JslConnectDevices(); 11 | return _deviceCount; 12 | } 13 | 14 | int GetDeviceCount() override 15 | { 16 | // I believe this method is expected to update the current count without changing any objects. 17 | // This feature of SDL2 is not available in JSL, so this implementation makes the auto reconnect unusable 18 | // until JSL is updated with a similar feature. 19 | return _deviceCount; 20 | } 21 | 22 | int GetConnectedDeviceHandles(int* deviceHandleArray, int size) override 23 | { 24 | return JslGetConnectedDeviceHandles(deviceHandleArray, size); 25 | } 26 | 27 | void DisconnectAndDisposeAll() override 28 | { 29 | return JslDisconnectAndDisposeAll(); 30 | } 31 | 32 | JOY_SHOCK_STATE GetSimpleState(int deviceId) override 33 | { 34 | return JslGetSimpleState(deviceId); 35 | } 36 | 37 | IMU_STATE GetIMUState(int deviceId) override 38 | { 39 | return JslGetIMUState(deviceId); 40 | } 41 | 42 | MOTION_STATE GetMotionState(int deviceId) override 43 | { 44 | return JslGetMotionState(deviceId); 45 | } 46 | 47 | TOUCH_STATE GetTouchState(int deviceId, bool previous = false) override 48 | { 49 | return JslGetTouchState(deviceId, previous); 50 | } 51 | 52 | bool GetTouchpadDimension(int deviceId, int& sizeX, int& sizeY) override 53 | { 54 | return JslGetTouchpadDimension(deviceId, sizeX, sizeY); 55 | } 56 | 57 | int GetButtons(int deviceId) override 58 | { 59 | return JslGetButtons(deviceId); 60 | } 61 | 62 | float GetLeftX(int deviceId) override 63 | { 64 | return JslGetLeftX(deviceId); 65 | } 66 | 67 | float GetLeftY(int deviceId) override 68 | { 69 | return JslGetLeftY(deviceId); 70 | } 71 | 72 | float GetRightX(int deviceId) override 73 | { 74 | return JslGetRightX(deviceId); 75 | } 76 | 77 | float GetRightY(int deviceId) override 78 | { 79 | return JslGetRightY(deviceId); 80 | } 81 | 82 | float GetLeftTrigger(int deviceId) override 83 | { 84 | return JslGetLeftTrigger(deviceId); 85 | } 86 | 87 | float GetRightTrigger(int deviceId) override 88 | { 89 | return JslGetRightTrigger(deviceId); 90 | } 91 | 92 | float GetGyroX(int deviceId) override 93 | { 94 | return JslGetGyroX(deviceId); 95 | } 96 | 97 | float GetGyroY(int deviceId) override 98 | { 99 | return JslGetGyroY(deviceId); 100 | } 101 | 102 | float GetGyroZ(int deviceId) override 103 | { 104 | return JslGetGyroZ(deviceId); 105 | } 106 | 107 | float GetAccelX(int deviceId) override 108 | { 109 | return JslGetAccelX(deviceId); 110 | } 111 | 112 | float GetAccelY(int deviceId) override 113 | { 114 | return JslGetAccelY(deviceId); 115 | } 116 | 117 | float GetAccelZ(int deviceId) override 118 | { 119 | return JslGetAccelZ(deviceId); 120 | } 121 | 122 | int GetTouchId(int deviceId, bool secondTouch = false) override 123 | { 124 | return JslGetTouchId(deviceId, secondTouch); 125 | } 126 | 127 | bool GetTouchDown(int deviceId, bool secondTouch = false) override 128 | { 129 | return JslGetTouchDown(deviceId, secondTouch); 130 | } 131 | 132 | float GetTouchX(int deviceId, bool secondTouch = false) override 133 | { 134 | return JslGetTouchX(deviceId, secondTouch); 135 | } 136 | 137 | float GetTouchY(int deviceId, bool secondTouch = false) override 138 | { 139 | return JslGetTouchY(deviceId, secondTouch); 140 | } 141 | 142 | float GetStickStep(int deviceId) override 143 | { 144 | return JslGetStickStep(deviceId); 145 | } 146 | 147 | float GetTriggerStep(int deviceId) override 148 | { 149 | return JslGetTriggerStep(deviceId); 150 | } 151 | 152 | float GetPollRate(int deviceId) override 153 | { 154 | return JslGetPollRate(deviceId); 155 | } 156 | 157 | void ResetContinuousCalibration(int deviceId) override 158 | { 159 | JslResetContinuousCalibration(deviceId); 160 | } 161 | 162 | void StartContinuousCalibration(int deviceId) override 163 | { 164 | JslStartContinuousCalibration(deviceId); 165 | } 166 | 167 | void PauseContinuousCalibration(int deviceId) override 168 | { 169 | return JslPauseContinuousCalibration(deviceId); 170 | } 171 | 172 | void GetCalibrationOffset(int deviceId, float& xOffset, float& yOffset, float& zOffset) override 173 | { 174 | JslGetCalibrationOffset(deviceId, xOffset, yOffset, zOffset); 175 | } 176 | 177 | void SetCalibrationOffset(int deviceId, float xOffset, float yOffset, float zOffset) override 178 | { 179 | JslSetCalibrationOffset(deviceId, xOffset, yOffset, zOffset); 180 | } 181 | 182 | void SetCallback(void (*callback)(int, JOY_SHOCK_STATE, JOY_SHOCK_STATE, IMU_STATE, IMU_STATE, float)) override 183 | { 184 | JslSetCallback(callback); 185 | } 186 | 187 | void SetTouchCallback(void (*callback)(int, TOUCH_STATE, TOUCH_STATE, float)) override 188 | { 189 | JslSetTouchCallback(callback); 190 | } 191 | 192 | int GetControllerType(int deviceId) override 193 | { 194 | return JslGetControllerType(deviceId); 195 | } 196 | 197 | int GetControllerSplitType(int deviceId) override 198 | { 199 | return JslGetControllerSplitType(deviceId); 200 | } 201 | 202 | int GetControllerColour(int deviceId) override 203 | { 204 | return JslGetControllerColour(deviceId); 205 | } 206 | 207 | void SetLightColour(int deviceId, int colour) override 208 | { 209 | JslSetLightColour(deviceId, colour); 210 | } 211 | 212 | void SetRumble(int deviceId, int smallRumble, int bigRumble) override 213 | { 214 | JslSetRumble(deviceId, smallRumble, bigRumble); 215 | } 216 | 217 | void SetPlayerNumber(int deviceId, int number) override 218 | { 219 | JslSetPlayerNumber(deviceId, number); 220 | } 221 | }; 222 | 223 | JslWrapper* JslWrapper::getNew() 224 | { 225 | return new JSlWrapperImpl(); 226 | } 227 | -------------------------------------------------------------------------------- /JoyShockMapper/src/Mapping.cpp: -------------------------------------------------------------------------------- 1 | #include "Mapping.h" 2 | #include "InputHelpers.h" 3 | #include 4 | #include 5 | 6 | const Mapping Mapping::NO_MAPPING = Mapping("NONE"); 7 | function Mapping::_isCommandValid = function(); 8 | 9 | ostream &operator<<(ostream &out, const Mapping &mapping) 10 | { 11 | return out << (mapping._command.empty() ? mapping._description : mapping._command); 12 | } 13 | 14 | istream &operator>>(istream &in, Mapping &mapping) 15 | { 16 | // Has friend access 17 | string valueName(128, '\0'); 18 | in.getline(&valueName[0], valueName.size()); 19 | valueName.resize(strlen(valueName.c_str())); 20 | smatch results; 21 | int count = 0; 22 | 23 | mapping._command = valueName; 24 | static constexpr string_view rgx = R"(\s*([!\^-]?)((\".*?\")|\w*[0-9A-Z]|\W)([\\\/+'_]?)\s*(.*))"; 25 | while (regex_match(valueName, results, regex(rgx.data())) && !results[0].str().empty()) 26 | { 27 | Mapping::ActionModifier actMod = 28 | results[1].str().empty() ? Mapping::ActionModifier::None : 29 | results[1].str()[0] == '!' ? Mapping::ActionModifier::Instant : 30 | results[1].str()[0] == '^' ? Mapping::ActionModifier::Toggle : 31 | results[1].str()[0] == '-' ? Mapping::ActionModifier::Release : 32 | Mapping::ActionModifier::INVALID; 33 | 34 | string keyStr(results[2]); 35 | 36 | Mapping::EventModifier evtMod = 37 | results[4].str().empty() ? Mapping::EventModifier::Auto : 38 | results[4].str()[0] == '\\' ? Mapping::EventModifier::StartPress : 39 | results[4].str()[0] == '+' ? Mapping::EventModifier::TurboPress : 40 | results[4].str()[0] == '/' ? Mapping::EventModifier::ReleasePress : 41 | results[4].str()[0] == '\'' ? Mapping::EventModifier::TapPress : 42 | results[4].str()[0] == '_' ? Mapping::EventModifier::HoldPress : 43 | Mapping::EventModifier::INVALID; 44 | 45 | string leftovers(results[5]); 46 | 47 | KeyCode key(keyStr); 48 | if (evtMod == Mapping::EventModifier::Auto) 49 | { 50 | evtMod = count == 0 ? (leftovers.empty() ? Mapping::EventModifier::StartPress : Mapping::EventModifier::TapPress) : 51 | (count == 1 ? Mapping::EventModifier::HoldPress : Mapping::EventModifier::Auto); 52 | } 53 | 54 | // Some exceptions :( 55 | if (key.code == COMMAND_ACTION && actMod == Mapping::ActionModifier::None) 56 | { 57 | // Any command actions are instant by default 58 | actMod = Mapping::ActionModifier::Instant; 59 | } 60 | else if (key.code == CALIBRATE && actMod == Mapping::ActionModifier::None && 61 | (evtMod == Mapping::EventModifier::TapPress || evtMod == Mapping::EventModifier::ReleasePress)) 62 | { 63 | // Calibrate only makes sense on tap or release if it toggles. Also preserves legacy. 64 | actMod = Mapping::ActionModifier::Toggle; 65 | } 66 | 67 | if (key.code == 0 || 68 | key.code == COMMAND_ACTION && actMod != Mapping::ActionModifier::Instant || 69 | actMod == Mapping::ActionModifier::INVALID || 70 | evtMod == Mapping::EventModifier::INVALID || 71 | evtMod == Mapping::EventModifier::Auto && count >= 2 || 72 | evtMod == Mapping::EventModifier::ReleasePress && actMod == Mapping::ActionModifier::None || 73 | !mapping.AddMapping(key, evtMod, actMod)) 74 | { 75 | // error!!! 76 | in.setstate(in.failbit); 77 | break; 78 | } 79 | valueName = leftovers; 80 | count++; 81 | } // Next item 82 | 83 | return in; 84 | } 85 | 86 | bool operator==(const Mapping &lhs, const Mapping &rhs) 87 | { 88 | // Very flawfull :( 89 | return lhs.command() == rhs.command(); 90 | } 91 | 92 | Mapping::Mapping(string_view mapping) 93 | { 94 | stringstream ss(mapping.data()); 95 | ss >> *this; 96 | if (ss.fail()) 97 | { 98 | clear(); 99 | } 100 | } 101 | 102 | void Mapping::ProcessEvent(BtnEvent evt, EventActionIf &button) const 103 | { 104 | // COUT << button._id << " processes event " << evt << '\n'; 105 | auto entry = _eventMapping.find(evt); 106 | if (entry != _eventMapping.end() && entry->second) // Skip over empty entries 107 | { 108 | switch (evt) 109 | { 110 | case BtnEvent::OnPress: 111 | COUT << button.getDisplayName() << ": true\n"; 112 | break; 113 | case BtnEvent::OnRelease: 114 | case BtnEvent::OnHoldRelease: 115 | COUT << button.getDisplayName() << ": false\n"; 116 | break; 117 | case BtnEvent::OnTap: 118 | COUT << button.getDisplayName() << ": tapped\n"; 119 | break; 120 | case BtnEvent::OnHold: 121 | COUT << button.getDisplayName() << ": held\n"; 122 | break; 123 | case BtnEvent::OnTurbo: 124 | COUT << button.getDisplayName() << ": turbo\n"; 125 | break; 126 | } 127 | 128 | if (entry->second) 129 | { 130 | // DEBUG_LOG << button.getDisplayName() << " processes event " << evt << '\n'; 131 | entry->second(&button); 132 | } 133 | } 134 | } 135 | 136 | void Mapping::InsertEventMapping(BtnEvent evt, EventActionIf::Callback action) 137 | { 138 | if (action) 139 | { 140 | auto existingActions = _eventMapping.find(evt); 141 | _eventMapping[evt] = existingActions == _eventMapping.end() ? action : 142 | bind(&RunBothActions, placeholders::_1, existingActions->second, action); // Chain with already existing mapping, if any 143 | } 144 | } 145 | 146 | bool Mapping::AddMapping(KeyCode key, EventModifier evtMod, ActionModifier actMod) 147 | { 148 | EventActionIf::Callback apply, apply2, release; 149 | if (key.code == 0) 150 | { 151 | return false; 152 | } 153 | if (key.code == CALIBRATE) 154 | { 155 | apply = bind(&EventActionIf::StartCalibration, placeholders::_1); 156 | release = bind(&EventActionIf::FinishCalibration, placeholders::_1); 157 | _tapDurationMs = MAGIC_EXTENDED_TAP_DURATION; // Unused in regular press 158 | } 159 | else if (key.code >= GYRO_INV_X && key.code <= GYRO_TRACKBALL) 160 | { 161 | apply = bind(&EventActionIf::ApplyGyroAction, placeholders::_1, key); 162 | release = bind(&EventActionIf::RemoveGyroAction, placeholders::_1); 163 | _tapDurationMs = MAGIC_EXTENDED_TAP_DURATION; // Unused in regular press 164 | } 165 | else if (key.code == COMMAND_ACTION) 166 | { 167 | _ASSERT_EXPR(Mapping::_isCommandValid, "You need to assign a function to this field. It should be a function that validates the command line."); 168 | if (!Mapping::_isCommandValid(key.name)) 169 | { 170 | COUT << "Error: \"" << key.name << "\" is not a valid command\n"; 171 | return false; 172 | } 173 | apply = bind(&WriteToConsole, key.name); 174 | release = EventActionIf::Callback(); 175 | } 176 | else if (key.code == RUMBLE) 177 | { 178 | union Rumble 179 | { 180 | int raw; 181 | array bytes; 182 | } rumble; 183 | rumble.raw = stoi(key.name.substr(1, 4), nullptr, 16); 184 | apply = bind(&EventActionIf::SetRumble, placeholders::_1, rumble.bytes[0] << 8, rumble.bytes[1] << 8); 185 | release = bind(&EventActionIf::SetRumble, placeholders::_1, 0, 0); 186 | _tapDurationMs = MAGIC_EXTENDED_TAP_DURATION; // Unused in regular press 187 | } 188 | else // 189 | { 190 | _hasViGEmBtn |= isControllerKey(key.code); // Set flag if vigem button 191 | apply = bind(&EventActionIf::ApplyBtnPress, placeholders::_1, key); 192 | release = bind(&EventActionIf::ApplyBtnRelease, placeholders::_1, key); 193 | } 194 | 195 | BtnEvent applyEvt, releaseEvt; 196 | switch (evtMod) 197 | { 198 | case EventModifier::StartPress: 199 | applyEvt = BtnEvent::OnPress; 200 | releaseEvt = BtnEvent::OnRelease; 201 | break; 202 | case EventModifier::TapPress: 203 | applyEvt = BtnEvent::OnTap; 204 | releaseEvt = BtnEvent::OnTapRelease; 205 | break; 206 | case EventModifier::HoldPress: 207 | applyEvt = BtnEvent::OnHold; 208 | releaseEvt = BtnEvent::OnHoldRelease; 209 | break; 210 | case EventModifier::ReleasePress: 211 | // Acttion Modifier is required 212 | applyEvt = BtnEvent::OnRelease; 213 | releaseEvt = BtnEvent::OnRelease; 214 | break; 215 | case EventModifier::TurboPress: 216 | applyEvt = BtnEvent::OnTurbo; 217 | releaseEvt = BtnEvent::OnRelease; 218 | break; 219 | default: // EventModifier::INVALID or None 220 | return false; 221 | } 222 | 223 | switch (actMod) 224 | { 225 | case ActionModifier::Toggle: 226 | apply = bind(&EventActionIf::ApplyButtonToggle, placeholders::_1, key, apply, release); 227 | release = nullptr; 228 | break; 229 | case ActionModifier::Instant: 230 | apply2 = bind(&EventActionIf::RegisterInstant, placeholders::_1, applyEvt, release); 231 | apply = bind(&Mapping::RunBothActions, placeholders::_1, apply, apply2); 232 | release = nullptr; 233 | break; 234 | case ActionModifier::Release: 235 | apply = release; 236 | release = nullptr; 237 | break; 238 | case ActionModifier::INVALID: 239 | return false; 240 | // None applies no modification... Hey! 241 | } 242 | 243 | // Extra turbo handling 244 | if (evtMod == EventModifier::TurboPress) 245 | { 246 | if (actMod == ActionModifier::None) // Regular turbo holds key down and pulses up during the instant window 247 | { 248 | apply2 = bind(&EventActionIf::RegisterInstant, placeholders::_1, applyEvt, apply);// send key down on instant 249 | apply = bind(&Mapping::RunBothActions, placeholders::_1, release, apply2); // send key up and register key down 250 | } 251 | // else handled already in instant case above 252 | } 253 | 254 | InsertEventMapping(applyEvt, apply); 255 | InsertEventMapping(releaseEvt, release); 256 | 257 | stringstream ss; 258 | // Update Description 259 | if (_description.compare("no input") != 0) 260 | { 261 | ss << _description; 262 | if (_eventMapping.size() > 2 && _eventMapping.find(BtnEvent::OnPress) != _eventMapping.end()) 263 | { 264 | ss << " on Start Press"; 265 | } 266 | ss << " and "; 267 | } 268 | if (actMod != Mapping::ActionModifier::None) 269 | { 270 | ss << actMod << " "; 271 | } 272 | ss << key.name; 273 | if (_eventMapping.size() > 3 || evtMod != Mapping::EventModifier::StartPress) 274 | { 275 | ss << " on " << evtMod; 276 | } 277 | // else don't display event modifier when using default binding on single key 278 | _description = ss.str(); 279 | return true; 280 | } 281 | 282 | bool Mapping::AppendToCommand(KeyCode key, EventModifier evtMod, ActionModifier actMod) 283 | { 284 | if (key.name.empty() || evtMod == EventModifier::INVALID || actMod == ActionModifier::INVALID) 285 | { 286 | return false; 287 | } 288 | stringstream ss; 289 | if (!_command.empty()) 290 | { 291 | ss << _command << " "; 292 | } 293 | 294 | if (actMod != ActionModifier::None) 295 | { 296 | ss << (actMod == ActionModifier::Instant ? '!' : '^'); 297 | } 298 | ss << key.name; 299 | 300 | if (evtMod != EventModifier::Auto) 301 | { 302 | ss << (evtMod == EventModifier::StartPress ? '\\' : 303 | evtMod == EventModifier::TurboPress ? '+' : 304 | evtMod == EventModifier::ReleasePress ? '/' : 305 | evtMod == EventModifier::TapPress ? '\'' : 306 | /* evtMod == EventModifier::HoldPress */ '_'); 307 | } 308 | _command = ss.str(); 309 | return true; 310 | } 311 | 312 | void Mapping::RunBothActions(EventActionIf *btn, EventActionIf::Callback action1, EventActionIf::Callback action2) 313 | { 314 | if (action1) 315 | action1(btn); 316 | if (action2) 317 | action2(btn); 318 | } 319 | -------------------------------------------------------------------------------- /JoyShockMapper/src/MotionImpl.cpp: -------------------------------------------------------------------------------- 1 | #include "MotionIf.h" 2 | #include "GamepadMotion.hpp" 3 | 4 | class MotionImpl : public MotionIf 5 | { 6 | GamepadMotion gamepadMotion; 7 | public: 8 | MotionImpl() = default; 9 | 10 | virtual ~MotionImpl() = default; 11 | 12 | virtual void reset() override 13 | { 14 | gamepadMotion.Reset(); 15 | } 16 | 17 | virtual void ProcessMotion(float gyroX, float gyroY, float gyroZ, 18 | float accelX, float accelY, float accelZ, float deltaTime) override 19 | { 20 | gamepadMotion.ProcessMotion(gyroX, gyroY, gyroZ, accelX, accelY, accelZ, deltaTime); 21 | } 22 | 23 | // reading the current state 24 | virtual void GetCalibratedGyro(float& x, float& y, float& z) override 25 | { 26 | gamepadMotion.GetCalibratedGyro(x, y, z); 27 | } 28 | 29 | virtual void GetGravity(float& x, float& y, float& z) override 30 | { 31 | gamepadMotion.GetGravity(x, y, z); 32 | } 33 | 34 | virtual void GetProcessedAcceleration(float& x, float& y, float& z) override 35 | { 36 | gamepadMotion.GetProcessedAcceleration(x, y, z); 37 | } 38 | 39 | virtual void GetOrientation(float& w, float& x, float& y, float& z) override 40 | { 41 | gamepadMotion.GetOrientation(w, x, y, z); 42 | } 43 | 44 | // gyro calibration functions 45 | virtual void StartContinuousCalibration() override 46 | { 47 | gamepadMotion.StartContinuousCalibration(); 48 | } 49 | 50 | virtual void PauseContinuousCalibration() override 51 | { 52 | gamepadMotion.PauseContinuousCalibration(); 53 | } 54 | 55 | virtual void ResetContinuousCalibration() override 56 | { 57 | gamepadMotion.ResetContinuousCalibration(); 58 | } 59 | 60 | virtual void GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset) override 61 | { 62 | gamepadMotion.GetCalibrationOffset(xOffset, yOffset, zOffset); 63 | } 64 | 65 | virtual void SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight) override 66 | { 67 | gamepadMotion.SetCalibrationOffset(xOffset, yOffset, zOffset, weight); 68 | } 69 | 70 | virtual void SetAutoCalibration(bool enabled, float gyroThreshold, float accelThreshold) override 71 | { 72 | if (enabled) 73 | { 74 | gamepadMotion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Stillness | GamepadMotionHelpers::CalibrationMode::SensorFusion); 75 | gamepadMotion.Settings.StillnessGyroDelta = gyroThreshold; 76 | gamepadMotion.Settings.StillnessAccelDelta = accelThreshold; 77 | } 78 | else 79 | { 80 | gamepadMotion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Manual); 81 | } 82 | } 83 | 84 | void virtual ResetMotion() override 85 | { 86 | gamepadMotion.ResetMotion(); 87 | } 88 | }; 89 | 90 | MotionIf* MotionIf::getNew() 91 | { 92 | return new MotionImpl(); 93 | } -------------------------------------------------------------------------------- /JoyShockMapper/src/SettingsManager.cpp: -------------------------------------------------------------------------------- 1 | #include "SettingsManager.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SettingsManager::SettingsMap SettingsManager::_settings; 7 | 8 | bool SettingsManager::add(SettingID id, JSMVariableBase *setting) 9 | { 10 | return _settings.emplace(id, setting).second; 11 | } 12 | 13 | 14 | void SettingsManager::resetAllSettings() 15 | { 16 | static constexpr auto callReset = [](SettingsMap::value_type &kvPair) 17 | { 18 | kvPair.second->reset(); 19 | }; 20 | static constexpr auto exceptions = [](SettingsMap::value_type &kvPair) 21 | { 22 | static set exceptions = { 23 | SettingID::AUTOLOAD, 24 | SettingID::JSM_DIRECTORY, 25 | SettingID::HIDE_MINIMIZED, 26 | SettingID::VIRTUAL_CONTROLLER, 27 | SettingID::ADAPTIVE_TRIGGER, 28 | SettingID::RUMBLE, 29 | }; 30 | return exceptions.find(kvPair.first) == exceptions.end(); 31 | }; 32 | ranges::for_each(_settings | views::filter(exceptions), callReset); 33 | } 34 | -------------------------------------------------------------------------------- /JoyShockMapper/src/Stick.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Stick.h" 3 | #include "JSMVariable.hpp" 4 | 5 | extern vector grid_mappings; 6 | extern vector mappings; 7 | 8 | void ScrollAxis::init(DigitalButton& negativeBtn, DigitalButton& positiveBtn, int touchpadId) 9 | { 10 | _negativeButton = &negativeBtn; 11 | _positiveButton = &positiveBtn; 12 | _touchpadId = touchpadId; 13 | } 14 | 15 | void ScrollAxis::processScroll(float distance, float sens, chrono::steady_clock::time_point now) 16 | { 17 | if (!_negativeButton || !_positiveButton) 18 | return; // not initalized! 19 | 20 | _leftovers += distance; 21 | //if (distance != 0) 22 | // DEBUG_LOG << " leftover is now " << _leftovers << '\n'; 23 | //"[" << _negativeId << "," << _positiveId << "] moved " << distance << " so that 24 | 25 | Pressed isPressed; 26 | isPressed.time_now = now; 27 | isPressed.turboTime = 50; 28 | isPressed.holdTime = 150; 29 | Released isReleased; 30 | isReleased.time_now = now; 31 | isReleased.turboTime = 50; 32 | isReleased.holdTime = 150; 33 | if (_pressedBtn != ButtonID::NONE) 34 | { 35 | float pressedTime = 0; 36 | if (_pressedBtn == _negativeButton->_id) 37 | { 38 | GetDuration dur{ now }; 39 | pressedTime = _negativeButton->sendEvent(dur).out_duration; 40 | if (pressedTime < MAGIC_TAP_DURATION) 41 | { 42 | _negativeButton->sendEvent(isPressed); 43 | _positiveButton->sendEvent(isReleased); 44 | return; 45 | } 46 | } 47 | else // _pressedBtn == _positiveButton->_id 48 | { 49 | GetDuration dur{ now }; 50 | pressedTime = _positiveButton->sendEvent(dur).out_duration; 51 | if (pressedTime < MAGIC_TAP_DURATION) 52 | { 53 | _negativeButton->sendEvent(isReleased); 54 | _positiveButton->sendEvent(isPressed); 55 | return; 56 | } 57 | } 58 | // pressed time > TAP_DURATION meaning release the tap 59 | _negativeButton->sendEvent(isReleased); 60 | _positiveButton->sendEvent(isReleased); 61 | _pressedBtn = ButtonID::NONE; 62 | } 63 | else if (fabsf(_leftovers) > sens) 64 | { 65 | if (_leftovers > 0) 66 | { 67 | _negativeButton->sendEvent(isPressed); 68 | _positiveButton->sendEvent(isReleased); 69 | _pressedBtn = _negativeButton->_id; 70 | } 71 | else 72 | { 73 | _negativeButton->sendEvent(isReleased); 74 | _positiveButton->sendEvent(isPressed); 75 | _pressedBtn = _positiveButton->_id; 76 | } 77 | _leftovers = _leftovers > 0 ? _leftovers - sens : _leftovers + sens; 78 | } 79 | // else do nothing and accumulate leftovers 80 | } 81 | 82 | void ScrollAxis::reset(chrono::steady_clock::time_point now) 83 | { 84 | _leftovers = 0; 85 | Released isReleased; 86 | isReleased.time_now = now; 87 | isReleased.turboTime = 50; 88 | isReleased.holdTime = 150; 89 | _negativeButton->sendEvent(isReleased); 90 | _positiveButton->sendEvent(isReleased); 91 | _pressedBtn = ButtonID::NONE; 92 | } 93 | 94 | TouchStick::TouchStick(int index, shared_ptr common, int handle) 95 | : Stick(SettingID::TOUCH_DEADZONE_INNER, SettingID::ZERO, SettingID::TOUCH_RING_MODE, SettingID::TOUCH_STICK_MODE, 96 | ButtonID::TRING, ButtonID::TLEFT, ButtonID::TRIGHT, ButtonID::TUP, ButtonID::TDOWN) 97 | { 98 | this->_touchpadIndex = index; 99 | buttons.emplace(ButtonID::TUP, DigitalButton(common, mappings[int(ButtonID::TUP)])); 100 | buttons.emplace(ButtonID::TDOWN, DigitalButton(common, mappings[int(ButtonID::TDOWN)])); 101 | buttons.emplace(ButtonID::TLEFT, DigitalButton(common, mappings[int(ButtonID::TLEFT)])); 102 | buttons.emplace(ButtonID::TRIGHT, DigitalButton(common, mappings[int(ButtonID::TRIGHT)])); 103 | buttons.emplace(ButtonID::TRING, DigitalButton(common, mappings[int(ButtonID::TRING)])); 104 | } 105 | 106 | -------------------------------------------------------------------------------- /JoyShockMapper/src/linux/Gamepad.cpp: -------------------------------------------------------------------------------- 1 | #include "Gamepad.h" 2 | 3 | size_t Gamepad::_count = 0; 4 | 5 | Gamepad::Gamepad() 6 | { 7 | ++_count; 8 | } 9 | 10 | Gamepad::~Gamepad() 11 | { 12 | --_count; 13 | } 14 | 15 | 16 | class GamepadImpl : public Gamepad 17 | { 18 | public: 19 | // TODO: Implement this class by interacting with some external virtual controller software 20 | 21 | GamepadImpl() 22 | { 23 | 24 | } 25 | 26 | virtual ~GamepadImpl() 27 | { 28 | 29 | } 30 | 31 | // is not overriding anything? removed override from function 32 | virtual bool isInitialized(std::string* errorMsg = nullptr) { 33 | 34 | } 35 | 36 | virtual void setButton(KeyCode btn, bool pressed) override 37 | { 38 | 39 | } 40 | 41 | virtual void setLeftStick(float x, float y) override 42 | { 43 | 44 | } 45 | 46 | virtual void setRightStick(float x, float y) override 47 | { 48 | 49 | } 50 | 51 | virtual void setLeftTrigger(float) override 52 | { 53 | 54 | } 55 | 56 | virtual void setRightTrigger(float) override 57 | { 58 | 59 | } 60 | 61 | virtual void update() override 62 | { 63 | 64 | } 65 | }; 66 | 67 | Gamepad *Gamepad::getNew(ControllerScheme scheme, Callback notification) 68 | { 69 | // return new GamepadImpl(); 70 | return nullptr; 71 | } 72 | -------------------------------------------------------------------------------- /JoyShockMapper/src/linux/Init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "InputHelpers.h" 14 | 15 | static void terminateHandler(int signo) 16 | { 17 | if (signo != SIGTERM && signo != SIGINT) 18 | { 19 | std::fprintf(stderr, "Caught unexpected signal %d\n", signo); 20 | return; 21 | } 22 | 23 | WriteToConsole("QUIT"); 24 | } 25 | 26 | static const int initialize = [] { 27 | std::string appRootDir{}; 28 | 29 | // Check if we're running as an AppImage, and if so set up the root directory 30 | const auto APPDIR = ::getenv("APPDIR"); 31 | if (APPDIR != nullptr) 32 | { 33 | const auto userId = ::getuid(); 34 | std::printf("\n\033[1;33mRunning as AppImage, make sure user %d has RW permissions to /dev/uinput and /dev/hidraw*\n\033[0m", userId); 35 | appRootDir = APPDIR; 36 | } 37 | 38 | // Create the configuration directories for JSM 39 | std::string configDirectory; 40 | 41 | // Get the default config path, or if not defined set the default to $HOME/.config 42 | const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME"); 43 | if (XDG_CONFIG_HOME == nullptr) 44 | { 45 | configDirectory = std::string{ getenv("HOME") } + "/.config"; 46 | ::mkdir(configDirectory.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 47 | } 48 | else 49 | { 50 | configDirectory = XDG_CONFIG_HOME; 51 | } 52 | 53 | // Create the base configuration directory if it doesn't already exist 54 | ::mkdir(configDirectory.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 55 | configDirectory = configDirectory + "/JoyShockMapper/"; 56 | ::mkdir(configDirectory.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 57 | 58 | // Create the AutoLoad directory 59 | ::mkdir((configDirectory + "AutoLoad/").c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 60 | 61 | // Create the GyroConfigs directory 62 | configDirectory += "GyroConfigs/"; 63 | ::mkdir(configDirectory.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 64 | 65 | const auto globalConfigDirectory = appRootDir + "/etc/JoyShockMapper/GyroConfigs/"; 66 | 67 | // Copy the configuration files from etc to the local configuration, without overwritting 68 | const auto globalConfigFiles = ListDirectory(globalConfigDirectory); 69 | const auto localConfigFiles = ListDirectory(configDirectory); 70 | 71 | for (const auto &gFile : globalConfigFiles) 72 | { 73 | const auto it = std::find(localConfigFiles.begin(), localConfigFiles.end(), gFile); 74 | if (it == localConfigFiles.end()) 75 | { 76 | auto source = ::open((globalConfigDirectory + gFile).c_str(), O_RDONLY, 0); 77 | auto dest = ::open((configDirectory + gFile).c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); 78 | 79 | struct stat stat_source; 80 | ::fstat(source, &stat_source); 81 | ::sendfile(dest, source, 0, stat_source.st_size); 82 | 83 | ::close(source); 84 | ::close(dest); 85 | } 86 | } 87 | 88 | signal(SIGTERM, &terminateHandler); 89 | signal(SIGINT, &terminateHandler); 90 | 91 | return 0; 92 | }(); 93 | -------------------------------------------------------------------------------- /JoyShockMapper/src/linux/PlatformDefinitions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "JoyShockMapper.h" 12 | #include "PlatformDefinitions.h" 13 | 14 | 15 | // https://www.theurbanpenguin.com/4184-2/ 16 | #define FOREGROUND_BLUE 34 // text color is blue. 17 | #define FOREGROUND_GREEN 32 // text color is green. 18 | #define FOREGROUND_RED 31 // text color is red. 19 | #define FOREGROUND_YELLOW 33 // Text color is yellow 20 | #define FOREGROUND_INTENSITY 0x0100 // text color is bold. 21 | #define DEFAULT_COLOR 37 // text color is white 22 | 23 | template 24 | struct ColorStream : public std::stringbuf 25 | { 26 | ~ColorStream() 27 | { 28 | 29 | (*stdio) << "\033[" << (color >> 8) << ';' << (color & 0x00FF) << 'm' << str() << "\033[0;" << DEFAULT_COLOR << 'm'; 30 | } 31 | }; 32 | 33 | streambuf *Log::makeBuffer(Level level) 34 | { 35 | switch (level) 36 | { 37 | case Level::ERR: 38 | return new ColorStream<&std::cerr, FOREGROUND_RED | FOREGROUND_INTENSITY>(); 39 | case Level::WARN: 40 | return new ColorStream<&cout, FOREGROUND_YELLOW | FOREGROUND_INTENSITY>(); 41 | case Level::INFO: 42 | return new ColorStream<&cout, FOREGROUND_BLUE | FOREGROUND_INTENSITY>(); 43 | #if defined(NDEBUG) // release 44 | case Level::UT: 45 | return new NullBuffer(); // unused 46 | #else 47 | case Level::UT: 48 | return new ColorStream<&cout, FOREGROUND_BLUE | FOREGROUND_RED>(); // purplish 49 | #endif 50 | case Level::BOLD: 51 | return new ColorStream<&cout, FOREGROUND_GREEN | FOREGROUND_INTENSITY>(); 52 | default: 53 | return new ColorStream<&std::cout, FOREGROUND_GREEN>(); 54 | } 55 | } 56 | 57 | const char *AUTOLOAD_FOLDER() { 58 | std::string directory; 59 | 60 | const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME"); 61 | if (XDG_CONFIG_HOME == nullptr) 62 | { 63 | directory = std::string{ getenv("HOME") } + "/.config"; 64 | } 65 | else 66 | { 67 | directory = XDG_CONFIG_HOME; 68 | } 69 | 70 | directory = directory + "/JoyShockMapper/AutoLoad/"; 71 | return strdup(directory.c_str()); 72 | }; 73 | 74 | const char *GYRO_CONFIGS_FOLDER() { 75 | std::string directory; 76 | 77 | const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME"); 78 | if (XDG_CONFIG_HOME == nullptr) 79 | { 80 | directory = std::string{ getenv("HOME") } + "/.config"; 81 | } 82 | else 83 | { 84 | directory = XDG_CONFIG_HOME; 85 | } 86 | 87 | directory = directory + "/JoyShockMapper/GyroConfigs/"; 88 | return strdup(directory.c_str()); 89 | }; 90 | 91 | const char *BASE_JSM_CONFIG_FOLDER() { 92 | std::string directory; 93 | 94 | const auto XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME"); 95 | if (XDG_CONFIG_HOME == nullptr) 96 | { 97 | directory = std::string{ getenv("HOME") } + "/.config"; 98 | } 99 | else 100 | { 101 | directory = XDG_CONFIG_HOME; 102 | } 103 | 104 | directory = directory + "/JoyShockMapper/"; 105 | return strdup(directory.c_str()); 106 | }; 107 | 108 | unsigned long GetCurrentProcessId() 109 | { 110 | return ::getpid(); 111 | } 112 | 113 | /// Valid inputs: 114 | /// 0-9, N0-N9, F1-F29, A-Z, (L, R, )CONTROL, (L, R, )ALT, (L, R, )SHIFT, TAB, ENTER 115 | /// (L, M, R)MOUSE, SCROLL(UP, DOWN) 116 | /// NONE 117 | /// And characters: ; ' , . / \ [ ] + - ` 118 | /// Yes, this looks slow. But it's only there to help set up faster mappings 119 | WORD nameToKey(in_string name) 120 | { 121 | // https://msdn.microsoft.com/en-us/library/dd375731%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 122 | auto length = name.length(); 123 | if (length == 1) 124 | { 125 | // direct mapping to a number or character key 126 | char character = name.at(0); 127 | if (character >= '0' && character <= '9') 128 | { 129 | return character - '0' + 0x30; 130 | } 131 | if (character >= 'A' && character <= 'Z') 132 | { 133 | return character - 'A' + 0x41; 134 | } 135 | if (character == '+') 136 | { 137 | return VK_OEM_PLUS; 138 | } 139 | if (character == '-') 140 | { 141 | return VK_OEM_MINUS; 142 | } 143 | if (character == ',') 144 | { 145 | return VK_OEM_COMMA; 146 | } 147 | if (character == '.') 148 | { 149 | return VK_OEM_PERIOD; 150 | } 151 | if (character == ';') 152 | { 153 | return VK_OEM_1; 154 | } 155 | if (character == '/') 156 | { 157 | return VK_OEM_2; 158 | } 159 | if (character == '`') 160 | { 161 | return VK_OEM_3; 162 | } 163 | if (character == '[') 164 | { 165 | return VK_OEM_4; 166 | } 167 | if (character == '\\') 168 | { 169 | return VK_OEM_5; 170 | } 171 | if (character == ']') 172 | { 173 | return VK_OEM_6; 174 | } 175 | if (character == '\'') 176 | { 177 | return VK_OEM_7; 178 | } 179 | } 180 | if (length == 2) 181 | { 182 | // function key? 183 | char character = name.at(0); 184 | char character2 = name.at(1); 185 | if (character == 'F') 186 | { 187 | if (character2 >= '1' && character2 <= '9') 188 | { 189 | return character2 - '1' + VK_F1; 190 | } 191 | } 192 | else if (character == 'N') 193 | { 194 | if (character2 >= '0' && character2 <= '9') 195 | { 196 | return character2 - '0' + VK_NUMPAD0; 197 | } 198 | } 199 | } 200 | if (length == 3) 201 | { 202 | // could be function keys still 203 | char character = name.at(0); 204 | char character2 = name.at(1); 205 | char character3 = name.at(2); 206 | if (character == 'F') 207 | { 208 | if (character2 == '1' || character2 <= '2') 209 | { 210 | if (character3 >= '0' && character3 <= '9') 211 | { 212 | return (character2 - '1') * 10 + VK_F10 + (character3 - '0'); 213 | } 214 | } 215 | } 216 | } 217 | if (name.compare("LEFT") == 0) 218 | { 219 | return VK_LEFT; 220 | } 221 | if (name.compare("RIGHT") == 0) 222 | { 223 | return VK_RIGHT; 224 | } 225 | if (name.compare("UP") == 0) 226 | { 227 | return VK_UP; 228 | } 229 | if (name.compare("DOWN") == 0) 230 | { 231 | return VK_DOWN; 232 | } 233 | if (name.compare("SPACE") == 0) 234 | { 235 | return VK_SPACE; 236 | } 237 | if (name.compare("CONTROL") == 0) 238 | { 239 | return VK_CONTROL; 240 | } 241 | if (name.compare("LCONTROL") == 0) 242 | { 243 | return VK_LCONTROL; 244 | } 245 | if (name.compare("RCONTROL") == 0) 246 | { 247 | return VK_RCONTROL; 248 | } 249 | if (name.compare("SHIFT") == 0) 250 | { 251 | return VK_SHIFT; 252 | } 253 | if (name.compare("LSHIFT") == 0) 254 | { 255 | return VK_LSHIFT; 256 | } 257 | if (name.compare("RSHIFT") == 0) 258 | { 259 | return VK_RSHIFT; 260 | } 261 | if (name.compare("ALT") == 0) 262 | { 263 | return VK_MENU; 264 | } 265 | if (name.compare("LALT") == 0) 266 | { 267 | return VK_LMENU; 268 | } 269 | if (name.compare("RALT") == 0) 270 | { 271 | return VK_RMENU; 272 | } 273 | if (name.compare("TAB") == 0) 274 | { 275 | return VK_TAB; 276 | } 277 | if (name.compare("ENTER") == 0) 278 | { 279 | return VK_RETURN; 280 | } 281 | if (name.compare("ESC") == 0) 282 | { 283 | return VK_ESCAPE; 284 | } 285 | if (name.compare("PAGEUP") == 0) 286 | { 287 | return VK_PRIOR; 288 | } 289 | if (name.compare("PAGEDOWN") == 0) 290 | { 291 | return VK_NEXT; 292 | } 293 | if (name.compare("HOME") == 0) 294 | { 295 | return VK_HOME; 296 | } 297 | if (name.compare("END") == 0) 298 | { 299 | return VK_END; 300 | } 301 | if (name.compare("INSERT") == 0) 302 | { 303 | return VK_INSERT; 304 | } 305 | if (name.compare("DELETE") == 0) 306 | { 307 | return VK_DELETE; 308 | } 309 | if (name.compare("LMOUSE") == 0) 310 | { 311 | return VK_LBUTTON; 312 | } 313 | if (name.compare("RMOUSE") == 0) 314 | { 315 | return VK_RBUTTON; 316 | } 317 | if (name.compare("MMOUSE") == 0) 318 | { 319 | return VK_MBUTTON; 320 | } 321 | if (name.compare("BMOUSE") == 0) 322 | { 323 | return VK_XBUTTON1; 324 | } 325 | if (name.compare("FMOUSE") == 0) 326 | { 327 | return VK_XBUTTON2; 328 | } 329 | if (name.compare("SCROLLDOWN") == 0) 330 | { 331 | return V_WHEEL_DOWN; 332 | } 333 | if (name.compare("SCROLLUP") == 0) 334 | { 335 | return V_WHEEL_UP; 336 | } 337 | if (name.compare("BACKSPACE") == 0) 338 | { 339 | return VK_BACK; 340 | } 341 | if (name.compare("LWINDOWS") == 0) 342 | { 343 | return VK_LWIN; 344 | } 345 | if (name.compare("RWINDOWS") == 0) 346 | { 347 | return VK_RWIN; 348 | } 349 | if (name.compare("CONTEXT") == 0) 350 | { 351 | return VK_APPS; 352 | } 353 | if (name.compare("SCREENSHOT") == 0) 354 | { 355 | return VK_SNAPSHOT; 356 | } 357 | if (name.compare("NONE") == 0) 358 | { 359 | return NO_HOLD_MAPPED; 360 | } 361 | if (name.compare("CALIBRATE") == 0) 362 | { 363 | return CALIBRATE; 364 | } 365 | if (name.compare("GYRO_INV_X") == 0) 366 | { 367 | return GYRO_INV_X; 368 | } 369 | if (name.compare("GYRO_INV_Y") == 0) 370 | { 371 | return GYRO_INV_Y; 372 | } 373 | if (name.compare("GYRO_INVERT") == 0) 374 | { 375 | return GYRO_INVERT; 376 | } 377 | if (name.compare("GYRO_TRACK_X") == 0) 378 | { 379 | return GYRO_TRACK_X; 380 | } 381 | if (name.compare("GYRO_TRACK_Y") == 0) 382 | { 383 | return GYRO_TRACK_Y; 384 | } 385 | if (name.compare("GYRO_TRACKBALL") == 0) 386 | { 387 | return GYRO_TRACKBALL; 388 | } 389 | if (name.compare("GYRO_ON") == 0) 390 | { 391 | return GYRO_ON_BIND; 392 | } 393 | if (name.compare("GYRO_OFF") == 0) 394 | { 395 | return GYRO_OFF_BIND; 396 | } 397 | return 0x00; 398 | } 399 | -------------------------------------------------------------------------------- /JoyShockMapper/src/linux/StatusNotifierItem.cpp: -------------------------------------------------------------------------------- 1 | #include "linux/StatusNotifierItem.h" 2 | 3 | #include 4 | #include 5 | 6 | 7 | TrayIcon *TrayIcon::getNew(TrayIconData applicationName, std::function &&beforeShow) 8 | { 9 | return new StatusNotifierItem(applicationName, std::forward>(beforeShow)); 10 | } 11 | 12 | 13 | StatusNotifierItem::StatusNotifierItem(TrayIconData, std::function &&beforeShow) 14 | : thread_{ [this, &beforeShow] { 15 | int argc = 0; 16 | gtk_init(&argc, nullptr); 17 | 18 | std::string iconPath{}; 19 | const auto APPDIR = ::getenv("APPDIR"); 20 | if (APPDIR != nullptr) 21 | { 22 | iconPath = APPDIR; 23 | iconPath += "/usr/share/icons/hicolor/24x24/status/jsm-status-dark.svg"; 24 | gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), iconPath.c_str()); 25 | } 26 | else 27 | { 28 | iconPath = "jsm-status-dark"; 29 | } 30 | 31 | menu_ = std::unique_ptr{ GTK_MENU(gtk_menu_new()), &g_object_unref }; 32 | 33 | indicator_ = app_indicator_new(APPLICATION_RDN APPLICATION_NAME, iconPath.c_str(), APP_INDICATOR_CATEGORY_APPLICATION_STATUS); 34 | app_indicator_set_status(indicator_, APP_INDICATOR_STATUS_ACTIVE); 35 | app_indicator_set_menu(indicator_, menu_.get()); 36 | 37 | beforeShow(); 38 | 39 | gtk_main(); 40 | } } 41 | { 42 | } 43 | 44 | StatusNotifierItem::~StatusNotifierItem() 45 | { 46 | g_idle_add([](void *) -> int { 47 | gtk_main_quit(); 48 | return false; 49 | }, 50 | this); 51 | 52 | thread_.join(); 53 | } 54 | 55 | bool StatusNotifierItem::Show() 56 | { 57 | g_idle_add([](void *self) -> int { 58 | gtk_widget_show_all(GTK_WIDGET(static_cast(self)->menu_.get())); 59 | return false; 60 | }, 61 | this); 62 | 63 | return true; 64 | } 65 | 66 | bool StatusNotifierItem::Hide() 67 | { 68 | g_idle_add([](void *self) -> int { 69 | gtk_widget_hide(GTK_WIDGET(static_cast(self)->menu_.get())); 70 | return false; 71 | }, 72 | this); 73 | 74 | return true; 75 | } 76 | 77 | bool StatusNotifierItem::SendNotification(const std::string &) 78 | { 79 | return true; 80 | } 81 | 82 | void StatusNotifierItem::AddMenuItem(const std::string &label, ClickCallbackType &&onClick) 83 | { 84 | // Disable show-hide console since this is not supported on Linux 85 | if (label == "Show Console") 86 | return; 87 | 88 | menuItems_.emplace_back(GTK_MENU_ITEM(gtk_menu_item_new_with_label(label.c_str()))); 89 | 90 | auto &menuItem = menuItems_.back(); 91 | 92 | callbacks_.emplace_back(std::move(onClick)); 93 | auto &cb = callbacks_.back(); 94 | 95 | g_signal_connect(menuItem, "activate", G_CALLBACK(&StatusNotifierItem::OnActivate), const_cast(&cb)); 96 | 97 | gtk_container_add(GTK_CONTAINER(menu_.get()), GTK_WIDGET(menuItem)); 98 | gtk_widget_show_all(GTK_WIDGET(menuItem)); 99 | } 100 | 101 | void StatusNotifierItem::AddMenuItem(const std::string &label, ClickCallbackTypeChecked &&onClick, StateCallbackType &&getState) 102 | { 103 | // Disable show-hide console since this is not supported on Linux 104 | if (label == "Show Console") 105 | return; 106 | 107 | menuItems_.emplace_back(GTK_MENU_ITEM(gtk_check_menu_item_new_with_label(label.c_str()))); 108 | 109 | auto &menuItem = menuItems_.back(); 110 | 111 | gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuItem), getState()); 112 | 113 | callbacks_.emplace_back([=] { 114 | onClick(!getState()); 115 | }); 116 | auto &cb = callbacks_.back(); 117 | 118 | g_signal_connect(menuItem, "activate", G_CALLBACK(&StatusNotifierItem::OnActivate), const_cast(&cb)); 119 | 120 | gtk_container_add(GTK_CONTAINER(menu_.get()), GTK_WIDGET(menuItem)); 121 | gtk_widget_show_all(GTK_WIDGET(menuItem)); 122 | } 123 | 124 | void StatusNotifierItem::AddMenuItem(const std::string &l, const std::string &sl, ClickCallbackType &&onClick) 125 | { 126 | // Disable show-hide console since this is not supported on Linux 127 | if (l == "Show Console") 128 | return; 129 | 130 | const auto *label = l.c_str(); 131 | const auto *subLabel = sl.c_str(); 132 | 133 | const auto it = std::find_if(menuItems_.begin(), menuItems_.end(), [&label](GtkMenuItem *item) { 134 | return strcmp(label, gtk_menu_item_get_label(item)) == 0; 135 | }); 136 | 137 | GtkMenuItem *menuItem = nullptr; 138 | 139 | if (it == menuItems_.end()) 140 | { 141 | menuItems_.emplace_back(GTK_MENU_ITEM(gtk_check_menu_item_new_with_label(label))); 142 | menuItem = menuItems_.back(); 143 | gtk_container_add(GTK_CONTAINER(menu_.get()), GTK_WIDGET(menuItem)); 144 | gtk_widget_show_all(GTK_WIDGET(menuItem)); 145 | } 146 | else 147 | { 148 | menuItem = *it; 149 | } 150 | 151 | auto subMenuIt = subMenus_.find(menuItem); 152 | if (subMenuIt == subMenus_.end()) 153 | { 154 | subMenuIt = subMenus_.emplace(menuItem, std::pair>{ GTK_MENU(gtk_menu_new()), std::vector{} }).first; 155 | gtk_menu_item_set_submenu(menuItem, GTK_WIDGET(subMenuIt->second.first)); 156 | gtk_widget_show_all(GTK_WIDGET(menuItem)); 157 | } 158 | 159 | auto &subMenu = subMenuIt->second.first; 160 | auto &subMenuItems = subMenuIt->second.second; 161 | 162 | subMenuItems.emplace_back(GTK_MENU_ITEM(gtk_menu_item_new_with_label(subLabel))); 163 | auto &subMenuItem = subMenuItems.back(); 164 | 165 | callbacks_.emplace_back(std::move(onClick)); 166 | auto &cb = callbacks_.back(); 167 | g_signal_connect(subMenuItem, "activate", G_CALLBACK(&StatusNotifierItem::OnActivate), const_cast(&cb)); 168 | 169 | gtk_container_add(GTK_CONTAINER(subMenu), GTK_WIDGET(subMenuItem)); 170 | gtk_widget_show_all(GTK_WIDGET(subMenuItem)); 171 | } 172 | 173 | void StatusNotifierItem::ClearMenuMap() 174 | { 175 | } 176 | 177 | void StatusNotifierItem::OnActivate(GtkMenuItem *, void *data) noexcept 178 | { 179 | auto *cb = static_cast(data); 180 | (*cb)(); 181 | } 182 | 183 | StatusNotifierItem::operator bool() 184 | { 185 | return true; 186 | } 187 | -------------------------------------------------------------------------------- /JoyShockMapper/src/linux/Whitelister.cpp: -------------------------------------------------------------------------------- 1 | #include "Whitelister.h" 2 | 3 | 4 | class WhitelisterImpl : public Whitelister 5 | { 6 | public: 7 | // TODO: Implement this class by interacting with some external whitelisting software 8 | 9 | WhitelisterImpl() 10 | { 11 | 12 | } 13 | 14 | virtual bool ShowConsole() override 15 | { 16 | 17 | } 18 | virtual bool IsAvailable() override 19 | { 20 | 21 | } 22 | 23 | virtual bool Add(string* optErrMsg = nullptr) override 24 | { 25 | 26 | } 27 | virtual bool Remove(string* optErrMsg = nullptr) override 28 | { 29 | 30 | } 31 | }; 32 | 33 | Whitelister* Whitelister::getNew(bool add) 34 | { 35 | // return new WhitelisterImpl(); 36 | return nullptr; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /JoyShockMapper/src/operators.cpp: -------------------------------------------------------------------------------- 1 | #include "JoyShockMapper.h" 2 | #include "JslWrapper.h" 3 | #include "ColorCodes.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static optional getFloat(const string &str, size_t *newpos = nullptr) 13 | { 14 | try 15 | { 16 | float f = stof(str, newpos); 17 | return f; 18 | } 19 | catch (invalid_argument) 20 | { 21 | return nullopt; 22 | } 23 | } 24 | 25 | istream &operator>>(istream &in, ButtonID &rhv) 26 | { 27 | string s; 28 | in >> s; 29 | if (s.compare("-") == 0) 30 | rhv = ButtonID::MINUS; 31 | else if (s.compare("+") == 0) 32 | rhv = ButtonID::PLUS; 33 | else 34 | { 35 | auto opt = magic_enum::enum_cast(s); 36 | rhv = opt ? *opt : ButtonID::INVALID; 37 | } 38 | return in; 39 | } 40 | 41 | ostream &operator<<(ostream &out, const ButtonID &rhv) 42 | { 43 | if (rhv == ButtonID::PLUS) 44 | out << "+"; 45 | else if (rhv == ButtonID::MINUS) 46 | out << "-"; 47 | else 48 | out << magic_enum::enum_name(rhv); 49 | return out; 50 | } 51 | 52 | istream &operator>>(istream &in, FlickSnapMode &fsm) 53 | { 54 | string name; 55 | in >> name; 56 | if (name.compare("0") == 0) 57 | { 58 | fsm = FlickSnapMode::NONE; 59 | } 60 | else if (name.compare("4") == 0) 61 | { 62 | fsm = FlickSnapMode::FOUR; 63 | } 64 | else if (name.compare("8") == 0) 65 | { 66 | fsm = FlickSnapMode::EIGHT; 67 | } 68 | else 69 | { 70 | auto opt = magic_enum::enum_cast(name); 71 | fsm = opt ? *opt : FlickSnapMode::INVALID; 72 | } 73 | return in; 74 | } 75 | 76 | ostream &operator<<(ostream &out, const FlickSnapMode &fsm) 77 | { 78 | if (fsm == FlickSnapMode::FOUR) 79 | out << "4"; 80 | else if (fsm == FlickSnapMode::EIGHT) 81 | out << "8"; 82 | else 83 | out << magic_enum::enum_name(fsm); 84 | return out; 85 | } 86 | 87 | istream &operator>>(istream &in, TriggerMode &tm) 88 | { 89 | string name; 90 | in >> name; 91 | if (name.compare("PS_L2") == 0) 92 | { 93 | tm = TriggerMode::X_LT; 94 | } 95 | else if (name.compare("PS_R2") == 0) 96 | { 97 | tm = TriggerMode::X_RT; 98 | } 99 | else 100 | { 101 | auto opt = magic_enum::enum_cast(name); 102 | tm = opt ? *opt : TriggerMode::INVALID; 103 | } 104 | return in; 105 | } 106 | 107 | istream &operator>>(istream &in, GyroSettings &gyro_settings) 108 | { 109 | string valueName; 110 | in >> valueName; 111 | stringstream ss(valueName); 112 | auto rhsMappingIndex = magic_enum::enum_cast(valueName); 113 | if (valueName.compare("NONE\\") == 0) // Special handling to assign none to a modeshift 114 | { 115 | gyro_settings.button = ButtonID::NONE; 116 | gyro_settings.ignore_mode = GyroIgnoreMode::BUTTON; 117 | } 118 | else if (rhsMappingIndex && *rhsMappingIndex >= ButtonID::NONE) 119 | { 120 | gyro_settings.button = *rhsMappingIndex; 121 | gyro_settings.ignore_mode = GyroIgnoreMode::BUTTON; 122 | } 123 | else 124 | { 125 | auto ignoreMode = magic_enum::enum_cast(valueName); 126 | if (ignoreMode == GyroIgnoreMode::LEFT_STICK) 127 | { 128 | gyro_settings.button = ButtonID::NONE; 129 | gyro_settings.ignore_mode = GyroIgnoreMode::LEFT_STICK; 130 | } 131 | else if (ignoreMode == GyroIgnoreMode::RIGHT_STICK) 132 | { 133 | gyro_settings.button = ButtonID::NONE; 134 | gyro_settings.ignore_mode = GyroIgnoreMode::RIGHT_STICK; 135 | } 136 | else 137 | { 138 | gyro_settings.ignore_mode = GyroIgnoreMode::INVALID; 139 | in.setstate(in.failbit); 140 | } 141 | } 142 | return in; 143 | } 144 | 145 | ostream &operator<<(ostream &out, const GyroSettings &gyro_settings) 146 | { 147 | if (gyro_settings.ignore_mode == GyroIgnoreMode::BUTTON) 148 | { 149 | out << gyro_settings.button; 150 | } 151 | else 152 | { 153 | out << gyro_settings.ignore_mode; 154 | } 155 | return out; 156 | } 157 | 158 | bool operator==(const GyroSettings &lhs, const GyroSettings &rhs) 159 | { 160 | return lhs.always_off == rhs.always_off && 161 | lhs.button == rhs.button && 162 | lhs.ignore_mode == rhs.ignore_mode; 163 | } 164 | 165 | ostream &operator<<(ostream &out, const FloatXY &fxy) 166 | { 167 | out << fxy.first; 168 | if (fxy.first != fxy.second) 169 | out << " " << fxy.second; 170 | return out; 171 | } 172 | 173 | istream &operator>>(istream &in, FloatXY &fxy) 174 | { 175 | size_t pos; 176 | string value; 177 | getline(in, value); 178 | auto sens = getFloat(value, &pos); 179 | if (sens) 180 | { 181 | FloatXY newSens{ *sens, *sens }; 182 | sens = getFloat(&value[pos]); 183 | if (sens) 184 | { 185 | newSens.second = *sens; 186 | value[pos] = 0; 187 | } 188 | fxy = newSens; 189 | } 190 | else 191 | { 192 | in.setstate(in.failbit); 193 | } 194 | return in; 195 | } 196 | 197 | bool operator==(const FloatXY &lhs, const FloatXY &rhs) 198 | { 199 | // Do we need more precision than 1e-5? 200 | return fabs(lhs.first - rhs.first) < 1e-5 && 201 | fabs(lhs.second - rhs.second) < 1e-5; 202 | } 203 | 204 | istream &operator>>(istream &in, AxisMode &am) 205 | { 206 | string name; 207 | in >> name; 208 | if (name.compare("1") == 0) 209 | { 210 | am = AxisMode::STANDARD; 211 | } 212 | else if (name.compare("-1") == 0) 213 | { 214 | am = AxisMode::INVERTED; 215 | } 216 | else 217 | { 218 | auto opt = magic_enum::enum_cast(name); 219 | am = opt ? *opt : AxisMode::INVALID; 220 | } 221 | return in; 222 | } 223 | 224 | ostream &operator<<(ostream &out, const AxisSignPair &asp) 225 | { 226 | out << asp.first; 227 | if (asp.first != asp.second) 228 | { 229 | out << " " << asp.second; 230 | } 231 | return out; 232 | } 233 | 234 | istream &operator>>(istream &in, AxisSignPair &asp) 235 | { 236 | string value; 237 | getline(in, value); 238 | stringstream ss(value); 239 | ss >> asp.first; 240 | if (asp.first != AxisMode::INVALID) 241 | { 242 | if (ss.eof()) 243 | { 244 | asp.second = asp.first; 245 | } 246 | else 247 | { 248 | AxisMode second = AxisMode::INVALID; 249 | ss >> second; 250 | if (second != AxisMode::INVALID) 251 | { 252 | asp.second = second; 253 | } 254 | else 255 | { 256 | in.setstate(in.failbit); 257 | } 258 | } 259 | } 260 | else 261 | { 262 | in.setstate(in.failbit); 263 | } 264 | return in; 265 | } 266 | 267 | bool operator==(const AxisSignPair &lhs, const AxisSignPair &rhs) 268 | { 269 | return lhs.first == rhs.first && lhs.second == rhs.second; 270 | } 271 | 272 | istream &operator>>(istream &in, PathString &fxy) 273 | { 274 | // 260 is windows MAX_PATH length 275 | fxy.resize(260, '\0'); 276 | in.getline(&fxy[0], fxy.size()); 277 | fxy.resize(strlen(fxy.c_str())); 278 | return in; 279 | } 280 | 281 | istream &operator>>(istream &in, Color &color) 282 | { 283 | if (in.peek() == 'x') 284 | { 285 | char pound; 286 | in >> pound >> hex >> color.raw; 287 | } 288 | else if (in.peek() >= 'A' && in.peek() <= 'Z') 289 | { 290 | string s; 291 | in >> s; 292 | auto code = colorCodeMap.find(s); 293 | if (code == colorCodeMap.end()) 294 | { 295 | in.setstate(in.failbit); 296 | } 297 | else 298 | { 299 | color.raw = code->second; 300 | } 301 | } 302 | else 303 | { 304 | int r, g, b; 305 | in >> r >> g >> b; 306 | color.rgb.r = uint8_t(clamp(r, 0, 255)); 307 | color.rgb.g = uint8_t(clamp(g, 0, 255)); 308 | color.rgb.b = uint8_t(clamp(b, 0, 255)); 309 | } 310 | return in; 311 | } 312 | 313 | ostream &operator<<(ostream &out, const Color &color) 314 | { 315 | out << 'x' << hex << setw(2) << setfill('0') << int(color.rgb.r) 316 | << hex << setw(2) << setfill('0') << int(color.rgb.g) 317 | << hex << setw(2) << setfill('0') << int(color.rgb.b); 318 | return out; 319 | } 320 | 321 | bool operator==(const Color &lhs, const Color &rhs) 322 | { 323 | return lhs.raw == rhs.raw; 324 | } 325 | 326 | istream &operator>>(istream &in, AdaptiveTriggerSetting &atm) 327 | { 328 | string s; 329 | in >> s; 330 | auto opt = magic_enum::enum_cast(s); 331 | 332 | memset(&atm, 0, sizeof(AdaptiveTriggerSetting)); 333 | atm.mode = opt ? *opt : AdaptiveTriggerMode::INVALID; 334 | 335 | switch (atm.mode) 336 | { 337 | case AdaptiveTriggerMode::SEGMENT: 338 | in >> atm.start >> atm.end >> atm.force; 339 | break; 340 | case AdaptiveTriggerMode::RESISTANCE: 341 | in >> atm.start >> atm.force; 342 | break; 343 | case AdaptiveTriggerMode::BOW: 344 | in >> atm.start >> atm.end >> atm.force >> atm.forceExtra; 345 | break; 346 | case AdaptiveTriggerMode::GALLOPING: 347 | in >> atm.start >> atm.end >> atm.force >> atm.forceExtra >> atm.frequency; 348 | break; 349 | case AdaptiveTriggerMode::SEMI_AUTOMATIC: 350 | in >> atm.start >> atm.end >> atm.force; 351 | break; 352 | case AdaptiveTriggerMode::AUTOMATIC: 353 | in >> atm.start >> atm.force >> atm.frequency; 354 | break; 355 | case AdaptiveTriggerMode::MACHINE: 356 | in >> atm.start >> atm.end >> atm.force >> atm.forceExtra >> atm.frequency >> atm.frequencyExtra; 357 | break; 358 | default: 359 | break; 360 | } 361 | return in; 362 | } 363 | 364 | ostream &operator<<(ostream &out, const AdaptiveTriggerSetting &atm) 365 | { 366 | out << atm.mode << ' '; 367 | switch (atm.mode) 368 | { 369 | case AdaptiveTriggerMode::SEGMENT: 370 | out << atm.start << ' ' << atm.end << ' ' << atm.force; 371 | break; 372 | case AdaptiveTriggerMode::RESISTANCE: 373 | out << atm.start << ' ' << atm.force; 374 | break; 375 | case AdaptiveTriggerMode::BOW: 376 | out << atm.start << ' ' << atm.end << ' ' << atm.force << ' ' << atm.forceExtra; 377 | break; 378 | case AdaptiveTriggerMode::GALLOPING: 379 | out << atm.start << ' ' << atm.end << ' ' << atm.force << ' ' << atm.forceExtra << ' ' << atm.frequency; 380 | break; 381 | case AdaptiveTriggerMode::SEMI_AUTOMATIC: 382 | out << atm.start << ' ' << atm.end << ' ' << atm.force; 383 | break; 384 | case AdaptiveTriggerMode::AUTOMATIC: 385 | out << atm.start << ' ' << atm.force << ' ' << atm.frequency; 386 | break; 387 | case AdaptiveTriggerMode::MACHINE: 388 | out << atm.start << ' ' << atm.end << ' ' << atm.force << ' ' << atm.forceExtra << ' ' << atm.frequency << ' ' << atm.frequencyExtra; 389 | break; 390 | default: 391 | break; 392 | } 393 | return out; 394 | } 395 | 396 | bool operator==(const AdaptiveTriggerSetting &lhs, const AdaptiveTriggerSetting &rhs) 397 | { 398 | return lhs.mode == rhs.mode && 399 | lhs.start == rhs.start && 400 | lhs.end == rhs.end && 401 | lhs.force == rhs.force && 402 | lhs.frequency == rhs.frequency && 403 | lhs.forceExtra == rhs.forceExtra && 404 | lhs.frequencyExtra == rhs.frequencyExtra; 405 | } -------------------------------------------------------------------------------- /JoyShockMapper/src/quatMaths.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | #include 3 | 4 | struct Quat 5 | { 6 | float w; 7 | float x; 8 | float y; 9 | float z; 10 | 11 | Quat() 12 | { 13 | w = 1.0f; 14 | x = 0.0f; 15 | y = 0.0f; 16 | z = 0.0f; 17 | } 18 | 19 | Quat(float inW, float inX, float inY, float inZ) 20 | { 21 | w = inW; 22 | x = inX; 23 | y = inY; 24 | z = inZ; 25 | } 26 | 27 | static Quat AngleAxis(float inAngle, float inX, float inY, float inZ) 28 | { 29 | Quat result = Quat(cosf(inAngle * 0.5f), inX, inY, inZ); 30 | result.Normalize(); 31 | return result; 32 | } 33 | 34 | void Set(float inW, float inX, float inY, float inZ) 35 | { 36 | w = inW; 37 | x = inX; 38 | y = inY; 39 | z = inZ; 40 | } 41 | 42 | Quat& operator*=(const Quat& rhs) 43 | { 44 | Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, 45 | w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, 46 | w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, 47 | w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); 48 | return *this; 49 | } 50 | 51 | friend Quat operator*(Quat lhs, const Quat& rhs) 52 | { 53 | lhs *= rhs; 54 | return lhs; 55 | } 56 | 57 | void Normalize() 58 | { 59 | //printf("Normalizing: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); 60 | const float length = sqrtf(x * x + y * y + z * z); 61 | float targetLength = 1.0f - w * w; 62 | if (targetLength <= 0.0f || length <= 0.0f) 63 | { 64 | Set(1.0f, 0.0f, 0.0f, 0.0f); 65 | return; 66 | } 67 | targetLength = sqrtf(targetLength); 68 | const float fixFactor = targetLength / length; 69 | 70 | x *= fixFactor; 71 | y *= fixFactor; 72 | z *= fixFactor; 73 | 74 | //printf("Normalized: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); 75 | return; 76 | } 77 | 78 | Quat Normalized() const 79 | { 80 | Quat result = *this; 81 | result.Normalize(); 82 | return result; 83 | } 84 | 85 | void Invert() 86 | { 87 | x = -x; 88 | y = -y; 89 | z = -z; 90 | return; 91 | } 92 | 93 | Quat Inverse() const 94 | { 95 | Quat result = *this; 96 | result.Invert(); 97 | return result; 98 | } 99 | }; 100 | 101 | struct Vec 102 | { 103 | float x; 104 | float y; 105 | float z; 106 | 107 | Vec() 108 | { 109 | x = 0.0f; 110 | y = 0.0f; 111 | z = 0.0f; 112 | } 113 | 114 | Vec(float inX, float inY, float inZ) 115 | { 116 | x = inX; 117 | y = inY; 118 | z = inZ; 119 | } 120 | 121 | void Set(float inX, float inY, float inZ) 122 | { 123 | x = inX; 124 | y = inY; 125 | z = inZ; 126 | } 127 | 128 | float Length() const 129 | { 130 | return sqrtf(x * x + y * y + z * z); 131 | } 132 | 133 | void Normalize() 134 | { 135 | const float length = Length(); 136 | if (length == 0.0) 137 | { 138 | return; 139 | } 140 | const float fixFactor = 1.0f / length; 141 | 142 | x *= fixFactor; 143 | y *= fixFactor; 144 | z *= fixFactor; 145 | return; 146 | } 147 | 148 | Vec Normalized() const 149 | { 150 | Vec result = *this; 151 | result.Normalize(); 152 | return result; 153 | } 154 | 155 | Vec& operator+=(const Vec& rhs) 156 | { 157 | Set(x + rhs.x, y + rhs.y, z + rhs.z); 158 | return *this; 159 | } 160 | 161 | friend Vec operator+(Vec lhs, const Vec& rhs) 162 | { 163 | lhs += rhs; 164 | return lhs; 165 | } 166 | 167 | Vec& operator-=(const Vec& rhs) 168 | { 169 | Set(x - rhs.x, y - rhs.y, z - rhs.z); 170 | return *this; 171 | } 172 | 173 | friend Vec operator-(Vec lhs, const Vec& rhs) 174 | { 175 | lhs -= rhs; 176 | return lhs; 177 | } 178 | 179 | Vec& operator*=(const float rhs) 180 | { 181 | Set(x * rhs, y * rhs, z * rhs); 182 | return *this; 183 | } 184 | 185 | friend Vec operator*(Vec lhs, const float rhs) 186 | { 187 | lhs *= rhs; 188 | return lhs; 189 | } 190 | 191 | Vec& operator/=(const float rhs) 192 | { 193 | Set(x / rhs, y / rhs, z / rhs); 194 | return *this; 195 | } 196 | 197 | friend Vec operator/(Vec lhs, const float rhs) 198 | { 199 | lhs /= rhs; 200 | return lhs; 201 | } 202 | 203 | Vec& operator*=(const Quat& rhs) 204 | { 205 | Quat temp = rhs * Quat(0.0f, x, y, z) * rhs.Inverse(); 206 | Set(temp.x, temp.y, temp.z); 207 | return *this; 208 | } 209 | 210 | friend Vec operator*(Vec lhs, const Quat& rhs) 211 | { 212 | lhs *= rhs; 213 | return lhs; 214 | } 215 | 216 | Vec operator-() const 217 | { 218 | Vec result = Vec(-x, -y, -z); 219 | return result; 220 | } 221 | 222 | float Dot(const Vec& other) const 223 | { 224 | return x * other.x + y * other.y + z * other.z; 225 | } 226 | 227 | Vec Cross(const Vec& other) const 228 | { 229 | return Vec(y * other.z - z * other.y, 230 | z * other.x - x * other.z, 231 | x * other.y - y * other.x); 232 | } 233 | }; -------------------------------------------------------------------------------- /JoyShockMapper/src/win32/HidGuardianWhitelister.cpp: -------------------------------------------------------------------------------- 1 | // Source: https://stackoverflow.com/questions/1011339/how-do-you-make-a-http-request-with-c 2 | #include "Whitelister.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #pragma comment(lib, "ws2_32.lib") 10 | 11 | constexpr uint16_t HID_GUARDIAN_PORT = 26762; 12 | 13 | // Keep windows types outside of h file 14 | static SOCKET connectToServer(const string& szServerName, WORD portNum) 15 | { 16 | struct hostent* hp; 17 | unsigned int addr; 18 | struct sockaddr_in server; 19 | SOCKET conn; 20 | 21 | conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 22 | if (conn == INVALID_SOCKET) 23 | return NULL; 24 | 25 | if (inet_addr(szServerName.c_str()) == INADDR_NONE) 26 | { 27 | hp = gethostbyname(szServerName.c_str()); 28 | } 29 | else 30 | { 31 | addr = inet_addr(szServerName.c_str()); 32 | hp = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET); 33 | } 34 | 35 | if (hp == NULL) 36 | { 37 | closesocket(conn); 38 | return NULL; 39 | } 40 | 41 | server.sin_addr.s_addr = *((unsigned long*)hp->h_addr); 42 | server.sin_family = AF_INET; 43 | server.sin_port = htons(portNum); 44 | if (connect(conn, (struct sockaddr*)&server, sizeof(server))) 45 | { 46 | closesocket(conn); 47 | return NULL; 48 | } 49 | return conn; 50 | } 51 | 52 | class HidGuardianWhitelister : public Whitelister 53 | { 54 | public: 55 | HidGuardianWhitelister(bool add = false) 56 | : Whitelister(add) 57 | { 58 | if (add) 59 | { 60 | Add(); 61 | } 62 | } 63 | 64 | ~HidGuardianWhitelister() 65 | { 66 | Remove(); 67 | } 68 | 69 | bool IsAvailable() override 70 | { 71 | // Source: https://stackoverflow.com/questions/7808085/how-to-get-the-status-of-a-service-programmatically-running-stopped 72 | SC_HANDLE theService, scm; 73 | SERVICE_STATUS_PROCESS ssStatus; 74 | DWORD dwBytesNeeded; 75 | 76 | scm = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE); 77 | if (!scm) 78 | { 79 | return 0; 80 | } 81 | 82 | theService = OpenService(scm, L"HidCerberus.Srv", SERVICE_QUERY_STATUS); 83 | if (!theService) 84 | { 85 | CloseServiceHandle(scm); 86 | return 0; 87 | } 88 | 89 | auto result = QueryServiceStatusEx(theService, SC_STATUS_PROCESS_INFO, 90 | reinterpret_cast(&ssStatus), sizeof(SERVICE_STATUS_PROCESS), 91 | &dwBytesNeeded); 92 | 93 | CloseServiceHandle(theService); 94 | CloseServiceHandle(scm); 95 | 96 | return result == TRUE && ssStatus.dwCurrentState == SERVICE_RUNNING; 97 | } 98 | 99 | bool ShowConsole() override 100 | { 101 | std::cout << "Your PID is " << GetCurrentProcessId() << endl 102 | << "Open HIDCerberus at the following adress in your browser:" << endl 103 | << "http://localhost:26762/" << endl; 104 | return true; 105 | // SECURE CODING! https://www.oreilly.com/library/view/secure-programming-cookbook/0596003943/ch01s08.html 106 | //STARTUPINFOA startupInfo; 107 | //PROCESS_INFORMATION procInfo; 108 | //memset(&startupInfo, 0, sizeof(STARTUPINFOA)); 109 | //memset(&procInfo, 0, sizeof(PROCESS_INFORMATION)); 110 | //auto pid = GetCurrentProcessId(); 111 | //auto success = CreateProcessA(NULL, R"(cmd /C "start http://localhost:26762/")", NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startupInfo, &procInfo); 112 | //if (success == TRUE) 113 | //{ 114 | // CloseHandle(procInfo.hProcess); 115 | // CloseHandle(procInfo.hThread); 116 | // return true; 117 | //} 118 | //auto err = GetLastError(); 119 | //return false; 120 | } 121 | 122 | virtual bool Add(string* optErrMsg = nullptr) override 123 | { 124 | if (!IsAvailable()) 125 | { 126 | if (optErrMsg) 127 | { 128 | *optErrMsg = "No Whitelister Application is available. JoyShockMapper ecommends the latest HidHide from Nefarius\n" \ 129 | "https://github.com/ViGEm/HidHide/releases/latest"; 130 | } 131 | } 132 | else if (!_whitelisted) 133 | { 134 | UINT64 pid = GetCurrentProcessId(); 135 | stringstream ss; 136 | ss << R"(http://localhost/api/v1/hidguardian/whitelist/add/)" << pid; 137 | auto result = SendToHIDGuardian(ss.str()); 138 | 139 | if (result.compare(R"(["OK"])") == 0) 140 | { 141 | _whitelisted = true; 142 | return true; 143 | } 144 | 145 | if (optErrMsg) 146 | *optErrMsg = result; 147 | } 148 | else // Available and whitelisted 149 | { 150 | if (optErrMsg) 151 | { 152 | *optErrMsg = "JoyShockMapper is already whitelisted"; 153 | } 154 | } 155 | return false; 156 | } 157 | 158 | virtual bool Remove(string* optErrMsg = nullptr) override 159 | { 160 | if (!IsAvailable()) 161 | { 162 | if (optErrMsg) 163 | { 164 | *optErrMsg = "No whitelisting application is available. JoyShockMapper recommends the latest HidHide from Nefarius:\n" \ 165 | "https://github.com/ViGEm/HidHide/releases/latest"; 166 | } 167 | } 168 | else if (_whitelisted) 169 | { 170 | UINT64 pid = GetCurrentProcessId(); 171 | stringstream ss; 172 | ss << R"(http://localhost/api/v1/hidguardian/whitelist/remove/)" << pid; 173 | auto result = SendToHIDGuardian(ss.str()); 174 | if (result.compare(R"(["OK"])") == 0) 175 | { 176 | _whitelisted = false; 177 | return true; 178 | } 179 | 180 | if (optErrMsg) 181 | *optErrMsg = result; 182 | } 183 | else // Available and not whitelisted 184 | { 185 | if (optErrMsg) 186 | { 187 | *optErrMsg = "JoyShockMapper is not whitelisted"; 188 | } 189 | } 190 | return false; 191 | } 192 | 193 | private: 194 | string SendToHIDGuardian(string command); 195 | string readUrl2(string& szUrl, long& bytesReturnedOut, string* headerOut); 196 | void mParseUrl(string mUrl, string& serverName, string& filepath, string& filename); 197 | int getHeaderLength(char* content); 198 | }; 199 | 200 | string HidGuardianWhitelister::SendToHIDGuardian(string command) 201 | { 202 | long fileSize = -1; 203 | WSADATA wsaData; 204 | string memBuffer, headerBuffer; 205 | 206 | if (WSAStartup(0x101, &wsaData) == 0) 207 | { 208 | memBuffer = readUrl2(command, fileSize, &headerBuffer); 209 | 210 | WSACleanup(); 211 | } 212 | return memBuffer; 213 | } 214 | 215 | string HidGuardianWhitelister::readUrl2(string& szUrl, long& bytesReturnedOut, string* headerOut) 216 | { 217 | constexpr size_t bufSize = 512; 218 | string readBuffer(bufSize, '\0'); 219 | string sendBuffer(bufSize, '\0'); 220 | stringstream tmpBuffer; 221 | string result; 222 | SOCKET conn; 223 | string server, filepath, filename; 224 | long totalBytesRead, thisReadSize, headerLen; 225 | 226 | mParseUrl(szUrl, server, filepath, filename); 227 | 228 | ///////////// step 1, connect ////////////////////// 229 | conn = connectToServer(server, HID_GUARDIAN_PORT); 230 | 231 | ///////////// step 2, send GET request ///////////// 232 | tmpBuffer << "GET " << filepath << " HTTP/1.0" 233 | << "\r\n" 234 | << "Host: " << server << "\r\n\r\n"; 235 | sendBuffer = tmpBuffer.str(); 236 | send(conn, sendBuffer.c_str(), sendBuffer.length(), 0); 237 | 238 | ///////////// step 3 - get received bytes //////////////// 239 | // Receive until the peer closes the connection 240 | totalBytesRead = 0; 241 | do 242 | { 243 | thisReadSize = recv(conn, &readBuffer[0], readBuffer.size(), 0); 244 | 245 | if (thisReadSize > 0) 246 | { 247 | result.append(readBuffer); 248 | totalBytesRead += thisReadSize; 249 | } 250 | } while (thisReadSize > 0); 251 | 252 | headerLen = getHeaderLength(&result[0]); 253 | if (headerOut) 254 | { 255 | *headerOut = result.substr(0, headerLen); 256 | } 257 | result.erase(0, headerLen); 258 | result.resize(strlen(result.c_str())); 259 | 260 | bytesReturnedOut = totalBytesRead - headerLen; 261 | closesocket(conn); 262 | return result; 263 | } 264 | 265 | void HidGuardianWhitelister::mParseUrl(string url, string& serverName, string& filepath, string& filename) 266 | { 267 | string::size_type n; 268 | 269 | if (url.substr(0, 7) == "http://") 270 | url.erase(0, 7); 271 | 272 | if (url.substr(0, 8) == "https://") 273 | url.erase(0, 8); 274 | 275 | n = url.find('/'); 276 | 277 | if (n != string::npos) 278 | { 279 | serverName = url.substr(0, n); 280 | filepath = url.substr(n); 281 | n = filepath.rfind('/'); 282 | filename = filepath.substr(n + 1); 283 | } 284 | else 285 | { 286 | serverName = url; 287 | filepath = "/"; 288 | filename = ""; 289 | } 290 | } 291 | 292 | int HidGuardianWhitelister::getHeaderLength(char* content) 293 | { 294 | const char* srchStr1 = "\r\n\r\n", * srchStr2 = "\n\r\n\r"; 295 | char* findPos; 296 | int ofset = -1; 297 | 298 | findPos = strstr(content, srchStr1); 299 | if (findPos != NULL) 300 | { 301 | ofset = findPos - content; 302 | ofset += strlen(srchStr1); 303 | } 304 | 305 | else 306 | { 307 | findPos = strstr(content, srchStr2); 308 | if (findPos != NULL) 309 | { 310 | ofset = findPos - content; 311 | ofset += strlen(srchStr2); 312 | } 313 | } 314 | return ofset; 315 | } 316 | -------------------------------------------------------------------------------- /JoyShockMapper/src/win32/HidHideWhitelister.cpp: -------------------------------------------------------------------------------- 1 | #include "Whitelister.h" 2 | #include "HidHideApi.h" 3 | #include 4 | 5 | class HidHideWhitelister : public Whitelister 6 | { 7 | private: 8 | wstring _fullImageName; 9 | DWORD _len; 10 | public: 11 | HidHideWhitelister(bool add = false) 12 | : Whitelister(add) 13 | , _fullImageName() 14 | , _len( 256 ) 15 | { 16 | wstring module(_len, '\0'); 17 | if (!HidHide::Present() || QueryFullProcessImageName(GetCurrentProcess(), 0, &module[0], &_len) == FALSE) 18 | { 19 | _len = 0; 20 | } 21 | else 22 | { 23 | module.resize(_len); 24 | // Convert the file name into a full image name 25 | _fullImageName = HidHide::FileNameToFullImageName(module).c_str(); 26 | // Find entries 27 | auto whitelist{ HidHide::GetWhitelist() }; 28 | for (auto iter = whitelist.begin(); iter != whitelist.end(); ++iter) 29 | { 30 | if (_fullImageName == *iter) 31 | { 32 | _whitelisted = true; 33 | } 34 | } // next entry 35 | } 36 | } 37 | 38 | ~HidHideWhitelister() 39 | { 40 | //Remove(); 41 | } 42 | 43 | virtual bool ShowConsole() override 44 | { 45 | cout << "Search for \"HidHide Configuration Client\" in your windows search bar.\n"; 46 | return true; 47 | } 48 | 49 | virtual bool IsAvailable() override 50 | { 51 | return HidHide::Present() && _len > 0; 52 | } 53 | 54 | virtual bool Add(string* optErrMsg = nullptr) override 55 | { 56 | if (_len == 0) 57 | { 58 | if (optErrMsg) *optErrMsg = "HidHideWhitelister did not initialize properly"; 59 | return false; 60 | } 61 | //else if (!HidHide::GetActive()) 62 | //{ 63 | // if (optErrMsg) *optErrMsg = "HidHide is installed but not active."; 64 | // return false; 65 | //} 66 | 67 | auto whitelist = HidHide::GetWhitelist(); 68 | 69 | // Avoid duplicate entries 70 | for (auto entry : whitelist) 71 | { 72 | if (_fullImageName == entry) 73 | { 74 | if (optErrMsg) 75 | { 76 | *optErrMsg = "JoyShockMapper is already whitelisted"; 77 | } 78 | return false; 79 | } 80 | } 81 | 82 | // No duplicates so add it 83 | whitelist.push_back(_fullImageName); 84 | 85 | HidHide::SetWhitelist(whitelist); 86 | _whitelisted = true; 87 | return true; 88 | } 89 | 90 | virtual bool Remove(string* optErrMsg = nullptr) override 91 | { 92 | if (_len == 0) 93 | { 94 | if (optErrMsg) *optErrMsg = "HidHideWhitelister did not initialize properly"; 95 | return false; 96 | } 97 | //else if (!HidHide::GetActive()) 98 | //{ 99 | // if (optErrMsg) *optErrMsg = "HidHide is installed but not active."; 100 | // return false; 101 | //} 102 | 103 | auto whitelist = HidHide::GetWhitelist(); 104 | 105 | // Find entries 106 | for (auto iter = whitelist.begin() ; iter != whitelist.end() ; ++iter) 107 | { 108 | if (_fullImageName == *iter) 109 | { 110 | // No duplicates so add it 111 | iter = whitelist.erase(iter); 112 | 113 | HidHide::SetWhitelist(whitelist); 114 | _whitelisted = false; 115 | return true; 116 | } 117 | } 118 | 119 | if (optErrMsg) 120 | { 121 | *optErrMsg = "JoyShockMapper is not whitelisted"; 122 | } 123 | return false; 124 | } 125 | }; 126 | 127 | #include "../src/win32/HidGuardianWhitelister.cpp" 128 | 129 | Whitelister* Whitelister::getNew(bool add) 130 | { 131 | auto hidhide = new HidHideWhitelister(add); 132 | if (!hidhide->IsAvailable()) 133 | { 134 | delete hidhide; 135 | return new HidGuardianWhitelister(add); 136 | } 137 | return hidhide; 138 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright 2018-2021 Julian "Jibb" Smart; Copyright 2021- Nicolas Lessard. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | --- 12 | 13 | This software also uses the following libraries, which are also covered by the MIT license, with the same permissions and disclaimers: 14 | * Neargye's magic_enum (Magic Enum C++), Copyright (c) 2019 - 2020 Daniil Goncharov: https://github.com/Neargye/magic_enum 15 | * iPenguin's version_git: https://github.com/iPenguin/version_git 16 | * Nefarius's ViGEm Client: https://github.com/ViGEm/ViGEmClient 17 | * Electronicks' Pocket_FSM: https://github.com/Electronicks/pocket_fsm 18 | * Jibb's GamepadMotionHelpers: https://github.com/JibbSmart/GamepadMotionHelpers 19 | * Nielk1's TriggerEffectGenerator: https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db 20 | --- 21 | 22 | Some versions of this software link to (and include compiled binaries of) Jibb's JoyShockLibrary (Copyright 2018-2021, Julian Smart), which is also covered by the same MIT license, with the same permissions and disclaimers. JoyShockLibrary incorporates some work from the following projects and their copyright holders, all also covered by the same MIT license, with the same permissions and disclaimers: 23 | * mfosse's JoyCon-Driver (JoyCon driver), Copyright 2018 Matthew Fosse: https://github.com/mfosse/JoyCon-Driver 24 | * chrippa's ds4drv (DualShock 4 driver), Copyright 2013-2014 Christopher Rosell: https://github.com/chrippa/ds4drv 25 | * Ryochan7 and Jay2Kings' DS4Windows (DualShock 4 input mapper), Copyright 2019 Travis Nickles: https://github.com/Ryochan7/DS4Windows 26 | 27 | --- 28 | 29 | JoyShockLibrary also utilises signal11's HIDAPI, which has its own permissive license: https://github.com/signal11/hidapi 30 | 31 | HIDAPI - Multi-Platform library for 32 | communication with HID devices. 33 | 34 | Copyright 2009, Alan Ott, Signal 11 Software. 35 | All Rights Reserved. 36 | 37 | This software may be used by anyone for any reason so 38 | long as the copyright notice in the source files 39 | remains intact. 40 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake: -------------------------------------------------------------------------------- 1 | # - Returns a version string from Git 2 | # 3 | # These functions force a re-configure on each git commit so that you can 4 | # trust the values of the variables in your build system. 5 | # 6 | # get_git_head_revision( [ ...]) 7 | # 8 | # Returns the refspec and sha hash of the current head revision 9 | # 10 | # git_describe( [ ...]) 11 | # 12 | # Returns the results of git describe on the source tree, and adjusting 13 | # the output so that it tests false if an error occurs. 14 | # 15 | # git_get_exact_tag( [ ...]) 16 | # 17 | # Returns the results of git describe --exact-match on the source tree, 18 | # and adjusting the output so that it tests false if there was no exact 19 | # matching tag. 20 | # 21 | # Requires CMake 2.6 or newer (uses the 'function' command) 22 | # 23 | # Original Author: 24 | # 2009-2010 Ryan Pavlik 25 | # http://academic.cleardefinition.com 26 | # Iowa State University HCI Graduate Program/VRAC 27 | # 28 | # Copyright Iowa State University 2009-2010. 29 | # Distributed under the Boost Software License, Version 1.0. 30 | # (See accompanying file LICENSE_1_0.txt or copy at 31 | # http://www.boost.org/LICENSE_1_0.txt) 32 | 33 | if(__get_git_revision_description) 34 | return() 35 | endif() 36 | set(__get_git_revision_description YES) 37 | 38 | # We must run the following at "include" time, not at function call time, 39 | # to find the path to this module rather than the path to a calling list file 40 | get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) 41 | 42 | function(get_git_head_revision _refspecvar _hashvar) 43 | set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 44 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 45 | while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories 46 | set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") 47 | get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) 48 | if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) 49 | # We have reached the root directory, we are not in git 50 | set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 51 | set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 52 | return() 53 | endif() 54 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 55 | endwhile() 56 | # check if this is a submodule 57 | if(NOT IS_DIRECTORY ${GIT_DIR}) 58 | file(READ ${GIT_DIR} submodule) 59 | string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) 60 | get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) 61 | get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) 62 | endif() 63 | set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") 64 | if(NOT EXISTS "${GIT_DATA}") 65 | file(MAKE_DIRECTORY "${GIT_DATA}") 66 | endif() 67 | 68 | if(NOT EXISTS "${GIT_DIR}/HEAD") 69 | return() 70 | endif() 71 | set(HEAD_FILE "${GIT_DATA}/HEAD") 72 | configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) 73 | 74 | configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" 75 | "${GIT_DATA}/grabRef.cmake" 76 | @ONLY) 77 | include("${GIT_DATA}/grabRef.cmake") 78 | 79 | set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) 80 | set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) 81 | endfunction() 82 | 83 | function(git_describe _var) 84 | if(NOT GIT_FOUND) 85 | find_package(Git QUIET) 86 | endif() 87 | get_git_head_revision(refspec hash) 88 | if(NOT GIT_FOUND) 89 | set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) 90 | return() 91 | endif() 92 | if(NOT hash) 93 | set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) 94 | return() 95 | endif() 96 | 97 | # TODO sanitize 98 | #if((${ARGN}" MATCHES "&&") OR 99 | # (ARGN MATCHES "||") OR 100 | # (ARGN MATCHES "\\;")) 101 | # message("Please report the following error to the project!") 102 | # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") 103 | #endif() 104 | 105 | #message(STATUS "Arguments to execute_process: ${ARGN}") 106 | 107 | execute_process(COMMAND 108 | "${GIT_EXECUTABLE}" 109 | describe 110 | 111 | ${ARGN} 112 | WORKING_DIRECTORY 113 | "${CMAKE_CURRENT_SOURCE_DIR}" 114 | RESULT_VARIABLE 115 | res 116 | OUTPUT_VARIABLE 117 | out 118 | ERROR_QUIET 119 | OUTPUT_STRIP_TRAILING_WHITESPACE) 120 | if(NOT res EQUAL 0) 121 | set(out "${out}-${res}-NOTFOUND") 122 | endif() 123 | 124 | set(${_var} "${out}" PARENT_SCOPE) 125 | endfunction() 126 | 127 | function(git_get_exact_tag _var) 128 | git_describe(out --exact-match ${ARGN}) 129 | set(${_var} "${out}" PARENT_SCOPE) 130 | endfunction() -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Internal file for GetGitRevisionDescription.cmake 3 | # 4 | # Requires CMake 2.6 or newer (uses the 'function' command) 5 | # 6 | # Original Author: 7 | # 2009-2010 Ryan Pavlik 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright Iowa State University 2009-2010. 12 | # Distributed under the Boost Software License, Version 1.0. 13 | # (See accompanying file LICENSE_1_0.txt or copy at 14 | # http://www.boost.org/LICENSE_1_0.txt) 15 | 16 | set(HEAD_HASH) 17 | 18 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 19 | 20 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 21 | if(HEAD_CONTENTS MATCHES "ref") 22 | # named branch 23 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 24 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 25 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 26 | elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") 27 | configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 28 | set(HEAD_HASH "${HEAD_REF}") 29 | endif() 30 | else() 31 | # detached HEAD 32 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 33 | endif() 34 | 35 | if(NOT HEAD_HASH) 36 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 37 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 38 | endif() -------------------------------------------------------------------------------- /cmake/LinuxConfig.cmake: -------------------------------------------------------------------------------- 1 | if (UNIX AND NOT APPLE) 2 | set (LINUX ON) 3 | 4 | find_package (PkgConfig QUIET REQUIRED) 5 | 6 | pkg_search_module (Gtkmm REQUIRED IMPORTED_TARGET gtk+-3.0) 7 | pkg_search_module (appindicator REQUIRED IMPORTED_TARGET appindicator3-0.1) 8 | pkg_search_module (evdev REQUIRED IMPORTED_TARGET libevdev) 9 | 10 | add_library ( 11 | platform_dependencies INTERFACE 12 | ) 13 | 14 | target_link_libraries ( 15 | platform_dependencies INTERFACE 16 | PkgConfig::Gtkmm 17 | PkgConfig::appindicator 18 | PkgConfig::evdev 19 | pthread 20 | dl 21 | ) 22 | 23 | add_library (Platform::Dependencies ALIAS platform_dependencies) 24 | 25 | install ( 26 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/50-joyshockmapper.rules 27 | DESTINATION lib/udev/rules.d 28 | ) 29 | 30 | install ( 31 | FILES ${PROJECT_SOURCE_DIR}/JoyShockMapper/gyro_icon.png 32 | DESTINATION share/icons/hicolor/512x512/apps 33 | RENAME JoyShockMapper.png 34 | ) 35 | 36 | install ( 37 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/JoyShockMapper.desktop 38 | DESTINATION share/applications 39 | ) 40 | 41 | install ( 42 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status.svg 43 | DESTINATION share/icons/hicolor/16x16/status 44 | ) 45 | 46 | install ( 47 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status-dark.svg 48 | DESTINATION share/icons/hicolor/16x16/status 49 | ) 50 | 51 | install ( 52 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status.svg 53 | DESTINATION share/icons/hicolor/22x22/status 54 | ) 55 | 56 | install ( 57 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status-dark.svg 58 | DESTINATION share/icons/hicolor/22x22/status 59 | ) 60 | 61 | install ( 62 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status.svg 63 | DESTINATION share/icons/hicolor/24x24/status 64 | ) 65 | 66 | install ( 67 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status-dark.svg 68 | DESTINATION share/icons/hicolor/24x24/status 69 | ) 70 | 71 | install ( 72 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status.svg 73 | DESTINATION share/icons/hicolor/scalable/status 74 | ) 75 | 76 | install ( 77 | FILES ${PROJECT_SOURCE_DIR}/dist/linux/jsm-status-dark.svg 78 | DESTINATION share/icons/hicolor/scalable/status 79 | ) 80 | 81 | install ( 82 | DIRECTORY ${PROJECT_SOURCE_DIR}/dist/GyroConfigs 83 | DESTINATION ../etc/JoyShockMapper/ 84 | ) 85 | endif () 86 | -------------------------------------------------------------------------------- /cmake/WindowsConfig.cmake: -------------------------------------------------------------------------------- 1 | if (WIN32) 2 | set (WINDOWS ON) 3 | set_property (GLOBAL PROPERTY USE_FOLDERS ON) 4 | enable_language (RC) 5 | 6 | if (MSVC) 7 | # Statically link the runtime libraries 8 | set ( 9 | MSVC_COMPILE_FLAGS 10 | CMAKE_CXX_FLAGS 11 | CMAKE_CXX_FLAGS_DEBUG 12 | CMAKE_CXX_FLAGS_RELEASE 13 | CMAKE_C_FLAGS 14 | CMAKE_C_FLAGS_DEBUG 15 | CMAKE_C_FLAGS_RELEASE 16 | ) 17 | foreach (FLAG ${MSVC_COMPILE_FLAGS}) 18 | string(REPLACE "/MD" "/MT" ${FLAG} "${${FLAG}}") 19 | endforeach () 20 | endif () 21 | 22 | add_library ( 23 | platform_dependencies INTERFACE 24 | ) 25 | 26 | target_compile_definitions ( 27 | platform_dependencies INTERFACE 28 | -DUNICODE 29 | -D_UNICODE 30 | -DNOMINMAX 31 | -DWIN32_LEAN_AND_MEAN 32 | -D_CRT_SECURE_NO_WARNINGS 33 | ) 34 | 35 | target_link_libraries ( 36 | platform_dependencies INTERFACE 37 | wsock32 38 | wininet 39 | ws2_32 40 | comctl32 41 | ) 42 | 43 | add_library (Platform::Dependencies ALIAS platform_dependencies) 44 | 45 | set(CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}/${CONFIG}/install") 46 | 47 | if(SDL) 48 | set(PACKAGE_DIR "${PROJECT_NAME}_${CMAKE_GENERATOR_PLATFORM}") 49 | else() 50 | set(PACKAGE_DIR "${PROJECT_NAME}_${CMAKE_GENERATOR_PLATFORM}_legacy") 51 | endif() 52 | 53 | install ( 54 | DIRECTORY ${PROJECT_SOURCE_DIR}/dist/GyroConfigs 55 | DESTINATION ${PACKAGE_DIR} 56 | ) 57 | 58 | install ( 59 | DIRECTORY ${PROJECT_SOURCE_DIR}/dist/AutoLoad 60 | DESTINATION ${PACKAGE_DIR} 61 | ) 62 | 63 | install ( 64 | FILES ${PROJECT_SOURCE_DIR}/dist/OnReset.txt 65 | DESTINATION ${PACKAGE_DIR} 66 | ) 67 | 68 | install ( 69 | FILES ${PROJECT_SOURCE_DIR}/dist/OnStartup.txt 70 | DESTINATION ${PACKAGE_DIR} 71 | ) 72 | 73 | install ( 74 | FILES ${PROJECT_SOURCE_DIR}/CHANGELOG.md 75 | DESTINATION ${PACKAGE_DIR} 76 | ) 77 | 78 | install ( 79 | FILES ${PROJECT_SOURCE_DIR}/LICENSE.md 80 | DESTINATION ${PACKAGE_DIR} 81 | ) 82 | 83 | install ( 84 | FILES ${PROJECT_SOURCE_DIR}/README.md 85 | DESTINATION ${PACKAGE_DIR} 86 | ) 87 | 88 | install ( 89 | FILES ${PROJECT_SOURCE_DIR}/README_中文.md 90 | DESTINATION ${PACKAGE_DIR} 91 | ) 92 | endif () 93 | -------------------------------------------------------------------------------- /dist/AutoLoad/README.txt: -------------------------------------------------------------------------------- 1 | Config files in this directory will be loaded automatically by JoyShockMapper when 2 | the window in focus has a process name identical to the configuration file. 3 | 4 | For example: A file named chrome.txt in this folder will load when the window of chrome.exe gets in focus. 5 | 6 | Watch closely the blue logs in the JoyShockMapper console : it will tell you how to name the 7 | file for the windows that got in focus when no file is present to load. 8 | 9 | To disable the Autoload feature, enter AUTOLOAD=OFF 10 | -------------------------------------------------------------------------------- /dist/GyroConfigs/Desktop.txt: -------------------------------------------------------------------------------- 1 | # Windows interaction using gyro 2 | # Clear previous settings 3 | RESET_MAPPINGS 4 | 5 | # Calibration 6 | REAL_WORLD_CALIBRATION = 5.3333 7 | IN_GAME_SENS = 1 8 | COUNTER_OS_MOUSE_SPEED 9 | 10 | # DPAD is arrows 11 | LEFT = LEFT 12 | RIGHT = RIGHT 13 | UP = UP 14 | DOWN = DOWN 15 | 16 | # Mouse Buttons and wheel 17 | R = FMOUSE 18 | L = BMOUSE 19 | ZR = LMOUSE LMOUSE 20 | ZL = RMOUSE RMOUSE 21 | L3 = MMOUSE 22 | LEFT_STICK_MODE = SCROLL_WHEEL 23 | LLEFT = SCROLLUP 24 | LRIGHT = SCROLLDOWN 25 | SCROLL_SENS = 60 26 | 27 | # Button pad is common buttons 28 | S = ENTER 29 | W = SPACE 30 | N = BACKSPACE 31 | E = ESC GYRO_OFF 32 | 33 | + = LALT\ !TAB\ # Task view 34 | - = CONTROL\ LWINDOWS\ O\ # On Screen Keyboard 35 | HOME = LWINDOWS # Start Menu 36 | HOME,HOME = LWINDOWS\ D\ # Minimize All 37 | 38 | # Right stick drives the cursor 39 | GyroConfigs/_2Dmouse.txt -------------------------------------------------------------------------------- /dist/GyroConfigs/_2Dcalibrate.txt: -------------------------------------------------------------------------------- 1 | # Useful settings for calculating a real world calibration for 2D (mouse moves a cursor) games 2 | # (Any line that starts with # is ignored by JoyShockMapper) 3 | # While 2D games have no obvious use for flick stick, it's still a practical way to calibrate JoyShockMapper such that the same other settings can be used between games. 4 | # When calibrating 2D games, the goal is for one complete rotation of the flick stick to move horizontally from one side of the screen to the other. In games where this distance depends on the screen resolution, 1920x1080 is considered standard for consistency's sake. 5 | # First, reset to defaults so we don't have to set values we don't care about 6 | RESET_MAPPINGS 7 | # Use flick stick 8 | RIGHT_STICK_MODE = FLICK 9 | REAL_WORLD_CALIBRATION = 1 10 | COUNTER_OS_MOUSE_SPEED 11 | # Change this to whatever mouse sensitivity you have in the game you're playing 12 | IN_GAME_SENS = 1 -------------------------------------------------------------------------------- /dist/GyroConfigs/_2Dmouse.txt: -------------------------------------------------------------------------------- 1 | # Gyro mouse setup 2 | MIN_GYRO_SENS = 8 3 | MAX_GYRO_SENS = 16 4 | GYRO_CUTOFF_RECOVERY = 5 5 | MIN_GYRO_THRESHOLD = 5 6 | MAX_GYRO_THRESHOLD = 75 7 | GYRO_SMOOTH_THRESHOLD = 5 8 | 9 | # In case you want to use the right stick instead 10 | RIGHT_STICK_MODE = AIM 11 | STICK_SENS = 360 12 | STICK_POWER = 1 13 | STICK_ACCELERATION_RATE = 2 14 | STICK_ACCELERATION_CAP = 4 -------------------------------------------------------------------------------- /dist/GyroConfigs/_2Dtemplate.txt: -------------------------------------------------------------------------------- 1 | # Example configuration for a 2D game (mouse is an on-screen cursor) 2 | # (Any line that starts with # is ignored by JoyShockMapper) 3 | # First, reset to defaults so we don't have to set values we don't care about 4 | RESET_MAPPINGS 5 | 6 | # Calibrate. Good calibration means people can easily change sensitivities and expect them to behave the same between games. 7 | # For 2D mouse games like this, the convention is to make it that a GYRO_SENS of 1 would mean the controller takes a full revolution to move the mouse from one side of a 1920x1080 screen to the other. 8 | REAL_WORLD_CALIBRATION = 5.3333 9 | # Please set IN_GAME_SENS to your in game mouse speed setting whenever you change it in game. This and COUNTER_OS_MOUSE_SPEED should cancel out the effects of different players having different mouse settings. 10 | IN_GAME_SENS = 1 11 | # Remove COUNTER_OS_MOUSE_SPEED or add IGNORE_OS_MOUSE_SPEED for games that use raw input and aren't affected by Windows' settings 12 | COUNTER_OS_MOUSE_SPEED 13 | 14 | # Button mappings 15 | GYRO_OFF = E 16 | R = LMOUSE 17 | L = RMOUSE 18 | ZL = LSHIFT 19 | ZR = LCONTROL 20 | S = SPACE 21 | + = ESC 22 | - = TAB 23 | 24 | # Mouse 25 | GyroConfigs/_2Dmouse.txt -------------------------------------------------------------------------------- /dist/GyroConfigs/_3Dcalibrate.txt: -------------------------------------------------------------------------------- 1 | # Useful settings for calculating a real world calibration for 3D (mouse moves the camera) games 2 | # (Any line that starts with # is ignored by JoyShockMapper) 3 | # When calibrating 3D games, the goal is for one complete rotation of the flick stick to turn the character around 360 degrees, ending facing the same direction they started. 4 | # At these default settings, you'll likely have to rotate the stick a few times to complete that rotation. But this gives JoyShockMapper enough information after one or several in-game rotations to calculate an accurate REAL_WORLD_CALIBRATION value. 5 | # First, reset to defaults so we don't have to set values we don't care about 6 | RESET_MAPPINGS 7 | # Use flick stick 8 | RIGHT_STICK_MODE = FLICK 9 | REAL_WORLD_CALIBRATION = 5 10 | # Some games where the mouse moves the camera use raw mouse input to ignore Windows' mouse settings. COUNTER_OS_MOUSE_SPEED counteracts Windows' mouse settings for consistency between different computers with different settings, but if the game you're playing uses raw mouse input, COUNTER_OS_MOUSE_SPEED is causing problems, not fixing them. 11 | # So, for games with raw input, remove the following line, or just put a '#' at the beginning of the line so JoyShockMapper ignores it for now. 12 | COUNTER_OS_MOUSE_SPEED 13 | # Change this to whatever mouse sensitivity you have in the game you're playing 14 | IN_GAME_SENS = 1 -------------------------------------------------------------------------------- /dist/GyroConfigs/_3Dmouse.txt: -------------------------------------------------------------------------------- 1 | # Aim settings 2 | # Gyro sens of 1 means you'll turn the same amount in game as in the real world 3 | MIN_GYRO_SENS = 1 4 | MAX_GYRO_SENS = 2 5 | # Gyro thresholds are in degrees per second 6 | MIN_GYRO_THRESHOLD = 0 7 | MAX_GYRO_THRESHOLD = 75 8 | 9 | # Stick sensitivity is in degrees per second 10 | STICK_SENS = 360 11 | STICK_POWER = 4 12 | STICK_ACCELERATION_RATE = 1 13 | STICK_ACCELERATION_CAP = 2 14 | RIGHT_STICK_MODE = FLICK 15 | #RIGHT_STICK_MODE = AIM -------------------------------------------------------------------------------- /dist/GyroConfigs/_3Dtemplate.txt: -------------------------------------------------------------------------------- 1 | # Example configuration for a 3D game (mouse turns the camera) with flick stick 2 | # (Any line that starts with # is ignored by JoyShockMapper) 3 | # First, reset to defaults so we don't have to set values we don't care about 4 | RESET_MAPPINGS 5 | 6 | # Calibrate. Flick stick relies on good calibration; gyro and stick sens make more sense with it, too 7 | REAL_WORLD_CALIBRATION = 40.0 8 | IN_GAME_SENS = 1 9 | # Please set IN_GAME_SENS to your in game mouse speed setting whenever you change it :) 10 | # Most modern shooters use raw input so they're unaffected by Windows' mouse settings. But if this game doesn't, use the following line (delete the "#") to counter the effects of Windows' settings so the configuration will work the same on different computers 11 | #COUNTER_OS_MOUSE_SPEED 12 | 13 | # Button mappings 14 | LLEFT = A 15 | LRIGHT = D 16 | LUP = W 17 | LDOWN = S 18 | GYRO_OFF = ZR 19 | # You can have the GYRO_OFF or GYRO_ON button used by something else at the same time, but we don't in this example 20 | ZL = LSHIFT 21 | R = LMOUSE 22 | L = RMOUSE 23 | N = Q 24 | # Assigning two keys to one button like the following makes the first key get triggered by taps and the second by holds 25 | W = R E 26 | E = C 27 | S = SPACE 28 | L3 = LCONTROL 29 | R3 = V 30 | + = ESC 31 | - = TAB 32 | 33 | # Mouse 34 | GyroConfigs/_3Dmouse.txt -------------------------------------------------------------------------------- /dist/GyroConfigs/ds4.txt: -------------------------------------------------------------------------------- 1 | # This configuration file will map a virtual ds4 controller for each connected controller. 2 | 3 | VIRTUAL_CONTROLLER = DS4 4 | 5 | UP = PS_UP 6 | DOWN = PS_DOWN 7 | LEFT = PS_LEFT 8 | RIGHT = PS_RIGHT 9 | L = PS_L1 10 | R = PS_R1 11 | W = PS_SQUARE 12 | S = PS_CROSS 13 | N = PS_TRIANGLE 14 | E = PS_CIRCLE 15 | L3 = PS_L3 16 | R3 = PS_R3 17 | - = PS_SHARE 18 | + = PS_OPTIONS 19 | HOME = PS_HOME 20 | CAPTURE = PS_PAD_CLICK 21 | ZL_MODE = PS_L2 22 | ZR_MODE = PS_R2 23 | LEFT_STICK_MODE = LEFT_STICK 24 | RIGHT_STICK_MODE = RIGHT_STICK 25 | TOUCHPAD_MODE = PS_TOUCHPAD 26 | GYRO_OUTPUT = PS_MOTION 27 | -------------------------------------------------------------------------------- /dist/GyroConfigs/xbox.txt: -------------------------------------------------------------------------------- 1 | # This configuration file will map a virtual xbox controller for each connected controller. 2 | # There is a separate configuration file if you want to use joycons sideways as a xbox controller 3 | 4 | VIRTUAL_CONTROLLER = XBOX 5 | 6 | UP = X_UP 7 | DOWN = X_DOWN 8 | LEFT = X_LEFT 9 | RIGHT = X_RIGHT 10 | L = X_LB 11 | R = X_RB 12 | W = X_X 13 | S = X_A 14 | N = X_Y 15 | E = X_B 16 | L3 = X_LS 17 | R3 = X_RS 18 | - = X_BACK 19 | + = X_START 20 | HOME = X_GUIDE 21 | ZL_MODE = X_LT 22 | ZR_MODE = X_RT 23 | LEFT_STICK_MODE = LEFT_STICK 24 | RIGHT_STICK_MODE = RIGHT_STICK -------------------------------------------------------------------------------- /dist/GyroConfigs/xbox_joycons.txt: -------------------------------------------------------------------------------- 1 | # This configuration file sets up joycons held sideways as xbox controllers. 2 | # Sticks are left sticks, and the button diamond is mapped, bumpers are bumpers 3 | # and the back/start are mapped on the central buttons left and right respectively. 4 | # The gyro serves as a motion stick for the controller right stick and both bumpers 5 | # enable right stick click. There is no binding for triggers and the dpad. 6 | # Additionally, The motion neutral can be set with stick click + start 7 | # and gyro calibration with double clicking the larger control button 8 | 9 | RECONNECT_CONTROLLERS SPLIT # Each joycon is its own controller 10 | VIRTUAL_CONTROLLER = XBOX # Each joycon is an xbox controller 11 | 12 | # Common bindings 13 | CONTROLLER_ORIENTATION = JOYCON_SIDEWAYS 14 | JOYCON_MOTION_MASK = USE_BOTH 15 | MOTION_STICK_MODE = RIGHT_STICK 16 | 17 | # The left joycon 18 | LEFT_STICK_MODE = LEFT_STICK 19 | L3 = X_LS 20 | LEFT = X_A 21 | DOWN = X_B 22 | UP = X_X 23 | RIGHT = X_Y 24 | SL = X_LB 25 | SR = X_RB 26 | SL+SR = X_RS 27 | CAPTURE = X_START 28 | - = X_BACK 29 | L = NONE 30 | ZL = NONE 31 | ZL_MODE = NO_FULL 32 | L3,CAPTURE = "SET_MOTION_STICK_NEUTRAL" # occurs on all controllers!!! 33 | CAPTURE,CAPTURE = CALIBRATE CALIBRATE 34 | 35 | #The right joycon 36 | RIGHT_STICK_MODE = LEFT_STICK 37 | R3 = X_LS 38 | E = X_A 39 | N = X_B 40 | S = X_X 41 | W = X_Y 42 | # SL = X_LB 43 | # SR = X_RB 44 | # SL+SR = X_RS 45 | + = X_START 46 | HOME = X_BACK 47 | R = NONE 48 | ZR_MODE = NO_FULL 49 | ZR = NONE 50 | R3,+ = "SET_MOTION_STICK_NEUTRAL" # occurs on all controllers!!! 51 | HOME,HOME = CALIBRATE CALIBRATE -------------------------------------------------------------------------------- /dist/OnReset.txt: -------------------------------------------------------------------------------- 1 | # Always have a calibrate button. Toggle on tap or hold to calibrate 2 | # Each joycon should have a calibrate button. That's why there's two bindings here. 3 | HOME = CALIBRATE CALIBRATE 4 | CAPTURE = CALIBRATE CALIBRATE -------------------------------------------------------------------------------- /dist/OnStartup.txt: -------------------------------------------------------------------------------- 1 | # Edit this file to your prefered settings on startup 2 | 3 | #GyroConfigs/Desktop.txt # Load a configuration to navigate the OS 4 | 5 | # Uncomment the following three lines to calibrate all your devices at startup 6 | #RESTART_GYRO_CALIBRATION 7 | #SLEEP 2 # wait two seconds 8 | #FINISH_GYRO_CALIBRATION 9 | 10 | #AUTOLOAD = OFF # Uncomment to disable AUTOLOAD on startup 11 | 12 | #HIDE_MINIMIZED = ON # Uncomment to remove JSM from the taskbar when minimized. 13 | -------------------------------------------------------------------------------- /dist/linux/50-joyshockmapper.rules: -------------------------------------------------------------------------------- 1 | # Steam Controller udev write access 2 | KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", GROUP="input", OPTIONS+="static_node=uinput" 3 | 4 | # DualShock 4 over USB hidraw 5 | KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", GROUP="input", MODE="0660", TAG+="uaccess" 6 | 7 | # DualShock 4 wireless adapter over USB hidraw 8 | KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ba0", GROUP="input", MODE="0660", TAG+="uaccess" 9 | 10 | # DualShock 4 Slim over USB hidraw 11 | KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", GROUP="input", MODE="0660", TAG+="uaccess" 12 | 13 | # DualShock 4 over bluetooth hidraw 14 | KERNEL=="hidraw*", KERNELS=="*054C:05C4*", GROUP="input", MODE="0660", TAG+="uaccess" 15 | 16 | # DualShock 4 Slim over bluetooth hidraw 17 | KERNEL=="hidraw*", KERNELS=="*054C:09CC*", GROUP="input", MODE="0660", TAG+="uaccess" 18 | 19 | # Nintendo Switch Pro Controller over USB hidraw 20 | KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", GROUP="input", MODE="0660", TAG+="uaccess" 21 | 22 | # Nintendo Switch Pro Controller over bluetooth hidraw 23 | KERNEL=="hidraw*", KERNELS=="*057E:2009*", GROUP="input", MODE="0660", TAG+="uaccess" 24 | -------------------------------------------------------------------------------- /dist/linux/JoyShockMapper.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=JoyShockMapper 3 | Comment=Play PC games with Sony PlayStation DualShock 4, Nintendo Switch JoyCons or Nintendo Switch Pro Controller using gyroscopic controls 4 | Categories=Game;Utility; 5 | Exec=JoyShockMapper 6 | Icon=JoyShockMapper 7 | Terminal=true 8 | Type=Application 9 | Keywords=JoyShockMapper;Gyro;Gyroscopic; -------------------------------------------------------------------------------- /dist/linux/PKGBUILD.in: -------------------------------------------------------------------------------- 1 | pkgname='joyshockmapper' 2 | _pkgname='joyshockmapper' 3 | pkgver=@VERSION@ 4 | pkgrel=@RELEASE@ 5 | pkgdesc='Play PC games with Sony PlayStation DualShock 4, Nintendo Switch JoyCons or Nintendo Switch Pro Controller using gyroscopic controls' 6 | arch=('x86_64') 7 | url='http://gyrowiki.jibbsmart.com/' 8 | license=('MIT') 9 | makedepends=('cmake' 'clang') 10 | depends=('libevdev' 'hidapi' 'gtk3' 'libappindicator-gtk3') 11 | optdepends=('libx11: Autoload configuration based on focused window.') 12 | provides=("${_pkgname}") 13 | source=("@TARBALL@") 14 | sha256sums=('@SHA256@') 15 | 16 | build() { 17 | cd ${srcdir}/ 18 | mkdir build && cd build 19 | cmake .. \ 20 | -DCMAKE_CXX_COMPILER=clang++ \ 21 | -DCMAKE_BUILD_TYPE=Release \ 22 | -DCMAKE_INSTALL_PREFIX=/usr/ \ 23 | -DCPM_JoyShockLibrary_SOURCE=../../JoyShockLibrary 24 | make 25 | } 26 | 27 | package() { 28 | cd ${srcdir}/build 29 | DESTDIR=${pkgdir} make install 30 | rm ${pkgdir}/usr/lib/*.a 31 | } -------------------------------------------------------------------------------- /dist/linux/joyshockmapper.dsc.in: -------------------------------------------------------------------------------- 1 | DEBTRANSFORM-TAR: @TARBALL@ 2 | Format: 3.0 (native) 3 | Source: joyshockmapper 4 | Binary: joyshockmapper 5 | Architecture: any 6 | Version: @VERSION@-@RELEASE@ 7 | Maintainer: Romeo Calota 8 | Standards-Version: 0.0.1 9 | Build-Depends: debhelper, git, build-essential, make, cmake, clang, libhidapi-dev, libevdev-dev, libgtk-3-dev, libappindicator3-dev 10 | Checksums-Sha1: 11 | @SHA1@ @SIZE@ @TARBALL@ 12 | Checksums-Sha256: 13 | @SHA256@ @SIZE@ @TARBALL@ 14 | Files: 15 | @MD5@ @SIZE@ @TARBALL@ 16 | 17 | -------------------------------------------------------------------------------- /script/create_linux_obs_sources.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | printerror() { cat <<< "$@" 1>&2; } 4 | 5 | USAGE=$(printf "\nUsage:\n$0 -v|--version 1.6.0 -r|--release 2\n\n") 6 | ARGS=$(getopt -o :v:r: --long version:,release: -- "$@") 7 | 8 | if [ $? != 0 ] ; then 9 | printerror "$USAGE" 10 | exit 1 11 | fi 12 | 13 | eval set -- "$ARGS" 14 | while true ; do 15 | case $1 in 16 | -v|--version) 17 | VERSION=$2 ; shift 2 ;; 18 | -r|--release) 19 | RELEASE=$2 ; shift 2 ;; 20 | --) 21 | shift ; break ;; 22 | esac 23 | done 24 | 25 | if [ -z "$VERSION" ]; then 26 | printerror "-v|--version is a mandatory parameter" 27 | printerror "$USAGE" 28 | exit 1 29 | fi 30 | 31 | if [ -z "$RELEASE" ]; then 32 | printerror "-r|--release is a mandatory parameter" 33 | printerror "$USAGE" 34 | exit 1 35 | fi 36 | 37 | SOURCE="${BASH_SOURCE[0]}" 38 | while [ -h "$SOURCE" ]; do 39 | DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" 40 | SOURCE="$(readlink "$SOURCE")" 41 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" 42 | done 43 | DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" 44 | 45 | PROJECT_ROOT=$DIR/../ 46 | 47 | pushd &>/dev/null 48 | 49 | cd "$DIR"/../dist/linux 50 | 51 | rm -rf release 52 | 53 | mkdir release && cd release 54 | 55 | cp -f ../PKGBUILD.in PKGBUILD 56 | cp -f ../joyshockmapper.dsc.in joyshockmapper.dsc 57 | 58 | sed -i 's/@VERSION@/'$VERSION'/g' PKGBUILD 59 | sed -i 's/@VERSION@/'$VERSION'/g' joyshockmapper.dsc 60 | 61 | sed -i 's/@RELEASE@/'$RELEASE'/g' PKGBUILD 62 | sed -i 's/@RELEASE@/'$RELEASE'/g' joyshockmapper.dsc 63 | 64 | mkdir build && cd build 65 | cmake "$PROJECT_ROOT" 66 | 67 | cd .. 68 | 69 | mkdir src && cd src 70 | 71 | cp -arf "$PROJECT_ROOT"/cmake . 72 | mkdir -p dist/linux 73 | cp -arf "$PROJECT_ROOT"/dist/linux/*.rules ./dist/linux/ 74 | cp -arf "$PROJECT_ROOT"/dist/linux/*.desktop ./dist/linux/ 75 | cp -arf "$PROJECT_ROOT"/dist/linux/*.svg ./dist/linux/ 76 | cp -arf "$PROJECT_ROOT"/dist/GyroConfigs ./dist/ 77 | cp -arf "$PROJECT_ROOT"/JoyShockMapper . 78 | cp -arf "$PROJECT_ROOT"/CMakeLists.txt . 79 | cp -arf "$PROJECT_ROOT"/*.md . 80 | cp -arf ../build/_deps/joyshocklibrary-src JoyShockLibrary 81 | 82 | find . -type d -name ".git" -exec rm -rf {} \; 83 | 84 | TARBALL=joyshockmapper_$VERSION-$RELEASE.tar.gz 85 | 86 | tar zcfv $TARBALL * 87 | 88 | MD5=$(md5sum $TARBALL | cut -f1 -d' ') 89 | SHA1=$(sha1sum $TARBALL | cut -f1 -d' ') 90 | SHA256=$(sha256sum $TARBALL | cut -f1 -d' ') 91 | SIZE=$(du --bytes $TARBALL | cut -f1) 92 | 93 | mv joyshockmapper_$VERSION-$RELEASE.tar.gz ../ 94 | cd ../ 95 | 96 | rm -rf src 97 | rm -rf build 98 | 99 | sed -i 's/@MD5@/'$MD5'/g' PKGBUILD 100 | sed -i 's/@MD5@/'$MD5'/g' joyshockmapper.dsc 101 | 102 | sed -i 's/@SHA1@/'$SHA1'/g' PKGBUILD 103 | sed -i 's/@SHA1@/'$SHA1'/g' joyshockmapper.dsc 104 | 105 | sed -i 's/@SHA256@/'$SHA256'/g' PKGBUILD 106 | sed -i 's/@SHA256@/'$SHA256'/g' joyshockmapper.dsc 107 | 108 | sed -i 's/@TARBALL@/'$TARBALL'/g' PKGBUILD 109 | sed -i 's/@TARBALL@/'$TARBALL'/g' joyshockmapper.dsc 110 | 111 | sed -i 's/@SIZE@/'$SIZE'/g' PKGBUILD 112 | sed -i 's/@SIZE@/'$SIZE'/g' joyshockmapper.dsc 113 | 114 | popd &>/dev/null 115 | 116 | exit 0 117 | -------------------------------------------------------------------------------- /script/create_windows_release_archives.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM build both version 4 | cmake --build ../build-jsm-win64-jsl/ --config Release 5 | cmake --build ../build-jsm-win64-sdl/ --config Release 6 | 7 | REM install both versions 8 | cmake --install ../build-jsm-win64-jsl/ --config Release --prefix ../install 9 | cmake --install ../build-jsm-win64-sdl/ --config Release --prefix ../install 10 | 11 | REM Zip both versions 12 | Del /Q ..\JoyShockMapper_x64.zip ..\JoyShockMapper_x64_legacy.zip 13 | PowerShell -command "Compress-Archive -Path ../install/JoyShockMapper_SDL2_x64 -DestinationPath ../JoyShockMapper_x64.zip" 14 | PowerShell -command "Compress-Archive -Path ../install/JoyShockMapper_JSL_x64_legacy -DestinationPath ../JoyShockMapper_x64_legacy.zip" 15 | 16 | pause -------------------------------------------------------------------------------- /script/generate_win32_vs_solution_JSL.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd .. 4 | RMDIR /S /Q build-jsm-win32-jsl 5 | mkdir build-jsm-win32-jsl 6 | cmake . -B build-jsm-win32-jsl -A Win32 -DBUILD_SHARED_LIBS=1 -DSDL=0 7 | 8 | echo Open the generated Visual Studio solution located at: build-jsm-win32-jsl\JoyShockMapper.sln 9 | 10 | pause 11 | -------------------------------------------------------------------------------- /script/generate_win32_vs_solution_SDL.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd .. 4 | RMDIR /S /Q build-jsm-win32-sdl 5 | mkdir build-jsm-win32-sdl 6 | cmake . -B build-jsm-win32-sdl -A Win32 -DBUILD_SHARED_LIBS=1 -DSDL=1 7 | 8 | echo Open the generated Visual Studio solution located at: build-jsm-win32-sdl\JoyShockMapper.sln 9 | 10 | pause 11 | -------------------------------------------------------------------------------- /script/generate_win64_vs_solution_JSL.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd .. 4 | rmdir /S /Q build-jsm-win64-jsl 5 | mkdir build-jsm-win64-jsl 6 | cmake . -B build-jsm-win64-jsl -A x64 -DBUILD_SHARED_LIBS=1 -DSDL=0 7 | 8 | echo Open the generated Visual Studio solution located at: build-jsm-win64-jsl\JoyShockMapper.sln 9 | 10 | pause 11 | -------------------------------------------------------------------------------- /script/generate_win64_vs_solution_SDL.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cd .. 4 | rmdir /S /Q build-jsm-win64-sdl 5 | mkdir build-jsm-win64-sdl 6 | cmake . -A x64 -B build-jsm-win64-sdl -DBUILD_SHARED_LIBS=1 -DSDL=1 7 | 8 | echo Open the generated Visual Studio solution located at: build-jsm-win64-sdl\JoyShockMapper.sln 9 | 10 | pause 11 | --------------------------------------------------------------------------------