-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathvirtualhid_control_model.cpp
More file actions
312 lines (278 loc) · 9.39 KB
/
Copy pathvirtualhid_control_model.cpp
File metadata and controls
312 lines (278 loc) · 9.39 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/**
* @file tools/virtualhid_control_model.cpp
* @brief Testable model helper definitions for the native libvirtualhid control UI.
*/
// standard includes
#include <algorithm>
#include <cctype>
#include <cmath>
#include <iomanip>
#include <ranges>
#include <sstream>
// local includes
#include "virtualhid_control_model.hpp"
namespace lvh::tools::virtualhid_control {
namespace {
void append_summary_separator(std::wostringstream &stream, bool wrote) {
if (wrote) {
stream << L" | ";
}
}
} // namespace
std::wstring device_type_name(DeviceType type) {
switch (type) {
using enum DeviceType;
case gamepad:
return L"gamepad";
case keyboard:
return L"keyboard";
case mouse:
return L"mouse";
case touchscreen:
return L"touchscreen";
case trackpad:
return L"trackpad";
case pen_tablet:
return L"pen tablet";
}
return L"unknown";
}
std::wstring node_kind_name(DeviceNodeKind kind) {
switch (kind) {
using enum DeviceNodeKind;
case input_event:
return L"input";
case joystick:
return L"joystick";
case hidraw:
return L"hidraw";
case sysfs:
return L"sysfs";
case other:
return L"other";
}
return L"other";
}
std::wstring output_kind_name(GamepadOutputKind kind) {
switch (kind) {
using enum GamepadOutputKind;
case rumble:
return L"rumble";
case rgb_led:
return L"rgb led";
case adaptive_triggers:
return L"adaptive triggers";
case raw_report:
return L"raw report";
case trigger_rumble:
return L"trigger rumble";
case player_led:
return L"player led";
case mic_led:
return L"mic led";
}
return L"raw report";
}
std::wstring battery_state_name(GamepadBatteryState state) {
switch (state) {
using enum GamepadBatteryState;
case unknown:
return L"unknown";
case discharging:
return L"discharging";
case charging:
return L"charging";
case full:
return L"full";
case voltage_or_temperature_error:
return L"voltage/temperature error";
case temperature_error:
return L"temperature error";
case charging_error:
return L"charging error";
}
return L"unknown";
}
int battery_choice_index(GamepadBatteryState state) {
for (std::size_t index = 0; index < battery_choices.size(); ++index) {
if (battery_choices[index].state == state) {
return static_cast<int>(index);
}
}
return 0;
}
std::wstring raw_hex(const std::vector<std::uint8_t> &bytes) {
std::wostringstream stream;
stream << std::hex << std::setfill(L'0');
for (const auto value : bytes) {
stream << std::setw(2) << static_cast<unsigned>(value);
}
return stream.str();
}
std::optional<DeviceProfile> profile_for_choice(const ProfileChoice &choice) {
switch (choice.kind) {
using enum GamepadProfileKind;
case generic:
return profiles::generic_gamepad();
case xbox_360:
return profiles::xbox_360();
case xbox_one:
return profiles::xbox_one();
case xbox_series:
return profiles::xbox_series();
case dualshock4:
return profiles::dualshock4();
case dualsense:
return profiles::dualsense();
case switch_pro:
return profiles::switch_pro();
}
return std::nullopt;
}
int axis_to_slider(float value) {
return static_cast<int>(std::lround(std::clamp(value, -1.0F, 1.0F) * static_cast<float>(slider_scale)));
}
int trigger_to_slider(float value) {
return static_cast<int>(std::lround(std::clamp(value, 0.0F, 1.0F) * static_cast<float>(slider_scale)));
}
float slider_to_float(long value) {
return static_cast<float>(value) / static_cast<float>(slider_scale);
}
std::wstring yes_no(bool value) {
return value ? L"yes" : L"no";
}
std::string normalized_license_key(std::string_view license_key) {
const auto is_whitespace = [](char character) {
return std::isspace(static_cast<unsigned char>(character)) != 0;
};
const auto first = std::ranges::find_if_not(license_key, is_whitespace);
const auto last = std::ranges::find_if_not(std::views::reverse(license_key), is_whitespace).base();
return first == license_key.end() ? std::string {} : std::string {first, last};
}
bool supports_normalized_feedback(const DeviceProfile &profile) {
using enum GamepadOutputKind;
return supports_gamepad_output(profile, rumble) ||
supports_gamepad_output(profile, rgb_led) ||
supports_gamepad_output(profile, adaptive_triggers) ||
supports_gamepad_output(profile, trigger_rumble);
}
std::wstring profile_feature_summary(const DeviceProfile &profile) {
using enum GamepadOutputKind;
const auto support = gamepad_profile_support(profile);
std::wostringstream stream;
stream << L"Features: battery " << yes_no(support.supports_battery);
stream << L" | rumble " << yes_no(supports_gamepad_output(profile, rumble));
stream << L" | trigger rumble " << yes_no(supports_gamepad_output(profile, trigger_rumble));
stream << L" | RGB LED " << yes_no(supports_gamepad_output(profile, rgb_led));
stream << L" | adaptive triggers " << yes_no(supports_gamepad_output(profile, adaptive_triggers));
stream << L" | raw output " << yes_no(supports_gamepad_output(profile, raw_report));
return stream.str();
}
bool append_latest_output_summary(std::wostringstream &stream, const OutputState &state) {
auto wrote = false;
if (state.latest_rumble) {
stream << L"rumble low=" << state.latest_rumble->low_frequency_rumble
<< L" high=" << state.latest_rumble->high_frequency_rumble;
wrote = true;
}
if (state.latest_trigger_rumble) {
append_summary_separator(stream, wrote);
stream << L"trigger rumble L=" << state.latest_trigger_rumble->left_trigger_rumble
<< L" R=" << state.latest_trigger_rumble->right_trigger_rumble;
wrote = true;
}
if (state.latest_rgb_led) {
append_summary_separator(stream, wrote);
stream << L"RGB " << static_cast<unsigned>(state.latest_rgb_led->red) << L","
<< static_cast<unsigned>(state.latest_rgb_led->green) << L","
<< static_cast<unsigned>(state.latest_rgb_led->blue);
wrote = true;
}
if (state.latest_player_led) {
append_summary_separator(stream, wrote);
stream << L"player LEDs=" << static_cast<unsigned>(state.latest_player_led->player_led);
wrote = true;
}
if (state.latest_mic_led) {
append_summary_separator(stream, wrote);
stream << L"mic LED=" << static_cast<unsigned>(state.latest_mic_led->mic_led);
wrote = true;
}
if (state.latest_adaptive_triggers) {
append_summary_separator(stream, wrote);
stream << L"adaptive flags=" << static_cast<unsigned>(state.latest_adaptive_triggers->adaptive_trigger_flags);
wrote = true;
}
if (!wrote && state.latest_raw_report) {
stream << L"raw report";
wrote = true;
}
return wrote;
}
std::wstring output_summary(const OutputState &state, const DeviceProfile &profile) {
std::wostringstream stream;
stream << L"Output: ";
if (state.outputs.empty()) {
stream << L"no reports received";
} else if (!append_latest_output_summary(stream, state)) {
stream << L"reports received";
}
if (!supports_normalized_feedback(profile)) {
stream << L" | profile has no normalized feedback categories";
}
return stream.str();
}
bool update_visible_controls_for_profile(
const DeviceProfile &profile,
std::array<bool, button_choices.size()> &visible_buttons,
bool &battery_controls_visible
) {
auto changed = false;
for (std::size_t index = 0; index < button_choices.size(); ++index) {
const auto visible = supports_gamepad_button(profile, button_choices[index].button);
changed = changed || visible_buttons[index] != visible;
visible_buttons[index] = visible;
}
changed = changed || battery_controls_visible != profile.capabilities.supports_battery;
battery_controls_visible = profile.capabilities.supports_battery;
return changed;
}
void record_output(
OutputState &state,
const GamepadOutput &output,
std::uint64_t &next_sequence,
std::size_t max_output_events
) {
state.outputs.push_back({.sequence = next_sequence++, .output = output});
if (state.outputs.size() > max_output_events) {
state.outputs.erase(
state.outputs.begin(),
state.outputs.begin() + static_cast<std::ptrdiff_t>(state.outputs.size() - max_output_events)
);
}
switch (output.kind) {
using enum GamepadOutputKind;
case rumble:
state.latest_rumble = output;
break;
case trigger_rumble:
state.latest_trigger_rumble = output;
break;
case rgb_led:
state.latest_rgb_led = output;
break;
case player_led:
state.latest_player_led = output;
break;
case mic_led:
state.latest_mic_led = output;
break;
case adaptive_triggers:
state.latest_adaptive_triggers = output;
break;
case raw_report:
state.latest_raw_report = output;
break;
}
}
} // namespace lvh::tools::virtualhid_control