├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.yaml
└── workflows
│ └── build.yaml
├── .gitignore
├── .gitmodules
├── OpenVR2Key.sln
├── OpenVR2Key
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainController.cs
├── MainModel.cs
├── MainUtils.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── OpenVR2Key.csproj
├── Properties
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── actions.json
├── app.vrmanifest
├── bindings_knuckles.json
├── bindings_oculus_touch.json
├── bindings_vive_controller.json
├── bindings_vive_tracker_camera.json
├── bindings_vive_tracker_chest.json
├── bindings_vive_tracker_handed.json
├── bindings_vive_tracker_head.json
├── bindings_vive_tracker_left_elbow.json
├── bindings_vive_tracker_left_foot.json
├── bindings_vive_tracker_left_knee.json
├── bindings_vive_tracker_left_shoulder.json
├── bindings_vive_tracker_right_elbow.json
├── bindings_vive_tracker_right_foot.json
├── bindings_vive_tracker_right_knee.json
├── bindings_vive_tracker_right_shoulder.json
├── bindings_vive_tracker_waist.json
└── resources
│ ├── logo.ico
│ └── logo.png
└── README.md
/.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 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | description: Create a report to help resolve issues with the software
4 | body:
5 | - type: textarea
6 | attributes:
7 | label: Current behavior
8 | description: A clear and concise description of what the bug is.
9 | validations:
10 | required: true
11 | - type: textarea
12 | attributes:
13 | label: Steps to reproduce the behavior
14 | description: Steps to reproduce the behavior.
15 | placeholder: |
16 | 1. Run game '...'
17 | 2. Run software '....'
18 | 3. Set input to '....'
19 | 4. Set keys to '...'
20 | 5. See error
21 | validations:
22 | required: true
23 | - type: textarea
24 | attributes:
25 | label: Expected behavior
26 | description: A clear and concise description of what you expected to happen.
27 | validations:
28 | required: true
29 | - type: input
30 | id: os
31 | attributes:
32 | label: Operating system version
33 | description: "Which operating system version do you run?"
34 | placeholder: "Example: Windows 10 Pro"
35 | validations:
36 | required: true
37 | - type: input
38 | id: steamvr
39 | attributes:
40 | label: SteamVR version
41 | description: "Which version of SteamVR are you using?"
42 | placeholder: "Example: 1.26"
43 | validations:
44 | required: true
45 | - type: input
46 | id: app
47 | attributes:
48 | label: Application version
49 | description: "Which version of OpenVR2Key are you using?"
50 | placeholder: "Example: Windows 10 Pro"
51 | validations:
52 | required: true
53 | - type: input
54 | id: apploc
55 | attributes:
56 | label: Application location
57 | description: "Where on your disk are you running OpenVR2Key from?"
58 | placeholder: "Example: C:\\Temp\\OpenVR2Key\\"
59 | validations:
60 | required: true
61 | - type: input
62 | id: hmd
63 | attributes:
64 | label: VR HMD
65 | description: "What VR headset are you using?"
66 | placeholder: "Example: Valve Index"
67 | validations:
68 | required: true
69 | - type: input
70 | id: input
71 | attributes:
72 | label: VR hardware
73 | description: "What VR controllers are you using?"
74 | placeholder: "Example: Valve Index Controllers"
75 | validations:
76 | required: true
77 | - type: input
78 | id: software
79 | attributes:
80 | label: Software to control
81 | description: "What software are you trying to control with OpenVR2Key?"
82 | placeholder: "Example: Discord"
83 | validations:
84 | required: true
85 | - type: input
86 | id: game
87 | attributes:
88 | label: Game
89 | description: "What game are you running?"
90 | placeholder: "Example: Half-Life: Alyx"
91 | validations:
92 | required: false
93 | - type: textarea
94 | attributes:
95 | label: Additional information
96 | description: "Any additional information?"
97 | placeholder: "Tip: you can drag screenshots into this field to attach them."
98 | validations:
99 | required: false
100 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build & Release
2 |
3 | on:
4 | release:
5 | types: [ published ]
6 |
7 | jobs:
8 | build:
9 | name: Build
10 | runs-on: windows-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | with:
16 | submodules: 'recursive'
17 |
18 | - name: Setup dotnet
19 | uses: actions/setup-dotnet@v4
20 | with:
21 | dotnet-version: 8.x
22 | dotnet-quality: ga
23 |
24 | - name: Install OpenVR Dependencies
25 | shell: cmd
26 | run: |
27 | call cd EasyOpenVR
28 | call download_openvr_api_dependencies.cmd
29 |
30 | - name: Install dependencies
31 | run: dotnet restore
32 |
33 | - name: Build
34 | run: dotnet build OpenVR2Key/OpenVR2Key.csproj --configuration Release --runtime win-x64
35 |
36 | - name: Publish
37 | run: dotnet publish OpenVR2Key/OpenVR2Key.csproj --no-build --configuration Release -o release
38 |
39 | - name: Zip release build
40 | shell: bash
41 | run: |
42 | 7z a -tzip "OpenVR2Key_${{github.event.release.tag_name}}.zip" "./release/*"
43 |
44 | - name: Upload release artifact
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: release
48 | path: OpenVR2Key_${{github.event.release.tag_name}}.zip
49 |
50 | - name: Restore local tools
51 | run: dotnet tool restore
52 |
53 | release:
54 | name: Upload Release
55 | runs-on: ubuntu-latest
56 | needs: build
57 |
58 | steps:
59 | - name: Download artifact
60 | uses: actions/download-artifact@v4
61 | with:
62 | name: release
63 |
64 | - name: Display structure of downloaded files
65 | run: ls -R .
66 |
67 | - name: Upload file to release
68 | uses: softprops/action-gh-release@v2
69 | with:
70 | files: |
71 | **/*.zip
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "EasyOpenVR"]
2 | path = EasyOpenVR
3 | url = https://github.com/BOLL7708/EasyOpenVR.git
4 | [submodule "EasyFramework"]
5 | path = EasyFramework
6 | url = https://github.com/BOLL7708/EasyFramework.git
7 |
--------------------------------------------------------------------------------
/OpenVR2Key.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.329
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenVR2Key", "OpenVR2Key\OpenVR2Key.csproj", "{752648BE-C543-40AA-98B2-722C886F5944}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyOpenVR", "EasyOpenVR\EasyOpenVR.csproj", "{E8F2F033-F02F-43F0-BC76-39C8497D739F}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyFramework", "EasyFramework\EasyFramework.csproj", "{3D8BE5FB-558D-4481-8C79-78272F8B2BDC}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Release|Any CPU = Release|Any CPU
17 | Release|x64 = Release|x64
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {752648BE-C543-40AA-98B2-722C886F5944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {752648BE-C543-40AA-98B2-722C886F5944}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {752648BE-C543-40AA-98B2-722C886F5944}.Debug|x64.ActiveCfg = Debug|x64
23 | {752648BE-C543-40AA-98B2-722C886F5944}.Debug|x64.Build.0 = Debug|x64
24 | {752648BE-C543-40AA-98B2-722C886F5944}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {752648BE-C543-40AA-98B2-722C886F5944}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {752648BE-C543-40AA-98B2-722C886F5944}.Release|x64.ActiveCfg = Release|x64
27 | {752648BE-C543-40AA-98B2-722C886F5944}.Release|x64.Build.0 = Release|x64
28 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Debug|x64.ActiveCfg = Debug|Any CPU
31 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Debug|x64.Build.0 = Debug|Any CPU
32 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Release|x64.ActiveCfg = Release|Any CPU
35 | {E8F2F033-F02F-43F0-BC76-39C8497D739F}.Release|x64.Build.0 = Release|Any CPU
36 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Debug|x64.ActiveCfg = Debug|Any CPU
39 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Debug|x64.Build.0 = Debug|Any CPU
40 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Release|x64.ActiveCfg = Release|Any CPU
43 | {3D8BE5FB-558D-4481-8C79-78272F8B2BDC}.Release|x64.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {3207F212-5A47-435E-9726-9CCE4B4C6CEA}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/OpenVR2Key/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | False
15 |
16 |
17 | False
18 |
19 |
20 | False
21 |
22 |
23 | False
24 |
25 |
26 | True
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/OpenVR2Key/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/OpenVR2Key/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace OpenVR2Key
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/OpenVR2Key/MainController.cs:
--------------------------------------------------------------------------------
1 | using EasyOpenVR;
2 | using EasyFramework;
3 | using GregsStack.InputSimulatorStandard;
4 | using GregsStack.InputSimulatorStandard.Native;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Threading;
10 | using System.Windows;
11 | using System.Windows.Input;
12 | using EasyOpenVR.Utils;
13 | using Valve.VR;
14 |
15 | namespace OpenVR2Key
16 | {
17 | class MainController
18 | {
19 | private EasyOpenVRSingleton _ovr = EasyOpenVRSingleton.Instance;
20 | private InputSimulator _sim = new InputSimulator();
21 | private bool _shouldShutDown = false;
22 |
23 | // Active key registration
24 | private string _registeringKey = string.Empty;
25 | private object _registeringElement = null;
26 | private HashSet _keys = new HashSet();
27 | private HashSet _keysDown = new HashSet();
28 |
29 | // Actions
30 | public Action StatusUpdateAction { get; set; } = (status) => { Debug.WriteLine("No status action set."); };
31 | public Action AppUpdateAction { get; set; } = (appId) => { Debug.WriteLine("No appID action set."); };
32 | public Action KeyTextUpdateAction { get; set; } = (status, cancel) => { Debug.WriteLine("No key text action set."); };
33 | public Action, bool> ConfigRetrievedAction { get; set; } = (config, forceButtonOff) => { Debug.WriteLine("No config loaded."); };
34 | public Action KeyActivatedAction { get; set; } = (key, on) => { Debug.WriteLine("No key simulated action set."); };
35 | public Action DashboardVisibleAction { get; set; } = (visible) => { Debug.WriteLine("No dashboard visible action set."); };
36 |
37 | // Other
38 | private string _currentApplicationId = "";
39 | private ulong _inputSourceHandleLeft = 0, _inputSourceHandleRight = 0;
40 | private ulong[] _inputSourceHandles = new ulong[14];
41 | private ulong _notificationOverlayHandle = 0;
42 | private string[] _actionKeys = new string[0];
43 |
44 | public MainController()
45 | {
46 | }
47 |
48 | public void Init(string[] actionKeys)
49 | {
50 | _actionKeys = actionKeys;
51 |
52 | // Sets default values for status labels
53 | StatusUpdateAction.Invoke(false);
54 | AppUpdateAction.Invoke(MainModel.CONFIG_DEFAULT);
55 | KeyActivatedAction.Invoke(string.Empty, false);
56 |
57 | // Loads default config
58 | LoadConfig(true);
59 |
60 | // Start background thread
61 | var workerThread = new Thread(WorkerThread);
62 | workerThread.Start();
63 | }
64 | public void SetDebugLogAction(Action action)
65 | {
66 | _ovr.SetDebugLogAction(action);
67 | }
68 |
69 | #region bindings
70 | public bool ToggleRegisteringKey(string actionKey, object sender, out object activeElement)
71 | {
72 | var active = _registeringKey == string.Empty;
73 | if (active)
74 | {
75 | _registeringKey = actionKey;
76 | _registeringElement = sender;
77 | _keysDown.Clear();
78 | _keys.Clear();
79 | activeElement = sender;
80 | }
81 | else
82 | {
83 | activeElement = _registeringElement;
84 | MainModel.RegisterBinding(_registeringKey, _keys); // TODO: Should only save existing configs
85 | _registeringKey = string.Empty;
86 | _registeringElement = null;
87 | }
88 | return active;
89 | }
90 |
91 | private void StopRegisteringKeys()
92 | {
93 | UpdateCurrentObject(true);
94 | _keysDown.Clear();
95 | _keys.Clear();
96 | _registeringKey = string.Empty;
97 | _registeringElement = null;
98 | }
99 |
100 | // Add incoming keys to the current binding
101 | public bool OnKeyDown(Key key)
102 | {
103 | if (_registeringElement == null) return true;
104 | if (MainUtils.MatchVirtualKey(key) != null)
105 | {
106 | if (_keysDown.Count == 0) _keys.Clear();
107 | _keys.Add(key);
108 | _keysDown.Add(key);
109 | UpdateCurrentObject();
110 | return true;
111 | }
112 | else
113 | {
114 | return false;
115 | }
116 | }
117 | public void OnKeyUp(Key key)
118 | {
119 | if (_registeringElement == null) return;
120 | if (key == Key.RightAlt) _keysDown.Remove(Key.LeftCtrl); // Because AltGr records as RightAlt+LeftCtrl
121 | _keysDown.Remove(key);
122 | UpdateCurrentObject();
123 | }
124 |
125 | // Send text to UI to update label
126 | private void UpdateCurrentObject(bool cancel=false)
127 | {
128 | KeyTextUpdateAction.Invoke(GetKeysLabel(), cancel);
129 | }
130 |
131 | // Generate label text from keys
132 | public string GetKeysLabel(Key[] keys = null)
133 | {
134 | if (keys == null)
135 | {
136 | keys = new Key[_keys.Count];
137 | _keys.CopyTo(keys);
138 | }
139 | List result = new List();
140 | foreach (Key k in keys)
141 | {
142 | result.Add(k.ToString());
143 | }
144 | return string.Join(" + ", result.ToArray());
145 | }
146 |
147 | #endregion
148 |
149 | #region worker
150 | private void WorkerThread()
151 | {
152 | Thread.CurrentThread.IsBackground = true;
153 | bool initComplete = false;
154 | while (true)
155 | {
156 | Thread.Sleep(10);
157 | if (_ovr.IsInitialized())
158 | {
159 | if (!initComplete)
160 | {
161 | initComplete = true;
162 |
163 | _ovr.AddApplicationManifest("./app.vrmanifest", "boll7708.openvr2key", true);
164 | _ovr.LoadActionManifest("./actions.json");
165 | RegisterActions();
166 | UpdateAppId();
167 | StatusUpdateAction.Invoke(true);
168 | UpdateInputSourceHandles();
169 | _notificationOverlayHandle = _ovr.InitNotificationOverlay("OpenVR2Key");
170 |
171 | _ovr.RegisterEvent(EVREventType.VREvent_Quit, (data) =>
172 | {
173 | _shouldShutDown = true;
174 | });
175 | _ovr.RegisterEvent(EVREventType.VREvent_SceneApplicationChanged, (data) =>
176 | {
177 | UpdateAppId();
178 | });
179 | _ovr.RegisterEvents(new EVREventType[] {
180 | EVREventType.VREvent_TrackedDeviceActivated,
181 | EVREventType.VREvent_TrackedDeviceRoleChanged,
182 | EVREventType.VREvent_TrackedDeviceUpdated },
183 | (data) =>
184 | {
185 | UpdateInputSourceHandles();
186 | }
187 | );
188 | _ovr.RegisterEvent(EVREventType.VREvent_DashboardActivated, (data) =>
189 | {
190 | DashboardVisibleAction.Invoke(true);
191 | });
192 | _ovr.RegisterEvent(EVREventType.VREvent_DashboardDeactivated, (data) =>
193 | {
194 | DashboardVisibleAction.Invoke(false);
195 | });
196 | DashboardVisibleAction.Invoke(OpenVR.Overlay.IsDashboardVisible()); // To convey the initial state if the Dashboard is visible on launch of application.
197 | }
198 | else
199 | {
200 | _ovr.UpdateActionStates(_inputSourceHandles, 0);
201 |
202 | _ovr.UpdateEvents();
203 |
204 | if (_shouldShutDown)
205 | {
206 | _shouldShutDown = false;
207 | initComplete = false;
208 | _ovr.AcknowledgeShutdown();
209 | _ovr.Shutdown();
210 | StatusUpdateAction.Invoke(false);
211 | }
212 | }
213 | }
214 | else
215 | {
216 | _ovr.Init();
217 | Thread.Sleep(1000);
218 | }
219 | }
220 | }
221 |
222 | // Controller roles have updated, refresh controller handles
223 | private void UpdateInputSourceHandles()
224 | {
225 | _inputSourceHandleLeft = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.LeftHand);
226 | _inputSourceHandleRight = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.RightHand);
227 | ulong index = 0L;
228 | _inputSourceHandles[index++] = _inputSourceHandleLeft;
229 | _inputSourceHandles[index++] = _inputSourceHandleRight;
230 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.Head);
231 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.Chest);
232 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.LeftShoulder);
233 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.LeftElbow);
234 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.LeftKnee);
235 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.LeftFoot);
236 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.RightShoulder);
237 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.RightElbow);
238 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.RightKnee);
239 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.RightFoot);
240 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.Waist);
241 | _inputSourceHandles[index++] = _ovr.GetInputSourceHandle(EasyOpenVRSingleton.InputSource.Camera);
242 | }
243 |
244 | // New app is running, distribute new app ID
245 | private void UpdateAppId()
246 | {
247 | StopRegisteringKeys();
248 | _currentApplicationId = _ovr.GetRunningApplicationId();
249 | if (_currentApplicationId == string.Empty) _currentApplicationId = MainModel.CONFIG_DEFAULT;
250 | AppUpdateAction.Invoke(_currentApplicationId);
251 | LoadConfig();
252 | }
253 |
254 | // Load config, if it exists
255 | public void LoadConfig(bool forceDefault=false)
256 | {
257 | var configName = forceDefault ? MainModel.CONFIG_DEFAULT : _currentApplicationId;
258 | var config = MainModel.RetrieveConfig(configName);
259 | if (config != null) MainModel.SetConfigName(configName);
260 | Debug.WriteLine($"Config for {configName} found: {config != null}");
261 | ConfigRetrievedAction.Invoke(config, _currentApplicationId == MainModel.CONFIG_DEFAULT);
262 | }
263 |
264 | public bool AppIsRunning()
265 | {
266 | Debug.WriteLine($"Running app: {_currentApplicationId}");
267 | return _currentApplicationId != MainModel.CONFIG_DEFAULT;
268 | }
269 | #endregion
270 |
271 | #region actions
272 | public void OpenConfigFolder() // TODO: This refuses to open the right folder so the button is hidden.
273 | {
274 | var folderPath = MainModel.GetConfigFolderPath();
275 | if (Directory.Exists(folderPath))
276 | {
277 | ProcessStartInfo startInfo = new ProcessStartInfo
278 | {
279 | Arguments = folderPath,
280 | FileName = "explorer.exe"
281 | };
282 | Process.Start(startInfo);
283 | } else
284 | {
285 | MessageBox.Show("Folder does not exist yet as no config has been saved.");
286 | }
287 | }
288 | #endregion
289 |
290 | #region vr_input
291 |
292 | // Register all actions with the input system
293 | private void RegisterActions()
294 | {
295 | _ovr.RegisterActionSet("/actions/keys");
296 | foreach (var actionKey in _actionKeys)
297 | {
298 | var localActionKey = actionKey;
299 | _ovr.RegisterDigitalAction($"/actions/keys/in/Key{actionKey}", (data, inputAction) => { OnAction(localActionKey, data, inputAction.handle); }, actionKey.Contains("C"));
300 | }
301 | }
302 |
303 | // Action was triggered, handle it
304 | private void OnAction(string actionKey, InputDigitalActionData_t data, ulong inputSourceHandle)
305 | {
306 | KeyActivatedAction.Invoke(actionKey, data.bState);
307 | Debug.WriteLine($"{actionKey} : " + (data.bState ? "PRESSED" : "RELEASED"));
308 | if (MainModel.BindingExists(actionKey))
309 | {
310 | var binding = MainModel.GetBinding(actionKey);
311 | if (data.bState)
312 | {
313 | if (MainModel.LoadSetting(MainModel.Setting.Haptic))
314 | {
315 | if (inputSourceHandle == _inputSourceHandleLeft) _ovr.TriggerHapticPulseInController(ETrackedControllerRole.LeftHand);
316 | if (inputSourceHandle == _inputSourceHandleRight) _ovr.TriggerHapticPulseInController(ETrackedControllerRole.RightHand);
317 | }
318 | if (MainModel.LoadSetting(MainModel.Setting.Notification))
319 | {
320 | var notificationBitmap = BitmapUtils.NotificationBitmapFromBitmap(Properties.Resources.logo);
321 | _ovr.EnqueueNotification(_notificationOverlayHandle, $"{actionKey} simulated {GetKeysLabel(binding.Item1)}", notificationBitmap);
322 | }
323 | }
324 | SimulateKeyPress(data, binding);
325 | }
326 | }
327 | #endregion
328 |
329 | #region keyboard_out
330 |
331 | // Simulate a keyboard press
332 | private void SimulateKeyPress(InputDigitalActionData_t data, Tuple binding)
333 | {
334 | if (data.bState)
335 | {
336 | foreach (var vk in binding.Item2) _sim.Keyboard.KeyDown(vk);
337 | foreach (var vk in binding.Item3) _sim.Keyboard.KeyDown(vk);
338 | }
339 | else
340 | {
341 | foreach (var vk in binding.Item3) _sim.Keyboard.KeyUp(vk);
342 | foreach (var vk in binding.Item2) _sim.Keyboard.KeyUp(vk);
343 | }
344 | }
345 | #endregion
346 |
347 | public void LaunchBindings()
348 | {
349 | OpenVR.Input.OpenBindingUI("", 0, 0, true);
350 | }
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/OpenVR2Key/MainModel.cs:
--------------------------------------------------------------------------------
1 | using GregsStack.InputSimulatorStandard.Native;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text.RegularExpressions;
6 | using System.Windows.Input;
7 | using System.Text.Json;
8 |
9 | namespace OpenVR2Key
10 | {
11 | static class MainModel
12 | {
13 | #region bindings
14 | public static readonly string CONFIG_DEFAULT = "default";
15 | private static readonly object _bindingsLock = new object();
16 | private static Dictionary> _bindings = new Dictionary>();
17 |
18 | /**
19 | * Store key codes as virtual key codes.
20 | */
21 | static public void RegisterBinding(string actionKey, HashSet keys)
22 | {
23 | var keysArr = new Key[keys.Count];
24 | keys.CopyTo(keysArr);
25 | var binding = MainUtils.ConvertKeys(keysArr);
26 | lock (_bindingsLock)
27 | {
28 | _bindings[actionKey] = binding;
29 | var config = new Dictionary();
30 | foreach (var key in _bindings.Keys)
31 | {
32 | config.Add(key, _bindings[key].Item1);
33 | }
34 | StoreConfig(config);
35 | }
36 | }
37 | static private void RegisterBindings(Dictionary config)
38 | {
39 | var bindings = new Dictionary>();
40 | foreach (var key in config.Keys)
41 | {
42 | var keys = config[key];
43 | var binding = MainUtils.ConvertKeys(keys);
44 | bindings[key] = binding;
45 | }
46 | lock (_bindingsLock)
47 | {
48 | _bindings = bindings;
49 | }
50 | }
51 | static public bool BindingExists(string actionKey)
52 | {
53 | lock (_bindingsLock)
54 | {
55 | return _bindings.ContainsKey(actionKey);
56 | }
57 | }
58 | public static Tuple GetBinding(string actionkey)
59 | {
60 | lock (_bindingsLock)
61 | {
62 | return _bindings[actionkey];
63 | }
64 | }
65 | static public void ClearBindings()
66 | {
67 | lock (_bindingsLock)
68 | {
69 | _bindings.Clear();
70 | }
71 | StoreConfig();
72 | }
73 | static public void RemoveBinding(string actionKey)
74 | {
75 | lock (_bindingsLock)
76 | {
77 | _bindings.Remove(actionKey);
78 | var config = new Dictionary();
79 | foreach (var key in _bindings.Keys)
80 | {
81 | config.Add(key, _bindings[key].Item1);
82 | }
83 | StoreConfig(config);
84 | }
85 | }
86 | #endregion
87 |
88 | #region config
89 | static private string _configName = CONFIG_DEFAULT;
90 |
91 | static public void SetConfigName(string configName)
92 | {
93 | CleanConfigName(ref configName);
94 | _configName = configName;
95 | }
96 |
97 | static public bool IsDefaultConfig()
98 | {
99 | return _configName == CONFIG_DEFAULT;
100 | }
101 |
102 | static private void CleanConfigName(ref string configName)
103 | {
104 | Regex rgx = new Regex(@"[^a-zA-Z0-9\.]");
105 | var cleaned = rgx.Replace(configName, String.Empty).Trim(new char[] { '.' });
106 | configName = cleaned == String.Empty ? CONFIG_DEFAULT : cleaned;
107 | }
108 |
109 | static public string GetConfigFolderPath()
110 | {
111 | return $"{Directory.GetCurrentDirectory()}\\config\\";
112 | }
113 |
114 | static public void StoreConfig(Dictionary config = null, string configName = null)
115 | {
116 | if (config == null)
117 | {
118 | config = new Dictionary();
119 | lock (_bindingsLock)
120 | {
121 | foreach (var key in _bindings.Keys)
122 | {
123 | config.Add(key, _bindings[key].Item1);
124 | }
125 | }
126 | }
127 | if (configName == null) configName = _configName;
128 | var jsonString = JsonSerializer.Serialize(config);
129 | var configDir = GetConfigFolderPath();
130 | var configFilePath = $"{configDir}{configName}.json";
131 | if (!Directory.Exists(configDir)) Directory.CreateDirectory(configDir);
132 | File.WriteAllText(configFilePath, jsonString);
133 | }
134 |
135 | static public void DeleteConfig(string configName = null)
136 | {
137 | if (configName == null) configName = _configName;
138 | var configDir = GetConfigFolderPath();
139 | var configFilePath = $"{configDir}{configName}.json";
140 | if(File.Exists(configFilePath))
141 | {
142 | File.Delete(configFilePath);
143 | _configName = CONFIG_DEFAULT;
144 | }
145 | }
146 |
147 | static public Dictionary RetrieveConfig(string configName = null)
148 | {
149 | if (configName == null) configName = _configName;
150 | CleanConfigName(ref configName);
151 | var configDir = $"{Directory.GetCurrentDirectory()}\\config\\";
152 | var configFilePath = $"{configDir}{configName}.json";
153 | var jsonString = File.Exists(configFilePath) ? File.ReadAllText(configFilePath) : null;
154 | if (jsonString != null)
155 | {
156 | var config = JsonSerializer.Deserialize(jsonString, typeof(Dictionary)) as Dictionary;
157 | RegisterBindings(config);
158 | return config;
159 | }
160 | return null;
161 | }
162 | #endregion
163 |
164 | #region Settings
165 | public enum Setting
166 | {
167 | Minimize, Tray, Notification, Haptic, ExitWithSteam
168 | }
169 |
170 | private static readonly Properties.Settings p = Properties.Settings.Default;
171 |
172 | static public void UpdateSetting(Setting setting, bool value)
173 | {
174 | var propertyName = Enum.GetName(typeof(Setting), setting);
175 | p[propertyName] = value;
176 | p.Save();
177 | }
178 |
179 | static public bool LoadSetting(Setting setting)
180 | {
181 | var propertyName = Enum.GetName(typeof(Setting), setting);
182 | return (bool)p[propertyName];
183 | }
184 |
185 | static public string GetVersion()
186 | {
187 | return (string)Properties.Resources.Version;
188 | }
189 | #endregion
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/OpenVR2Key/MainUtils.cs:
--------------------------------------------------------------------------------
1 | using GregsStack.InputSimulatorStandard.Native;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Windows.Input;
6 |
7 | namespace OpenVR2Key
8 | {
9 | public static class MainUtils
10 | {
11 | private static Dictionary translationTableModifiers = new Dictionary() {
12 | { Key.LeftAlt, VirtualKeyCode.LMENU },
13 | { Key.RightAlt, VirtualKeyCode.RMENU },
14 | { Key.LeftCtrl, VirtualKeyCode.LCONTROL },
15 | { Key.RightCtrl, VirtualKeyCode.RCONTROL },
16 | { Key.LeftShift, VirtualKeyCode.LSHIFT },
17 | { Key.RightShift, VirtualKeyCode.RSHIFT },
18 | { Key.System, VirtualKeyCode.MENU }
19 | };
20 | private static Dictionary translationTableKeys = new Dictionary()
21 | {
22 | { Key.PageUp, VirtualKeyCode.PRIOR },
23 | { Key.PageDown, VirtualKeyCode.NEXT },
24 | { Key.MediaNextTrack, VirtualKeyCode.MEDIA_NEXT_TRACK },
25 | { Key.MediaPreviousTrack, VirtualKeyCode.MEDIA_PREV_TRACK },
26 | { Key.MediaPlayPause, VirtualKeyCode.MEDIA_PLAY_PAUSE },
27 | { Key.MediaStop, VirtualKeyCode.MEDIA_STOP },
28 | { Key.SelectMedia, VirtualKeyCode.LAUNCH_MEDIA_SELECT },
29 | { Key.VolumeMute, VirtualKeyCode.VOLUME_MUTE },
30 | { Key.VolumeUp, VirtualKeyCode.VOLUME_UP },
31 | { Key.VolumeDown, VirtualKeyCode.VOLUME_DOWN },
32 | { Key.OemClear, VirtualKeyCode.OEM_CLEAR},
33 | { Key.OemComma, VirtualKeyCode.OEM_COMMA },
34 | { Key.OemMinus, VirtualKeyCode.OEM_MINUS},
35 | { Key.OemPeriod, VirtualKeyCode.OEM_PERIOD},
36 | { Key.OemPlus, VirtualKeyCode.OEM_PLUS},
37 | /*
38 | * References for virtual key codes.
39 | * https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
40 | * https://sites.google.com/site/douglaslash/Home/programming/c-notes--snippets/c-keycodes
41 | * http://www.kbdedit.com/manual/low_level_vk_list.html
42 | */
43 | { Key.OemOpenBrackets, VirtualKeyCode.OEM_4},
44 | { Key.OemQuestion, VirtualKeyCode.OEM_2},
45 | { Key.OemQuotes, VirtualKeyCode.OEM_7},
46 | { Key.OemBackslash, VirtualKeyCode.OEM_102}
47 | };
48 |
49 | /**
50 | * Match a key code against a virtual key code, also check if modifier
51 | */
52 | public static Tuple MatchVirtualKey(Key key)
53 | {
54 | Debug.WriteLine(key.ToString());
55 | var keyStr = key.ToString().ToUpper();
56 |
57 | // Check for direct translation
58 | if (translationTableKeys.ContainsKey(key))
59 | {
60 | return new Tuple(translationTableKeys[key], false);
61 | }
62 |
63 | // Check for translation as modifier key
64 | if (translationTableModifiers.ContainsKey(key))
65 | {
66 | return new Tuple(translationTableModifiers[key], true);
67 | }
68 |
69 | // Character keys which come in as A-Z
70 | if (keyStr.Length == 1) keyStr = $"VK_{keyStr}";
71 |
72 | // Number keys which come in as D0-D9
73 | else if (keyStr.Length == 2 && keyStr[0] == 'D' && Char.IsDigit(keyStr[1]))
74 | {
75 | keyStr = $"VK_{keyStr[1]}";
76 | }
77 | // OEM Number keys (these are weird and some require direct mapping from translation dictionaries translationTables above)
78 | else if (keyStr.StartsWith("OEM") && (int.TryParse(keyStr.Substring(3), out _)))
79 | {
80 | keyStr = $"OEM_{keyStr.Substring(3)}";
81 | }
82 | var success = Enum.TryParse(keyStr, out VirtualKeyCode result);
83 | if (!success)
84 | {
85 | Debug.WriteLine("Key not found.");
86 | }
87 | return success ? new Tuple(result, false) : null;
88 | // If no key found, returns null
89 | }
90 |
91 | /**
92 | * Match incoming key codes to virtual ones, sort out modifiers
93 | */
94 | public static Tuple ConvertKeys(Key[] keys)
95 | {
96 | var vModifiers = new List();
97 | var vKeys = new List();
98 | for (var i = 0; i < keys.Length; i++)
99 | {
100 | var match = MatchVirtualKey(keys[i]);
101 | if (match != null)
102 | {
103 | if (match.Item2) vModifiers.Add(match.Item1);
104 | else vKeys.Add(match.Item1);
105 | }
106 | }
107 | return new Tuple(keys, vModifiers.ToArray(), vKeys.ToArray());
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/OpenVR2Key/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Read Help
22 |
23 |
24 | Report issue
25 |
26 |
27 | Join Discord
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/OpenVR2Key/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 | using System.Runtime.Versioning;
7 | using System.Threading;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using EasyFramework;
14 | using Brushes = System.Windows.Media.Brushes;
15 | using KeyEventArgs = System.Windows.Input.KeyEventArgs;
16 |
17 | namespace OpenVR2Key
18 | {
19 | [SupportedOSPlatform("windows7.0")]
20 | public partial class MainWindow : Window
21 | {
22 | private static Mutex _mutex = null;
23 | private readonly static string DEFAULT_KEY_LABEL = "Unbound: Click to bind keys to simulate";
24 | private MainController _controller;
25 | private List _items = new List();
26 | private object _activeElement;
27 | private string _currentlyRunningAppId = MainModel.CONFIG_DEFAULT;
28 | private HashSet _activeKeys = new HashSet();
29 | private bool _initDone = false;
30 | private bool _dashboardIsVisible = false;
31 | public MainWindow()
32 | {
33 | InitWindow();
34 | InitializeComponent();
35 | Title = Properties.Resources.AppName;
36 |
37 | // Prevent multiple instances
38 | WindowUtils.CheckIfAlreadyRunning(Properties.Resources.AppName);
39 |
40 | // Tray icon
41 | var icon = Properties.Resources.icon.Clone() as Icon;
42 | WindowUtils.CreateTrayIcon(this, icon, Properties.Resources.AppName, Properties.Resources.Version);
43 |
44 | _controller = new MainController
45 | {
46 | // Reports on the status of OpenVR
47 | StatusUpdateAction = (connected) =>
48 | {
49 | Debug.WriteLine($"Status Update Action: connected={connected}");
50 | var message = connected ? "Connected" : "Disconnected";
51 | var color = connected ? Brushes.OliveDrab : Brushes.Tomato;
52 | Dispatcher.Invoke(() =>
53 | {
54 | LabelOpenVr.Content = message;
55 | LabelOpenVr.Background = color;
56 | ButtonLaunchBindings.IsEnabled = connected;
57 | if (!connected && _initDone && MainModel.LoadSetting(MainModel.Setting.ExitWithSteam)) {
58 | WindowUtils.DestroyTrayIcon();
59 | Application.Current.Shutdown();
60 | }
61 | });
62 | },
63 |
64 | // Triggered when a new scene app is detected
65 | AppUpdateAction = (appId) =>
66 | {
67 | Debug.WriteLine($"App Update Action: appId={appId}");
68 | _currentlyRunningAppId = appId;
69 | var toolTip = "The detected Steam app ID for currently running title.";
70 | var color = Brushes.OliveDrab;
71 | if (appId == MainModel.CONFIG_DEFAULT)
72 | {
73 | color = Brushes.Gray;
74 | toolTip = "No application detected.";
75 | }
76 | var appIdFixed = appId.Replace("_", "__"); // Single underscores makes underlined chars
77 | Dispatcher.Invoke(() =>
78 | {
79 | Debug.WriteLine($"Setting AppID to: {appId}");
80 | LabelApplication.Content = appIdFixed;
81 | LabelApplication.Background = color;
82 | LabelApplication.ToolTip = toolTip;
83 | });
84 | },
85 |
86 | // We should update the text on the current binding we are recording
87 | KeyTextUpdateAction = (keyText, cancel) =>
88 | {
89 | Debug.WriteLine($"Key Text Update Action: keyText={keyText}");
90 | Dispatcher.Invoke(() =>
91 | {
92 | if (_activeElement != null)
93 | {
94 | (_activeElement as Label).Content = keyText;
95 | if (cancel) UpdateLabel(_activeElement as Label, false);
96 | }
97 | });
98 | },
99 |
100 | // We have loaded a config
101 | ConfigRetrievedAction = (config, forceButtonOff) =>
102 | {
103 | var loaded = config != null;
104 | if (loaded) Debug.WriteLine($"Config Retrieved Action: count()={config.Count}");
105 | Dispatcher.Invoke(() =>
106 | {
107 | if (loaded) InitList(config);
108 | UpdateConfigButton(loaded, forceButtonOff);
109 | });
110 | },
111 |
112 | KeyActivatedAction = (key, on) =>
113 | {
114 | Dispatcher.Invoke(() =>
115 | {
116 | if (!_dashboardIsVisible) {
117 | if (on) _activeKeys.Add(key);
118 | else _activeKeys.Remove(key);
119 | if (_activeKeys.Count > 0) LabelKeys.Content = string.Join(", ", _activeKeys);
120 | else LabelKeys.Content = "None";
121 | LabelKeys.ToolTip = "The currently active inputs being detected.";
122 | LabelKeys.Background = Brushes.Gray;
123 | }
124 | });
125 | },
126 |
127 | // Dashboard Visible
128 | DashboardVisibleAction = (visible) => {
129 | Dispatcher.Invoke(() =>
130 | {
131 | _dashboardIsVisible = visible;
132 | if (visible)
133 | {
134 | LabelKeys.Content = "Blocked";
135 | LabelKeys.ToolTip = "The SteamVR Dashboard is visible which will block controller input from reaching this application.";
136 | LabelKeys.Background = Brushes.Tomato;
137 | }
138 | else
139 | {
140 | LabelKeys.Content = "None";
141 | LabelKeys.ToolTip = "Listening for incoming inputs from active controllers.";
142 | LabelKeys.Background = Brushes.Gray;
143 | }
144 | });
145 | }
146 | };
147 |
148 | // Receives error messages from OpenVR
149 | _controller.SetDebugLogAction((message) =>
150 | {
151 | Dispatcher.Invoke(() =>
152 | {
153 | WriteToLog(message);
154 | });
155 | });
156 |
157 | // Init the things
158 | var actionKeys = InitList();
159 | _controller.Init(actionKeys);
160 | InitSettings();
161 |
162 | _initDone = true;
163 | }
164 |
165 | private void WriteToLog(String message)
166 | {
167 | var time = DateTime.Now.ToString("HH:mm:ss");
168 | var oldLog = TextBoxLog.Text;
169 | var lines = oldLog.Split('\n');
170 | Array.Resize(ref lines, 3);
171 | var newLog = string.Join("\n", lines);
172 | TextBoxLog.Text = $"{time}: {message}\n{newLog}";
173 | }
174 |
175 | private void InitWindow()
176 | {
177 | var shouldMinimize = MainModel.LoadSetting(MainModel.Setting.Minimize);
178 | var onlyInTray = MainModel.LoadSetting(MainModel.Setting.Tray);
179 | if(shouldMinimize) WindowUtils.Minimize(this, !onlyInTray);
180 | }
181 |
182 | #region bindings
183 |
184 | // Fill list with entries
185 | private string[] InitList(Dictionary config = null)
186 | {
187 | var actionKeys = new List();
188 | actionKeys.AddRange(GenerateActionKeyRange(16, 'L')); // Left
189 | actionKeys.AddRange(GenerateActionKeyRange(16, 'R')); // Right
190 | actionKeys.AddRange(GenerateActionKeyRange(16, 'C')); // Chord
191 | actionKeys.AddRange(GenerateActionKeyRange(16, 'T')); // Tracker
192 | string[] GenerateActionKeyRange(int count, char type)
193 | {
194 | var keys = new List();
195 | for (var i = 1; i <= count; i++) keys.Add($"{type}{i}");
196 | return keys.ToArray();
197 | }
198 |
199 | if (config == null) config = new Dictionary();
200 | _items.Clear();
201 | foreach (var actionKey in actionKeys)
202 | {
203 | var text = config.ContainsKey(actionKey) ? _controller.GetKeysLabel(config[actionKey]) : string.Empty;
204 | if (text == string.Empty) text = DEFAULT_KEY_LABEL;
205 | _items.Add(new BindingItem()
206 | {
207 | Key = actionKey,
208 | Label = $"Key {actionKey}",
209 | Text = text
210 | });
211 | }
212 | ItemsControlBindings.ItemsSource = null;
213 | ItemsControlBindings.ItemsSource = _items;
214 |
215 | return actionKeys.ToArray();
216 | }
217 |
218 | // Binding data class
219 | public class BindingItem
220 | {
221 | public string Key { get; set; }
222 | public string Label { get; set; }
223 | public string Text { get; set; }
224 | }
225 | #endregion
226 |
227 | #region events
228 | // All key down events in the app
229 | private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
230 | {
231 | e.Handled = true;
232 | var key = e.Key == Key.System ? e.SystemKey : e.Key;
233 | if (!_controller.OnKeyDown(key))
234 | {
235 | string message = $"Key not mapped: " + key.ToString();
236 | WriteToLog(message);
237 | }
238 |
239 | }
240 |
241 | // All key up events in the app
242 | private void Window_PreviewKeyUp(object sender, KeyEventArgs e)
243 | {
244 | e.Handled = true;
245 | var key = e.Key == Key.System ? e.SystemKey : e.Key;
246 | _controller.OnKeyUp(key);
247 | }
248 |
249 | protected override void OnClosing(CancelEventArgs e)
250 | {
251 | WindowUtils.DestroyTrayIcon();
252 | }
253 |
254 | private void Window_StateChanged(object sender, EventArgs e)
255 | {
256 | var onlyInTray = MainModel.LoadSetting(MainModel.Setting.Tray);
257 | WindowUtils.OnStateChange(this, !onlyInTray);
258 | }
259 |
260 | #endregion
261 |
262 | #region actions
263 | private void UpdateConfigButton(bool hasConfig, bool forceButtonOff=false)
264 | {
265 | Debug.WriteLine($"Update Config Button: {hasConfig}");
266 | if(!forceButtonOff && _controller.AppIsRunning())
267 | {
268 | ButtonAppBinding.Content = hasConfig ? "Remove app-specific config" : "Add app-specific config";
269 | ButtonAppBinding.IsEnabled = true;
270 | ButtonAppBinding.Tag = hasConfig;
271 | } else
272 | {
273 | ButtonAppBinding.Content = "No application running right now";
274 | ButtonAppBinding.IsEnabled = false;
275 | ButtonAppBinding.Tag = null;
276 | }
277 | }
278 |
279 | // Click to either create new config for current app or remove the existing config.
280 | private void Button_AppBinding_Click(object sender, RoutedEventArgs e)
281 | {
282 | var tag = (sender as Button).Tag;
283 | switch(tag)
284 | {
285 | case null:
286 | // This should never happen as the button cannot be pressed while disabled.
287 | break;
288 | case true:
289 | var result = MessageBox.Show(
290 | Application.Current.MainWindow,
291 | "Are you sure you want to delete this configuration?",
292 | "OpenVR2Key",
293 | MessageBoxButton.YesNo,
294 | MessageBoxImage.Warning
295 | );
296 | if (result == MessageBoxResult.Yes)
297 | {
298 | MainModel.ClearBindings();
299 | MainModel.DeleteConfig();
300 | _controller.LoadConfig(true); // Loads default
301 | UpdateConfigButton(false);
302 | }
303 | break;
304 | case false:
305 | MainModel.SetConfigName(_currentlyRunningAppId);
306 | MainModel.StoreConfig(new Dictionary());
307 | _controller.LoadConfig(); // Loads the empty new one
308 | UpdateConfigButton(true);
309 | break;
310 | }
311 | }
312 |
313 | // This should clear all bindings from the current config
314 | private void Button_ClearAll_Click(object sender, RoutedEventArgs e)
315 | {
316 | var result = MessageBox.Show(
317 | Application.Current.MainWindow,
318 | "Are you sure you want to clear all mappings in this configuration?",
319 | "OpenVR2Key",
320 | MessageBoxButton.YesNo,
321 | MessageBoxImage.Warning
322 | );
323 | if(result == MessageBoxResult.Yes)
324 | {
325 | MainModel.ClearBindings();
326 | InitList();
327 | }
328 | }
329 |
330 | private void Button_Folder_Click(object sender, RoutedEventArgs e)
331 | {
332 | _controller.OpenConfigFolder();
333 | }
334 |
335 | private void Button_LaunchBindings_Click(object sender, RoutedEventArgs e)
336 | {
337 | _controller.LaunchBindings();
338 | }
339 | #endregion
340 |
341 | #region bindings
342 | // Main action that is clicked from the list to start and end registration of keys
343 | private void Label_RecordSave_Click(object sender, MouseButtonEventArgs e)
344 | {
345 | var element = sender as Label;
346 | var dataItem = element.DataContext as BindingItem;
347 | var active = _controller.ToggleRegisteringKey(dataItem.Key, element, out object activeElement);
348 | UpdateLabel(activeElement as Label, active);
349 | if (active) _activeElement = activeElement;
350 | else _activeElement = null;
351 | }
352 |
353 | private void UpdateLabel(Label label, bool active)
354 | {
355 | {
356 | label.Foreground = active ? Brushes.DarkRed : Brushes.Black;
357 | label.BorderBrush = active ? Brushes.Tomato : Brushes.DarkGray;
358 | label.Background = active ? Brushes.LightPink : Brushes.LightGray;
359 | }
360 | }
361 |
362 | private void Label_HighlightOn(object sender, RoutedEventArgs e)
363 | {
364 | if (_activeElement != sender) (sender as Label).Background = Brushes.WhiteSmoke;
365 | }
366 |
367 | private void Label_HighlightOff(object sender, RoutedEventArgs e)
368 | {
369 | if (_activeElement != sender) (sender as Label).Background = Brushes.LightGray;
370 | }
371 |
372 | // Clear the current binding
373 | private void Button_ClearCancel_Click(object sender, RoutedEventArgs e)
374 | {
375 | var button = sender as Button;
376 | var dataItem = button.DataContext as BindingItem;
377 | MainModel.RemoveBinding(dataItem.Key);
378 | DockPanel sp = VisualTreeHelper.GetParent(button) as DockPanel;
379 | var element = sp.Children[2] as Label;
380 | element.Content = DEFAULT_KEY_LABEL;
381 | }
382 | #endregion
383 |
384 | #region settings
385 | // Load settings and apply them to the checkboxes
386 | private void InitSettings()
387 | {
388 | CheckBoxMinimize.IsChecked = MainModel.LoadSetting(MainModel.Setting.Minimize);
389 | CheckBoxTray.IsChecked = MainModel.LoadSetting(MainModel.Setting.Tray);
390 | CheckBoxExitWithSteamVr.IsChecked = MainModel.LoadSetting(MainModel.Setting.ExitWithSteam);
391 | CheckBoxDebugNotifications.IsChecked = MainModel.LoadSetting(MainModel.Setting.Notification);
392 | CheckBoxHapticFeedback.IsChecked = MainModel.LoadSetting(MainModel.Setting.Haptic);
393 | #if DEBUG
394 | LabelVersion.Content = $"{MainModel.GetVersion()}d";
395 | #else
396 | LabelVersion.Content = MainModel.GetVersion();
397 | #endif
398 | }
399 | private bool CheckboxValue(RoutedEventArgs e)
400 | {
401 | var name = e.RoutedEvent.Name;
402 | return name == "Checked";
403 | }
404 | private void CheckBox_Minimize_Checked(object sender, RoutedEventArgs e)
405 | {
406 | MainModel.UpdateSetting(MainModel.Setting.Minimize, CheckboxValue(e));
407 | }
408 |
409 | private void CheckBox_Tray_Checked(object sender, RoutedEventArgs e)
410 | {
411 | MainModel.UpdateSetting(MainModel.Setting.Tray, CheckboxValue(e));
412 | }
413 |
414 | private void CheckBox_DebugNotifications_Checked(object sender, RoutedEventArgs e)
415 | {
416 | MainModel.UpdateSetting(MainModel.Setting.Notification, CheckboxValue(e));
417 | }
418 |
419 | private void CheckBox_HapticFeedback_Checked(object sender, RoutedEventArgs e)
420 | {
421 | MainModel.UpdateSetting(MainModel.Setting.Haptic, CheckboxValue(e));
422 | }
423 |
424 | private void ClickedUrl(object sender, RoutedEventArgs e)
425 | {
426 | var link = (Hyperlink)sender;
427 | MiscUtils.OpenUrl(link.NavigateUri.ToString());
428 | }
429 | #endregion
430 |
431 | private void CheckBox_ExitWithSteamVR_Checked(object sender, RoutedEventArgs e)
432 | {
433 | MainModel.UpdateSetting(MainModel.Setting.ExitWithSteam, CheckboxValue(e));
434 | }
435 | }
436 | }
437 |
--------------------------------------------------------------------------------
/OpenVR2Key/OpenVR2Key.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | WinExe
5 | false
6 | true
7 | true
8 | true
9 | enable
10 | 12.0
11 | false
12 | en
13 | win-x64
14 | x64
15 |
16 |
17 | true
18 | true
19 | embedded
20 | false
21 | false
22 |
23 |
24 | resources\logo.ico
25 |
26 |
27 |
28 | Always
29 |
30 |
31 | Always
32 |
33 |
34 | Always
35 |
36 |
37 | Always
38 |
39 |
40 | Always
41 |
42 |
43 | Always
44 |
45 |
46 | Always
47 |
48 |
49 | Always
50 |
51 |
52 | Always
53 |
54 |
55 | Always
56 |
57 |
58 | Always
59 |
60 |
61 | Always
62 |
63 |
64 | Always
65 |
66 |
67 | Always
68 |
69 |
70 | Always
71 |
72 |
73 | Always
74 |
75 |
76 | Always
77 |
78 |
79 | Always
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/OpenVR2Key/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace OpenVR2Key.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenVR2Key.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to OpenVR2Key.
65 | ///
66 | public static string AppName {
67 | get {
68 | return ResourceManager.GetString("AppName", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
74 | ///
75 | public static System.Drawing.Icon icon {
76 | get {
77 | object obj = ResourceManager.GetObject("icon", resourceCulture);
78 | return ((System.Drawing.Icon)(obj));
79 | }
80 | }
81 |
82 | ///
83 | /// Looks up a localized resource of type System.Drawing.Bitmap.
84 | ///
85 | public static System.Drawing.Bitmap logo {
86 | get {
87 | object obj = ResourceManager.GetObject("logo", resourceCulture);
88 | return ((System.Drawing.Bitmap)(obj));
89 | }
90 | }
91 |
92 | ///
93 | /// Looks up a localized string similar to v0.61.
94 | ///
95 | public static string Version {
96 | get {
97 | return ResourceManager.GetString("Version", resourceCulture);
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/OpenVR2Key/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | OpenVR2Key
122 |
123 |
124 |
125 | ..\resources\logo.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
126 |
127 |
128 | ..\resources\logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
129 |
130 |
131 | v2.1.1
132 |
133 |
--------------------------------------------------------------------------------
/OpenVR2Key/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace OpenVR2Key.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.UserScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
29 | public bool Minimize {
30 | get {
31 | return ((bool)(this["Minimize"]));
32 | }
33 | set {
34 | this["Minimize"] = value;
35 | }
36 | }
37 |
38 | [global::System.Configuration.UserScopedSettingAttribute()]
39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
40 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
41 | public bool Tray {
42 | get {
43 | return ((bool)(this["Tray"]));
44 | }
45 | set {
46 | this["Tray"] = value;
47 | }
48 | }
49 |
50 | [global::System.Configuration.UserScopedSettingAttribute()]
51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
52 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
53 | public bool Notification {
54 | get {
55 | return ((bool)(this["Notification"]));
56 | }
57 | set {
58 | this["Notification"] = value;
59 | }
60 | }
61 |
62 | [global::System.Configuration.UserScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
65 | public bool Haptic {
66 | get {
67 | return ((bool)(this["Haptic"]));
68 | }
69 | set {
70 | this["Haptic"] = value;
71 | }
72 | }
73 |
74 | [global::System.Configuration.UserScopedSettingAttribute()]
75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
76 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
77 | public bool ExitWithSteam {
78 | get {
79 | return ((bool)(this["ExitWithSteam"]));
80 | }
81 | set {
82 | this["ExitWithSteam"] = value;
83 | }
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/OpenVR2Key/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | False
7 |
8 |
9 | False
10 |
11 |
12 | False
13 |
14 |
15 | False
16 |
17 |
18 | True
19 |
20 |
21 |
--------------------------------------------------------------------------------
/OpenVR2Key/actions.json:
--------------------------------------------------------------------------------
1 | {
2 | "default_bindings": [
3 | {
4 | "controller_type": "knuckles",
5 | "binding_url": "bindings_knuckles.json"
6 | },
7 | {
8 | "controller_type": "vive_controller",
9 | "binding_url": "bindings_vive_controller.json"
10 | },
11 | {
12 | "controller_type": "oculus_touch",
13 | "binding_url": "bindings_oculus_touch.json"
14 | },
15 | {
16 | "controller_type": "vive_tracker_head",
17 | "binding_url": "bindings_vive_tracker_head.json"
18 | },
19 | {
20 | "controller_type": "vive_tracker_left_shoulder",
21 | "binding_url": "bindings_vive_tracker_left_shoulder.json"
22 | },
23 | {
24 | "controller_type": "vive_tracker_right_shoulder",
25 | "binding_url": "bindings_vive_tracker_right_shoulder.json"
26 | },
27 | {
28 | "controller_type": "vive_tracker_chest",
29 | "binding_url": "bindings_vive_tracker_chest.json"
30 | },
31 | {
32 | "controller_type": "vive_tracker_handed",
33 | "binding_url": "bindings_vive_tracker_handed.json"
34 | },
35 | {
36 | "controller_type": "vive_tracker_left_elbow",
37 | "binding_url": "bindings_vive_tracker_left_elbow.json"
38 | },
39 | {
40 | "controller_type": "vive_tracker_right_elbow",
41 | "binding_url": "bindings_vive_tracker_right_elbow.json"
42 | },
43 | {
44 | "controller_type": "vive_tracker_waist",
45 | "binding_url": "bindings_vive_tracker_waist.json"
46 | },
47 | {
48 | "controller_type": "vive_tracker_left_knee",
49 | "binding_url": "bindings_vive_tracker_left_knee.json"
50 | },
51 | {
52 | "controller_type": "vive_tracker_right_knee",
53 | "binding_url": "bindings_vive_tracker_right_knee.json"
54 | },
55 | {
56 | "controller_type": "vive_tracker_left_foot",
57 | "binding_url": "bindings_vive_tracker_left_foot.json"
58 | },
59 | {
60 | "controller_type": "vive_tracker_right_foot",
61 | "binding_url": "bindings_vive_tracker_right_foot.json"
62 | },
63 | {
64 | "controller_type": "vive_tracker_camera",
65 | "binding_url": "bindings_vive_tracker_camera.json"
66 | }
67 | ],
68 | "actions": [
69 | {
70 | "name": "/actions/keys/in/KeyL1",
71 | "requirement": "optional",
72 | "type": "boolean"
73 | },
74 | {
75 | "name": "/actions/keys/in/KeyL2",
76 | "requirement": "optional",
77 | "type": "boolean"
78 | },
79 | {
80 | "name": "/actions/keys/in/KeyL3",
81 | "requirement": "optional",
82 | "type": "boolean"
83 | },
84 | {
85 | "name": "/actions/keys/in/KeyL4",
86 | "requirement": "optional",
87 | "type": "boolean"
88 | },
89 | {
90 | "name": "/actions/keys/in/KeyL5",
91 | "requirement": "optional",
92 | "type": "boolean"
93 | },
94 | {
95 | "name": "/actions/keys/in/KeyL6",
96 | "requirement": "optional",
97 | "type": "boolean"
98 | },
99 | {
100 | "name": "/actions/keys/in/KeyL7",
101 | "requirement": "optional",
102 | "type": "boolean"
103 | },
104 | {
105 | "name": "/actions/keys/in/KeyL8",
106 | "requirement": "optional",
107 | "type": "boolean"
108 | },
109 | {
110 | "name": "/actions/keys/in/KeyL9",
111 | "requirement": "optional",
112 | "type": "boolean"
113 | },
114 | {
115 | "name": "/actions/keys/in/KeyL10",
116 | "requirement": "optional",
117 | "type": "boolean"
118 | },
119 | {
120 | "name": "/actions/keys/in/KeyL11",
121 | "requirement": "optional",
122 | "type": "boolean"
123 | },
124 | {
125 | "name": "/actions/keys/in/KeyL12",
126 | "requirement": "optional",
127 | "type": "boolean"
128 | },
129 | {
130 | "name": "/actions/keys/in/KeyL13",
131 | "requirement": "optional",
132 | "type": "boolean"
133 | },
134 | {
135 | "name": "/actions/keys/in/KeyL14",
136 | "requirement": "optional",
137 | "type": "boolean"
138 | },
139 | {
140 | "name": "/actions/keys/in/KeyL15",
141 | "requirement": "optional",
142 | "type": "boolean"
143 | },
144 | {
145 | "name": "/actions/keys/in/KeyL16",
146 | "requirement": "optional",
147 | "type": "boolean"
148 | },
149 | {
150 | "name": "/actions/keys/in/KeyR1",
151 | "requirement": "optional",
152 | "type": "boolean"
153 | },
154 | {
155 | "name": "/actions/keys/in/KeyR2",
156 | "requirement": "optional",
157 | "type": "boolean"
158 | },
159 | {
160 | "name": "/actions/keys/in/KeyR3",
161 | "requirement": "optional",
162 | "type": "boolean"
163 | },
164 | {
165 | "name": "/actions/keys/in/KeyR4",
166 | "requirement": "optional",
167 | "type": "boolean"
168 | },
169 | {
170 | "name": "/actions/keys/in/KeyR5",
171 | "requirement": "optional",
172 | "type": "boolean"
173 | },
174 | {
175 | "name": "/actions/keys/in/KeyR6",
176 | "requirement": "optional",
177 | "type": "boolean"
178 | },
179 | {
180 | "name": "/actions/keys/in/KeyR7",
181 | "requirement": "optional",
182 | "type": "boolean"
183 | },
184 | {
185 | "name": "/actions/keys/in/KeyR8",
186 | "requirement": "optional",
187 | "type": "boolean"
188 | },
189 | {
190 | "name": "/actions/keys/in/KeyR9",
191 | "requirement": "optional",
192 | "type": "boolean"
193 | },
194 | {
195 | "name": "/actions/keys/in/KeyR10",
196 | "requirement": "optional",
197 | "type": "boolean"
198 | },
199 | {
200 | "name": "/actions/keys/in/KeyR11",
201 | "requirement": "optional",
202 | "type": "boolean"
203 | },
204 | {
205 | "name": "/actions/keys/in/KeyR12",
206 | "requirement": "optional",
207 | "type": "boolean"
208 | },
209 | {
210 | "name": "/actions/keys/in/KeyR13",
211 | "requirement": "optional",
212 | "type": "boolean"
213 | },
214 | {
215 | "name": "/actions/keys/in/KeyR14",
216 | "requirement": "optional",
217 | "type": "boolean"
218 | },
219 | {
220 | "name": "/actions/keys/in/KeyR15",
221 | "requirement": "optional",
222 | "type": "boolean"
223 | },
224 | {
225 | "name": "/actions/keys/in/KeyR16",
226 | "requirement": "optional",
227 | "type": "boolean"
228 | },
229 | {
230 | "name": "/actions/keys/in/KeyC1",
231 | "requirement": "optional",
232 | "type": "boolean"
233 | },
234 | {
235 | "name": "/actions/keys/in/KeyC2",
236 | "requirement": "optional",
237 | "type": "boolean"
238 | },
239 | {
240 | "name": "/actions/keys/in/KeyC3",
241 | "requirement": "optional",
242 | "type": "boolean"
243 | },
244 | {
245 | "name": "/actions/keys/in/KeyC4",
246 | "requirement": "optional",
247 | "type": "boolean"
248 | },
249 | {
250 | "name": "/actions/keys/in/KeyC5",
251 | "requirement": "optional",
252 | "type": "boolean"
253 | },
254 | {
255 | "name": "/actions/keys/in/KeyC6",
256 | "requirement": "optional",
257 | "type": "boolean"
258 | },
259 | {
260 | "name": "/actions/keys/in/KeyC7",
261 | "requirement": "optional",
262 | "type": "boolean"
263 | },
264 | {
265 | "name": "/actions/keys/in/KeyC8",
266 | "requirement": "optional",
267 | "type": "boolean"
268 | },
269 | {
270 | "name": "/actions/keys/in/KeyC9",
271 | "requirement": "optional",
272 | "type": "boolean"
273 | },
274 | {
275 | "name": "/actions/keys/in/KeyC10",
276 | "requirement": "optional",
277 | "type": "boolean"
278 | },
279 | {
280 | "name": "/actions/keys/in/KeyC11",
281 | "requirement": "optional",
282 | "type": "boolean"
283 | },
284 | {
285 | "name": "/actions/keys/in/KeyC12",
286 | "requirement": "optional",
287 | "type": "boolean"
288 | },
289 | {
290 | "name": "/actions/keys/in/KeyC13",
291 | "requirement": "optional",
292 | "type": "boolean"
293 | },
294 | {
295 | "name": "/actions/keys/in/KeyC14",
296 | "requirement": "optional",
297 | "type": "boolean"
298 | },
299 | {
300 | "name": "/actions/keys/in/KeyC15",
301 | "requirement": "optional",
302 | "type": "boolean"
303 | },
304 | {
305 | "name": "/actions/keys/in/KeyC16",
306 | "requirement": "optional",
307 | "type": "boolean"
308 | },
309 | {
310 | "name": "/actions/keys/in/KeyT1",
311 | "requirement": "optional",
312 | "type": "boolean"
313 | },
314 | {
315 | "name": "/actions/keys/in/KeyT2",
316 | "requirement": "optional",
317 | "type": "boolean"
318 | },
319 | {
320 | "name": "/actions/keys/in/KeyT3",
321 | "requirement": "optional",
322 | "type": "boolean"
323 | },
324 | {
325 | "name": "/actions/keys/in/KeyT4",
326 | "requirement": "optional",
327 | "type": "boolean"
328 | },
329 | {
330 | "name": "/actions/keys/in/KeyT5",
331 | "requirement": "optional",
332 | "type": "boolean"
333 | },
334 | {
335 | "name": "/actions/keys/in/KeyT6",
336 | "requirement": "optional",
337 | "type": "boolean"
338 | },
339 | {
340 | "name": "/actions/keys/in/KeyT7",
341 | "requirement": "optional",
342 | "type": "boolean"
343 | },
344 | {
345 | "name": "/actions/keys/in/KeyT8",
346 | "requirement": "optional",
347 | "type": "boolean"
348 | },
349 | {
350 | "name": "/actions/keys/in/KeyT9",
351 | "requirement": "optional",
352 | "type": "boolean"
353 | },
354 | {
355 | "name": "/actions/keys/in/KeyT10",
356 | "requirement": "optional",
357 | "type": "boolean"
358 | },
359 | {
360 | "name": "/actions/keys/in/KeyT11",
361 | "requirement": "optional",
362 | "type": "boolean"
363 | },
364 | {
365 | "name": "/actions/keys/in/KeyT12",
366 | "requirement": "optional",
367 | "type": "boolean"
368 | },
369 | {
370 | "name": "/actions/keys/in/KeyT13",
371 | "requirement": "optional",
372 | "type": "boolean"
373 | },
374 | {
375 | "name": "/actions/keys/in/KeyT14",
376 | "requirement": "optional",
377 | "type": "boolean"
378 | },
379 | {
380 | "name": "/actions/keys/in/KeyT15",
381 | "requirement": "optional",
382 | "type": "boolean"
383 | },
384 | {
385 | "name": "/actions/keys/in/KeyT16",
386 | "requirement": "optional",
387 | "type": "boolean"
388 | }
389 | ],
390 | "action_sets": [
391 | {
392 | "name": "/actions/keys",
393 | "usage": "leftright"
394 | }
395 | ],
396 | "localization" : [
397 | {
398 | "language_tag": "en_US",
399 | "/actions/keys": "Keys",
400 | "/actions/keys/in/KeyL1": "Key Left 1",
401 | "/actions/keys/in/KeyL2": "Key Left 2",
402 | "/actions/keys/in/KeyL3": "Key Left 3",
403 | "/actions/keys/in/KeyL4": "Key Left 4",
404 | "/actions/keys/in/KeyL5": "Key Left 5",
405 | "/actions/keys/in/KeyL6": "Key Left 6",
406 | "/actions/keys/in/KeyL7": "Key Left 7",
407 | "/actions/keys/in/KeyL8": "Key Left 8",
408 | "/actions/keys/in/KeyL9": "Key Left 9",
409 | "/actions/keys/in/KeyL10": "Key Left 10",
410 | "/actions/keys/in/KeyL11": "Key Left 11",
411 | "/actions/keys/in/KeyL12": "Key Left 12",
412 | "/actions/keys/in/KeyL13": "Key Left 13",
413 | "/actions/keys/in/KeyL14": "Key Left 14",
414 | "/actions/keys/in/KeyL15": "Key Left 15",
415 | "/actions/keys/in/KeyL16": "Key Left 16",
416 | "/actions/keys/in/KeyR1": "Key Right 1",
417 | "/actions/keys/in/KeyR2": "Key Right 2",
418 | "/actions/keys/in/KeyR3": "Key Right 3",
419 | "/actions/keys/in/KeyR4": "Key Right 4",
420 | "/actions/keys/in/KeyR5": "Key Right 5",
421 | "/actions/keys/in/KeyR6": "Key Right 6",
422 | "/actions/keys/in/KeyR7": "Key Right 7",
423 | "/actions/keys/in/KeyR8": "Key Right 8",
424 | "/actions/keys/in/KeyR9": "Key Right 9",
425 | "/actions/keys/in/KeyR10": "Key Right 10",
426 | "/actions/keys/in/KeyR11": "Key Right 11",
427 | "/actions/keys/in/KeyR12": "Key Right 12",
428 | "/actions/keys/in/KeyR13": "Key Right 13",
429 | "/actions/keys/in/KeyR14": "Key Right 14",
430 | "/actions/keys/in/KeyR15": "Key Right 15",
431 | "/actions/keys/in/KeyR16": "Key Right 16",
432 | "/actions/keys/in/KeyC1": "Key Chord 1",
433 | "/actions/keys/in/KeyC2": "Key Chord 2",
434 | "/actions/keys/in/KeyC3": "Key Chord 3",
435 | "/actions/keys/in/KeyC4": "Key Chord 4",
436 | "/actions/keys/in/KeyC5": "Key Chord 5",
437 | "/actions/keys/in/KeyC6": "Key Chord 6",
438 | "/actions/keys/in/KeyC7": "Key Chord 7",
439 | "/actions/keys/in/KeyC8": "Key Chord 8",
440 | "/actions/keys/in/KeyC9": "Key Chord 9",
441 | "/actions/keys/in/KeyC10": "Key Chord 10",
442 | "/actions/keys/in/KeyC11": "Key Chord 11",
443 | "/actions/keys/in/KeyC12": "Key Chord 12",
444 | "/actions/keys/in/KeyC13": "Key Chord 13",
445 | "/actions/keys/in/KeyC14": "Key Chord 14",
446 | "/actions/keys/in/KeyC15": "Key Chord 15",
447 | "/actions/keys/in/KeyC16": "Key Chord 16",
448 | "/actions/keys/in/KeyT1": "Key Tracker 1",
449 | "/actions/keys/in/KeyT2": "Key Tracker 2",
450 | "/actions/keys/in/KeyT3": "Key Tracker 3",
451 | "/actions/keys/in/KeyT4": "Key Tracker 4",
452 | "/actions/keys/in/KeyT5": "Key Tracker 5",
453 | "/actions/keys/in/KeyT6": "Key Tracker 6",
454 | "/actions/keys/in/KeyT7": "Key Tracker 7",
455 | "/actions/keys/in/KeyT8": "Key Tracker 8",
456 | "/actions/keys/in/KeyT9": "Key Tracker 9",
457 | "/actions/keys/in/KeyT10": "Key Tracker 10",
458 | "/actions/keys/in/KeyT11": "Key Tracker 11",
459 | "/actions/keys/in/KeyT12": "Key Tracker 12",
460 | "/actions/keys/in/KeyT13": "Key Tracker 13",
461 | "/actions/keys/in/KeyT14": "Key Tracker 14",
462 | "/actions/keys/in/KeyT15": "Key Tracker 15",
463 | "/actions/keys/in/KeyT16": "Key Tracker 16"
464 | }
465 | ]
466 | }
--------------------------------------------------------------------------------
/OpenVR2Key/app.vrmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "applications": [{
3 | "app_key": "boll7708.openvr2key",
4 | "launch_type": "binary",
5 | "binary_path_windows": "OpenVR2Key.exe",
6 | "is_dashboard_overlay": true,
7 | "action_manifest_path": "./actions.json",
8 |
9 | "strings": {
10 | "en_us": {
11 | "name": "OpenVR2Key",
12 | "description": "Trigger keys on your desktop."
13 | }
14 | }
15 | }]
16 | }
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_knuckles.json:
--------------------------------------------------------------------------------
1 | {
2 | "action_manifest_version": 0,
3 | "alias_info": {},
4 | "app_key": "boll7708.openvr2key",
5 | "bindings": {
6 | "/actions/keys": {
7 | "sources": [
8 | {
9 | "inputs": {
10 | "touch": {
11 | "output": "/actions/keys/in/keyl1"
12 | }
13 | },
14 | "mode": "button",
15 | "path": "/user/hand/left/input/system"
16 | },
17 | {
18 | "inputs": {
19 | "click": {
20 | "output": "/actions/keys/in/keyl2"
21 | }
22 | },
23 | "mode": "button",
24 | "path": "/user/hand/left/input/trigger"
25 | },
26 | {
27 | "inputs": {
28 | "click": {
29 | "output": "/actions/keys/in/keyl3"
30 | }
31 | },
32 | "mode": "button",
33 | "path": "/user/hand/left/input/trackpad"
34 | },
35 | {
36 | "inputs": {
37 | "click": {
38 | "output": "/actions/keys/in/keyl8"
39 | }
40 | },
41 | "mode": "button",
42 | "path": "/user/hand/left/input/thumbstick"
43 | },
44 | {
45 | "inputs": {
46 | "east": {
47 | "output": "/actions/keys/in/keyl10"
48 | },
49 | "north": {
50 | "output": "/actions/keys/in/keyl9"
51 | },
52 | "south": {
53 | "output": "/actions/keys/in/keyl11"
54 | },
55 | "west": {
56 | "output": "/actions/keys/in/keyl12"
57 | }
58 | },
59 | "mode": "dpad",
60 | "parameters": {
61 | "sub_mode": "touch"
62 | },
63 | "path": "/user/hand/left/input/thumbstick"
64 | },
65 | {
66 | "inputs": {
67 | "click": {
68 | "output": "/actions/keys/in/keyl13"
69 | }
70 | },
71 | "mode": "button",
72 | "path": "/user/hand/left/input/b"
73 | },
74 | {
75 | "inputs": {
76 | "click": {
77 | "output": "/actions/keys/in/keyl14"
78 | }
79 | },
80 | "mode": "button",
81 | "path": "/user/hand/left/input/a"
82 | },
83 | {
84 | "inputs": {
85 | "grab": {
86 | "output": "/actions/keys/in/keyl15"
87 | }
88 | },
89 | "mode": "grab",
90 | "path": "/user/hand/left/input/grip"
91 | },
92 | {
93 | "inputs": {
94 | "grab": {
95 | "output": "/actions/keys/in/keyl16"
96 | }
97 | },
98 | "mode": "grab",
99 | "path": "/user/hand/left/input/pinch"
100 | },
101 | {
102 | "inputs": {
103 | "east": {
104 | "output": "/actions/keys/in/keyl5"
105 | },
106 | "north": {
107 | "output": "/actions/keys/in/keyl4"
108 | },
109 | "south": {
110 | "output": "/actions/keys/in/keyl6"
111 | },
112 | "west": {
113 | "output": "/actions/keys/in/keyl7"
114 | }
115 | },
116 | "mode": "dpad",
117 | "parameters": {
118 | "sub_mode": "touch"
119 | },
120 | "path": "/user/hand/left/input/trackpad"
121 | },
122 | {
123 | "inputs": {
124 | "grab": {
125 | "output": "/actions/keys/in/keyr16"
126 | }
127 | },
128 | "mode": "grab",
129 | "path": "/user/hand/right/input/pinch"
130 | },
131 | {
132 | "inputs": {
133 | "grab": {
134 | "output": "/actions/keys/in/keyr15"
135 | }
136 | },
137 | "mode": "grab",
138 | "path": "/user/hand/right/input/grip"
139 | },
140 | {
141 | "inputs": {
142 | "click": {
143 | "output": "/actions/keys/in/keyr14"
144 | }
145 | },
146 | "mode": "button",
147 | "path": "/user/hand/right/input/a"
148 | },
149 | {
150 | "inputs": {
151 | "click": {
152 | "output": "/actions/keys/in/keyr13"
153 | }
154 | },
155 | "mode": "button",
156 | "path": "/user/hand/right/input/b"
157 | },
158 | {
159 | "inputs": {
160 | "click": {
161 | "output": "/actions/keys/in/keyr8"
162 | }
163 | },
164 | "mode": "button",
165 | "path": "/user/hand/right/input/thumbstick"
166 | },
167 | {
168 | "inputs": {
169 | "east": {
170 | "output": "/actions/keys/in/keyr10"
171 | },
172 | "north": {
173 | "output": "/actions/keys/in/keyr9"
174 | },
175 | "south": {
176 | "output": "/actions/keys/in/keyr11"
177 | },
178 | "west": {
179 | "output": "/actions/keys/in/keyr12"
180 | }
181 | },
182 | "mode": "dpad",
183 | "parameters": {
184 | "sub_mode": "touch"
185 | },
186 | "path": "/user/hand/right/input/thumbstick"
187 | },
188 | {
189 | "inputs": {
190 | "click": {
191 | "output": "/actions/keys/in/keyr3"
192 | }
193 | },
194 | "mode": "button",
195 | "path": "/user/hand/right/input/trackpad"
196 | },
197 | {
198 | "inputs": {
199 | "east": {
200 | "output": "/actions/keys/in/keyr5"
201 | },
202 | "north": {
203 | "output": "/actions/keys/in/keyr4"
204 | },
205 | "south": {
206 | "output": "/actions/keys/in/keyr6"
207 | },
208 | "west": {
209 | "output": "/actions/keys/in/keyr7"
210 | }
211 | },
212 | "mode": "dpad",
213 | "parameters": {
214 | "sub_mode": "touch"
215 | },
216 | "path": "/user/hand/right/input/trackpad"
217 | },
218 | {
219 | "inputs": {
220 | "click": {
221 | "output": "/actions/keys/in/keyr2"
222 | }
223 | },
224 | "mode": "button",
225 | "path": "/user/hand/right/input/trigger"
226 | },
227 | {
228 | "inputs": {
229 | "touch": {
230 | "output": "/actions/keys/in/keyr1"
231 | }
232 | },
233 | "mode": "button",
234 | "path": "/user/hand/right/input/system"
235 | }
236 | ]
237 | }
238 | },
239 | "category": "steamvr_input",
240 | "controller_type": "knuckles",
241 | "description": "OpenVR2Key input configuration for Index Controllers, feel free to edit this to fit your use case. Mapping the system button only works for touch, the click event is consumed by SteamVR itself.",
242 | "name": "Default Index Controller Binding",
243 | "options": {},
244 | "simulated_actions": []
245 | }
246 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_oculus_touch.json:
--------------------------------------------------------------------------------
1 | {
2 | "action_manifest_version": 0,
3 | "alias_info": {},
4 | "app_key": "boll7708.openvr2key",
5 | "bindings": {
6 | "/actions/keys": {
7 | "sources": [
8 | {
9 | "inputs": {
10 | "click": {
11 | "output": "/actions/keys/in/keyl2"
12 | }
13 | },
14 | "mode": "button",
15 | "path": "/user/hand/left/input/trigger"
16 | },
17 | {
18 | "inputs": {
19 | "click": {
20 | "output": "/actions/keys/in/keyl10"
21 | }
22 | },
23 | "mode": "button",
24 | "path": "/user/hand/left/input/grip"
25 | },
26 | {
27 | "inputs": {
28 | "click": {
29 | "output": "/actions/keys/in/keyr10"
30 | }
31 | },
32 | "mode": "button",
33 | "path": "/user/hand/right/input/grip"
34 | },
35 | {
36 | "inputs": {
37 | "click": {
38 | "output": "/actions/keys/in/keyr9"
39 | }
40 | },
41 | "mode": "button",
42 | "path": "/user/hand/right/input/a"
43 | },
44 | {
45 | "inputs": {
46 | "click": {
47 | "output": "/actions/keys/in/keyr8"
48 | }
49 | },
50 | "mode": "button",
51 | "path": "/user/hand/right/input/b"
52 | },
53 | {
54 | "inputs": {
55 | "click": {
56 | "output": "/actions/keys/in/keyr2"
57 | }
58 | },
59 | "mode": "button",
60 | "path": "/user/hand/right/input/trigger"
61 | },
62 | {
63 | "inputs": {
64 | "click": {
65 | "output": "/actions/keys/in/keyr1"
66 | }
67 | },
68 | "mode": "button",
69 | "path": "/user/hand/right/input/system"
70 | },
71 | {
72 | "inputs": {
73 | "click": {
74 | "output": "/actions/keys/in/keyl1"
75 | }
76 | },
77 | "mode": "button",
78 | "path": "/user/hand/left/input/system"
79 | },
80 | {
81 | "inputs": {
82 | "click": {
83 | "output": "/actions/keys/in/keyl9"
84 | }
85 | },
86 | "mode": "button",
87 | "path": "/user/hand/left/input/x"
88 | },
89 | {
90 | "inputs": {
91 | "click": {
92 | "output": "/actions/keys/in/keyl8"
93 | }
94 | },
95 | "mode": "button",
96 | "path": "/user/hand/left/input/y"
97 | },
98 | {
99 | "inputs": {
100 | "click": {
101 | "output": "/actions/keys/in/keyl3"
102 | }
103 | },
104 | "mode": "button",
105 | "path": "/user/hand/left/input/joystick"
106 | },
107 | {
108 | "inputs": {
109 | "click": {
110 | "output": "/actions/keys/in/keyr3"
111 | }
112 | },
113 | "mode": "button",
114 | "path": "/user/hand/right/input/joystick"
115 | },
116 | {
117 | "inputs": {
118 | "east": {
119 | "output": "/actions/keys/in/keyl5"
120 | },
121 | "north": {
122 | "output": "/actions/keys/in/keyl4"
123 | },
124 | "south": {
125 | "output": "/actions/keys/in/keyl6"
126 | },
127 | "west": {
128 | "output": "/actions/keys/in/keyl7"
129 | }
130 | },
131 | "mode": "dpad",
132 | "parameters": {
133 | "sub_mode": "touch"
134 | },
135 | "path": "/user/hand/left/input/joystick"
136 | },
137 | {
138 | "inputs": {
139 | "east": {
140 | "output": "/actions/keys/in/keyr5"
141 | },
142 | "north": {
143 | "output": "/actions/keys/in/keyr4"
144 | },
145 | "south": {
146 | "output": "/actions/keys/in/keyr6"
147 | },
148 | "west": {
149 | "output": "/actions/keys/in/keyr7"
150 | }
151 | },
152 | "mode": "dpad",
153 | "parameters": {
154 | "sub_mode": "touch"
155 | },
156 | "path": "/user/hand/right/input/joystick"
157 | }
158 | ]
159 | }
160 | },
161 | "category": "steamvr_input",
162 | "controller_type": "oculus_touch",
163 | "description": "OpenVR2Key input configuration for Oculus Touch, feel free to edit this to fit your use case.",
164 | "name": "Default Oculus Touch Binding",
165 | "options": {},
166 | "simulated_actions": []
167 | }
168 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_controller.json:
--------------------------------------------------------------------------------
1 | {
2 | "action_manifest_version": 0,
3 | "alias_info": {},
4 | "app_key": "boll7708.openvr2key",
5 | "bindings": {
6 | "/actions/keys": {
7 | "sources": [
8 | {
9 | "inputs": {
10 | "click": {
11 | "output": "/actions/keys/in/keyl2"
12 | }
13 | },
14 | "mode": "button",
15 | "path": "/user/hand/left/input/trigger"
16 | },
17 | {
18 | "inputs": {
19 | "click": {
20 | "output": "/actions/keys/in/keyl3"
21 | }
22 | },
23 | "mode": "button",
24 | "path": "/user/hand/left/input/trackpad"
25 | },
26 | {
27 | "inputs": {
28 | "grab": {
29 | "output": "/actions/keys/in/keyl8"
30 | }
31 | },
32 | "mode": "button",
33 | "path": "/user/hand/left/input/grip"
34 | },
35 | {
36 | "inputs": {
37 | "east": {
38 | "output": "/actions/keys/in/keyl5"
39 | },
40 | "north": {
41 | "output": "/actions/keys/in/keyl4"
42 | },
43 | "south": {
44 | "output": "/actions/keys/in/keyl6"
45 | },
46 | "west": {
47 | "output": "/actions/keys/in/keyl7"
48 | }
49 | },
50 | "mode": "dpad",
51 | "parameters": {
52 | "sub_mode": "touch"
53 | },
54 | "path": "/user/hand/left/input/trackpad"
55 | },
56 | {
57 | "inputs": {
58 | "grab": {
59 | "output": "/actions/keys/in/keyr8"
60 | }
61 | },
62 | "mode": "button",
63 | "path": "/user/hand/right/input/grip"
64 | },
65 | {
66 | "inputs": {
67 | "click": {
68 | "output": "/actions/keys/in/keyr3"
69 | }
70 | },
71 | "mode": "button",
72 | "path": "/user/hand/right/input/trackpad"
73 | },
74 | {
75 | "inputs": {
76 | "east": {
77 | "output": "/actions/keys/in/keyr5"
78 | },
79 | "north": {
80 | "output": "/actions/keys/in/keyr4"
81 | },
82 | "south": {
83 | "output": "/actions/keys/in/keyr6"
84 | },
85 | "west": {
86 | "output": "/actions/keys/in/keyr7"
87 | }
88 | },
89 | "mode": "dpad",
90 | "parameters": {
91 | "sub_mode": "touch"
92 | },
93 | "path": "/user/hand/right/input/trackpad"
94 | },
95 | {
96 | "inputs": {
97 | "click": {
98 | "output": "/actions/keys/in/keyr2"
99 | }
100 | },
101 | "mode": "button",
102 | "path": "/user/hand/right/input/trigger"
103 | },
104 | {
105 | "inputs": {
106 | "click": {
107 | "output": "/actions/keys/in/keyl1"
108 | }
109 | },
110 | "mode": "button",
111 | "path": "/user/hand/left/input/application_menu"
112 | },
113 | {
114 | "inputs": {
115 | "click": {
116 | "output": "/actions/keys/in/keyr1"
117 | }
118 | },
119 | "mode": "button",
120 | "path": "/user/hand/right/input/application_menu"
121 | }
122 | ]
123 | }
124 | },
125 | "category": "steamvr_input",
126 | "controller_type": "vive_controller",
127 | "description": "OpenVR2Key input configuration for Vive Controllers, feel free to edit this to fit your use case.",
128 | "name": "Default Vive Controller Binding",
129 | "options": {},
130 | "simulated_actions": []
131 | }
132 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_camera.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/camera/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_camera",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Camera",
23 | "name": "Camera Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_chest.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/chest/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_chest",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Chest",
23 | "name": "Chest Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_handed.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/hand/left/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | },
18 | {
19 | "path": "/user/hand/right/input/power",
20 | "mode": "button",
21 | "inputs": {
22 | "click": {
23 | "output": "/actions/keys/in/keyt1"
24 | }
25 | }
26 | }
27 | ]
28 | }
29 | },
30 | "controller_type": "vive_tracker_handed",
31 | "description": "Default OpenVR2Key input configuration for Vive Tracker in Hand",
32 | "name": "Tracker in Hand"
33 | }
34 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_head.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/head/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_head",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Head",
23 | "name": "Head Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_left_elbow.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/elbow/left/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_left_elbow",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Left Elbow",
23 | "name": "Left Elbow Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_left_foot.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/foot/left/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_left_foot",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Left Foot",
23 | "name": "Left Foot Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_left_knee.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/knee/left/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_left_knee",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Left Knee",
23 | "name": "Left Knee Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_left_shoulder.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/shoulder/left/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_left_shoulder",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Left Shoulder",
23 | "name": "Left Shoulder Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_right_elbow.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/elbow/right/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_right_elbow",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Right Elbow",
23 | "name": "Right Elbow Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_right_foot.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/foot/right/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_right_foot",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Right Foot",
23 | "name": "Right Foot Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_right_knee.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/knee/right/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_right_knee",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Right Knee",
23 | "name": "Right Knee Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_right_shoulder.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/shoulder/right/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_right_shoulder",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Right Shoulder",
23 | "name": "Right Shoulder Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/bindings_vive_tracker_waist.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": {
3 | "/actions/keys": {
4 | "chords": [],
5 | "haptics": [],
6 | "poses": [],
7 | "skeleton": [],
8 | "sources": [
9 | {
10 | "path": "/user/waist/input/power",
11 | "mode": "button",
12 | "inputs": {
13 | "click": {
14 | "output": "/actions/keys/in/keyt1"
15 | }
16 | }
17 | }
18 | ]
19 | }
20 | },
21 | "controller_type": "vive_tracker_waist",
22 | "description": "Default OpenVR2Key input configuration for Vive Tracker Waist",
23 | "name": "Waist Tracker"
24 | }
25 |
--------------------------------------------------------------------------------
/OpenVR2Key/resources/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BOLL7708/OpenVR2Key/e1f37ae045882c8c81b6947f8c049b4baf09e747/OpenVR2Key/resources/logo.ico
--------------------------------------------------------------------------------
/OpenVR2Key/resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BOLL7708/OpenVR2Key/e1f37ae045882c8c81b6947f8c049b4baf09e747/OpenVR2Key/resources/logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenVR2Key
2 | Simulate key presses on your Windows desktop from SteamVR input, download the latest release [here](https://github.com/BOLL7708/OpenVR2Key/releases).
3 |
4 | 
5 |
6 | ## How does it work?
7 | It uses the SteamVR input system to listen to VR controller input and maps that to keyboard input that it then simulates.
8 |
9 | The application comes with ear tagged key actions, 1-16 for left and right respectively and 1-8 for chords, these are mapped against whatever you configure inside this application.
10 |
11 | Key configurations can be automatically loaded for the running title, but the SteamVR input bindings stay the same regardless.
12 |
13 | To get the application to launch with SteamVR, have the application and SteamVR running, go into the SteamVR settings > Startup / Shutdown > Choose Startup Overlay Apps, and toggle OpenVR2Key to On.
14 |
15 | ## How do I use it?
16 | ### Steam Library
17 | To get OpenVR2Key to show up in the list of applications with bindings, you need to add it to your Steam library as an external application and flag it as a VR application.
18 | 1. In the Steam client, in the bottom left corner, click: `ADD A GAME` then `Add a Non-Steam Game...`
19 | 2. Click `Browse...` and locate the executable on your machine, then click `ADD SELECTED PROGRAMS`
20 | 3. Search for `OpenVR2Key` in your Steam library, in the list, right click it and choose `Properties...`
21 | 4. In the properties window check `Include in VR Library` and then close the window. That should be it!
22 |
23 | ### SteamVR Input
24 | [Clip](https://streamable.com/jvokn) - To get started, we begin in SteamVR, open the `Settings` from the hamburger menu in the SteamVR status window, then navigate to...
25 |
26 | Settings > Controller Options > Manage Controller Bindings
27 |
28 | Here locate `OPENVR2KEY` in the drop down, then switch the `Active Controller Binding` to `CUSTOM` and click `EDIT THIS BINDING`. This will open the `Controller Binding` interface.
29 |
30 | ### Controller Binding
31 | [Clip](https://streamable.com/8q2ti) - The application currently only comes with Index controller bindings, because that is what I use and have tested with. It should in theory work with any controllers that are recognized by the input system.
32 |
33 | When editing the default bindings you'll see that most inputs have a button option added to it, even if the click and touch events are not registered to anything. This is to have the inputs show up in the chord interface which otherwise would be empty.
34 |
35 | To bind a key, simply click any of the available events and pick a key. Alternatively, and what should be most useful, click the `Add Chords` button in the center and add a chord. These are button combinations that will be harder to trigger by mistake.
36 |
37 | ### Key Mapping
38 | [Clip](https://streamable.com/5ypyx) - Inside the application there's not much to do really. The main interface is the list at the bottom, where we do the actual key mapping.
39 |
40 | The row label to the left denotes which action this maps to in SteamVR, it can be benefitial to have both interfaces open at the same time if you have a lot of keys bound.
41 |
42 | On the row you want to configure, click the big button to start registering keys, now press the keys you want to be in the mapping. As long as you don't let all keys go you can add more without holding down all the previous ones, it will restart the current registration if you press any key after letting go of all them. To finish the registration click the row again. That's it!
43 |
44 | 
45 |
46 | ### Notes
47 | I've tried to add support for as many keys as possible, report anything you feel is missing as I have to match codes from input to output, they are not the same.
48 |
49 | Keep in mind that the key presses act just as if you typed on your keyboard, what will happen depends on where the application focus is. The base use for this was to either control things in the current game or to trigger global hotkeys.
50 |
51 |
52 |
--------------------------------------------------------------------------------