@nearcade/virtual-gamepad 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/config/game_profiles.csv +339 -0
- package/config/kbm_bindings.json +50 -0
- package/index.js +10 -0
- package/lib/InputOrchestrator.js +829 -0
- package/native/binding.gyp +18 -0
- package/native/build/Release/uinputBridge.node +0 -0
- package/native/uinputBridge.cpp +613 -0
- package/package.json +56 -0
- package/python/__init__.py +1 -0
- package/python/linux_uinput.py +697 -0
- package/python/mac_gamepad_bridge.py +532 -0
- package/python/read_gamepads.py +291 -0
- package/python/windows_hidmaestro.py +394 -0
- package/python/windows_vigem.py +544 -0
- package/windows/HmBridge/HmBridge.csproj +14 -0
- package/windows/HmBridge/HmBridge.exe +0 -0
- package/windows/HmBridge/Program.cs +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.IO;
|
|
4
|
+
using System.Text.Json;
|
|
5
|
+
using System.Text.Json.Serialization;
|
|
6
|
+
using HIDMaestro;
|
|
7
|
+
|
|
8
|
+
namespace HmBridge;
|
|
9
|
+
|
|
10
|
+
class Program
|
|
11
|
+
{
|
|
12
|
+
static HMContext? _ctx;
|
|
13
|
+
static readonly Dictionary<string, HMController> _controllers = new();
|
|
14
|
+
|
|
15
|
+
static readonly JsonSerializerOptions JsonOptions = new()
|
|
16
|
+
{
|
|
17
|
+
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
|
18
|
+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static void Main()
|
|
22
|
+
{
|
|
23
|
+
using var stdin = new StreamReader(Console.OpenStandardInput());
|
|
24
|
+
string? line;
|
|
25
|
+
while ((line = stdin.ReadLine()) != null)
|
|
26
|
+
{
|
|
27
|
+
line = line.Trim();
|
|
28
|
+
if (line.Length == 0) continue;
|
|
29
|
+
try
|
|
30
|
+
{
|
|
31
|
+
ProcessMessage(line);
|
|
32
|
+
}
|
|
33
|
+
catch (Exception ex)
|
|
34
|
+
{
|
|
35
|
+
Emit(new { type = "error", message = ex.Message, code = "HM_BRIDGE_ERROR" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
foreach (var c in _controllers.Values) try { c.Dispose(); } catch { }
|
|
39
|
+
try { _ctx?.Dispose(); } catch { }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static void ProcessMessage(string json)
|
|
43
|
+
{
|
|
44
|
+
using var doc = JsonDocument.Parse(json);
|
|
45
|
+
var root = doc.RootElement;
|
|
46
|
+
var type = root.GetProperty("type").GetString();
|
|
47
|
+
|
|
48
|
+
switch (type)
|
|
49
|
+
{
|
|
50
|
+
case "init": HandleInit(root); break;
|
|
51
|
+
case "create": HandleCreate(root); break;
|
|
52
|
+
case "state": HandleState(root); break;
|
|
53
|
+
case "free": HandleFree(root); break;
|
|
54
|
+
case "destroy_all": HandleDestroyAll(); break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static void HandleInit(JsonElement msg)
|
|
59
|
+
{
|
|
60
|
+
if (_ctx != null) return;
|
|
61
|
+
_ctx = new HMContext();
|
|
62
|
+
_ctx.LoadDefaultProfiles();
|
|
63
|
+
Emit(new { type = "ready", message = $"HmBridge ok ({_ctx.AllProfiles.Count} profiles loaded)" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static void HandleCreate(JsonElement msg)
|
|
67
|
+
{
|
|
68
|
+
EnsureContext();
|
|
69
|
+
var padId = msg.GetProperty("pad_id").GetString()!;
|
|
70
|
+
if (_controllers.ContainsKey(padId)) return;
|
|
71
|
+
|
|
72
|
+
var profileId = "xbox-360-wired";
|
|
73
|
+
if (msg.TryGetProperty("profile", out var pEl))
|
|
74
|
+
profileId = pEl.GetString() ?? profileId;
|
|
75
|
+
|
|
76
|
+
var profile = _ctx!.GetProfile(profileId);
|
|
77
|
+
if (profile == null)
|
|
78
|
+
{
|
|
79
|
+
Emit(new { type = "error", message = $"Profile '{profileId}' not found", code = "PROFILE_NOT_FOUND" });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!_ctx.IsDriverInstalled)
|
|
84
|
+
try { _ctx.InstallDriver(); }
|
|
85
|
+
catch (UnauthorizedAccessException)
|
|
86
|
+
{
|
|
87
|
+
Emit(new { type = "error", message = "HIDMaestro driver needs admin. Run Nearcade as Administrator.", code = "ADMIN_REQUIRED" });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
HMController controller;
|
|
92
|
+
try { controller = _ctx.CreateController(profile); }
|
|
93
|
+
catch (UnauthorizedAccessException)
|
|
94
|
+
{
|
|
95
|
+
Emit(new { type = "error", message = "Creating controllers needs admin. Run Nearcade as Administrator.", code = "ADMIN_REQUIRED" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
var captured = padId;
|
|
100
|
+
controller.OutputReceived += (_, packet) =>
|
|
101
|
+
{
|
|
102
|
+
var data = packet.Data.Span;
|
|
103
|
+
if (data.Length >= 4)
|
|
104
|
+
Emit(new { type = "rumble", pad_id = captured, strong = data[2] / 255.0, weak = data[3] / 255.0, duration = 250 });
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
_controllers[padId] = controller;
|
|
108
|
+
Emit(new { type = "log", message = $"Created {profile.Name} for {padId}" });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static void HandleState(JsonElement msg)
|
|
112
|
+
{
|
|
113
|
+
if (_ctx == null) return;
|
|
114
|
+
var padId = msg.GetProperty("pad_id").GetString()!;
|
|
115
|
+
if (!_controllers.TryGetValue(padId, out var ctrl)) return;
|
|
116
|
+
|
|
117
|
+
double G(string key, double def) =>
|
|
118
|
+
msg.TryGetProperty(key, out var el) ? el.GetDouble() : def;
|
|
119
|
+
|
|
120
|
+
var axes = HMGamepadStateHelpers.StandardAxes(ctrl.Profile,
|
|
121
|
+
(float)G("lx", 0.5), (float)G("ly", 0.5),
|
|
122
|
+
(float)G("rx", 0.5), (float)G("ry", 0.5),
|
|
123
|
+
(float)G("lt", 0.0), (float)G("rt", 0.0));
|
|
124
|
+
|
|
125
|
+
uint buttons = 0;
|
|
126
|
+
if (msg.TryGetProperty("buttons", out var bEl)) buttons = bEl.GetUInt32();
|
|
127
|
+
|
|
128
|
+
int hat = 0;
|
|
129
|
+
if (msg.TryGetProperty("hat", out var hEl)) hat = hEl.GetInt32();
|
|
130
|
+
|
|
131
|
+
ctrl.SubmitState(new HMGamepadState { Axes = axes, Buttons = (HMButton)buttons, Hat = (HMHat)hat });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static void HandleFree(JsonElement msg)
|
|
135
|
+
{
|
|
136
|
+
var padId = msg.GetProperty("pad_id").GetString()!;
|
|
137
|
+
if (_controllers.TryGetValue(padId, out var ctrl))
|
|
138
|
+
{
|
|
139
|
+
ctrl.Dispose();
|
|
140
|
+
_controllers.Remove(padId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static void HandleDestroyAll()
|
|
145
|
+
{
|
|
146
|
+
foreach (var c in _controllers.Values) c.Dispose();
|
|
147
|
+
_controllers.Clear();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static void EnsureContext()
|
|
151
|
+
{
|
|
152
|
+
if (_ctx == null)
|
|
153
|
+
{
|
|
154
|
+
_ctx = new HMContext();
|
|
155
|
+
_ctx.LoadDefaultProfiles();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static void Emit(object obj)
|
|
160
|
+
{
|
|
161
|
+
Console.WriteLine(JsonSerializer.Serialize(obj, JsonOptions));
|
|
162
|
+
Console.Out.Flush();
|
|
163
|
+
}
|
|
164
|
+
}
|