Repository navigation
Expand file tree
/
Copy pathvirtual_gamepad.cpp
More file actions
183 lines (154 loc) · 5.06 KB
/
Copy pathvirtual_gamepad.cpp
File metadata and controls
183 lines (154 loc) · 5.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include "virtual_gamepad.hpp"
#include "pyro_protocol.h"
#include "bitops.hpp"
#include <stdexcept>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/uinput.h>
#include <string.h>
namespace PyroFling
{
static const int button_mapping[] =
{
BTN_A, BTN_B, BTN_X, BTN_Y, // Steam input seems to prefer this layout rather than standard cardinal.
BTN_TL, BTN_TR,
BTN_THUMBL, BTN_THUMBR,
BTN_START, BTN_SELECT,
//BTN_MODE, Causes too much annoyance and confusion when this is piped through.
};
void VirtualGamepad::init_debug_mouse()
{
uinput_setup usetup = {};
if (::ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY) < 0 ||
::ioctl(uinput_fd, UI_SET_KEYBIT, BTN_LEFT) < 0 ||
::ioctl(uinput_fd, UI_SET_KEYBIT, BTN_RIGHT) < 0 ||
::ioctl(uinput_fd, UI_SET_EVBIT, EV_REL) < 0 ||
::ioctl(uinput_fd, UI_SET_RELBIT, REL_X) < 0 ||
::ioctl(uinput_fd, UI_SET_RELBIT, REL_Y) < 0)
{
throw std::runtime_error("Failed to setup uinput.");
}
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = FAKE_VID;
usetup.id.product = FAKE_PID_MOUSE;
strcpy(usetup.name, "PyroFling Fake Mouse");
if (::ioctl(uinput_fd, UI_DEV_SETUP, &usetup) < 0 ||
::ioctl(uinput_fd, UI_DEV_CREATE) < 0)
{
throw std::runtime_error("Failed to setup uinput.");
}
}
VirtualGamepad::VirtualGamepad()
{
uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (uinput_fd < 0)
throw std::runtime_error("Failed to open /dev/uinput.");
}
void VirtualGamepad::init_gamepad(uint32_t fake_vid, uint32_t fake_pid, const char *fake_name)
{
if (ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY) < 0)
throw std::runtime_error("Failed to set EV_KEY.");
if (ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS) < 0)
throw std::runtime_error("Failed to set EV_KEY.");
// Emulate a generic pad according that conforms to Linux evdev specs and basically emulates a PS4 controller.
for (auto btn : button_mapping)
if (ioctl(uinput_fd, UI_SET_KEYBIT, btn) < 0)
throw std::runtime_error("Failed to set keybit.");
static const int axes[] =
{
ABS_X, ABS_Y, ABS_RX, ABS_RY,
ABS_Z, ABS_RZ,
ABS_HAT0X, ABS_HAT0Y,
};
for (auto axis : axes)
{
if (ioctl(uinput_fd, UI_SET_ABSBIT, axis) < 0)
throw std::runtime_error("Failed to set absbit.");
int range;
if (axis == ABS_HAT0X || axis == ABS_HAT0Y)
range = 1;
else if (axis == ABS_Z || axis == ABS_RZ)
range = 0xff;
else
range = 0x7fff;
uinput_abs_setup abs_setup = {};
abs_setup.code = axis;
abs_setup.absinfo.minimum = (axis == ABS_Z || axis == ABS_RZ) ? 0 : -range;
abs_setup.absinfo.maximum = range;
if (ioctl(uinput_fd, UI_ABS_SETUP, &abs_setup) < 0)
{
close(uinput_fd);
throw std::runtime_error("Failed to set absbit.");
}
}
uinput_setup usetup = {};
usetup.id.bustype = BUS_USB;
usetup.id.vendor = fake_vid ? fake_vid : uint32_t(FAKE_VID); // Dummy values.
usetup.id.product = fake_pid ? fake_pid : uint32_t(FAKE_PID);
strcpy(usetup.name, fake_name ? fake_name : "PyroFling virtual gamepad");
if (ioctl(uinput_fd, UI_DEV_SETUP, &usetup) < 0 || ioctl(uinput_fd, UI_DEV_CREATE) < 0)
throw std::runtime_error("Failed to setup uinput device.");
}
VirtualGamepad::VirtualGamepad(DebugMode mode, uint32_t fake_vid, uint32_t fake_pid, const char *fake_name)
: VirtualGamepad()
{
debug_mode = mode;
if (mode == DebugMode::None)
init_gamepad(fake_vid, fake_pid, fake_name);
else if (mode == DebugMode::RightAnalogToMouse)
init_debug_mouse();
}
static void write_event(int fd, int type, int code, int val)
{
input_event e = {};
e.code = code;
e.type = type;
e.value = val;
if (write(fd, &e, sizeof(e)) < 0)
fprintf(stderr, "Failed to write uinput event.\n");
}
void VirtualGamepad::report_state(const pyro_gamepad_state &state)
{
if (debug_mode == DebugMode::RightAnalogToMouse)
{
// Designed for doing latency measurements with the layer when doing remote gamepad path.
bool delta_x = state.axis_rx && state.axis_rx != last_state.axis_rx;
bool delta_y = state.axis_ry && state.axis_ry != last_state.axis_ry;
if (delta_x)
write_event(uinput_fd, EV_REL, REL_X, (state.axis_rx - last_state.axis_rx) / 3000);
if (delta_y)
write_event(uinput_fd, EV_REL, REL_Y, (state.axis_ry - last_state.axis_ry) / 3000);
if (delta_x || delta_y)
write_event(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
else if (debug_mode == DebugMode::None)
{
uint16_t delta_btn = state.buttons ^ last_state.buttons;
Util::for_each_bit(delta_btn, [this, v = state.buttons](unsigned bit) {
if (bit < sizeof(button_mapping) / sizeof(button_mapping[0]))
write_event(uinput_fd, EV_KEY, button_mapping[bit], (v & (1u << bit)) ? 1 : 0);
});
#define AXIS(v, code) do { if (state.v != last_state.v) write_event(uinput_fd, EV_ABS, code, state.v); } while(0)
AXIS(axis_lx, ABS_X);
AXIS(axis_ly, ABS_Y);
AXIS(axis_rx, ABS_RX);
AXIS(axis_ry, ABS_RY);
AXIS(lz, ABS_Z);
AXIS(rz, ABS_RZ);
AXIS(hat_x, ABS_HAT0X);
AXIS(hat_y, ABS_HAT0Y);
write_event(uinput_fd, EV_SYN, SYN_REPORT, 0);
}
last_state = state;
}
VirtualGamepad::~VirtualGamepad()
{
if (uinput_fd >= 0)
{
ioctl(uinput_fd, UI_DEV_DESTROY);
close(uinput_fd);
}
}
}