2021-09-21 00:43:16 +00:00
|
|
|
// Copyright 2021 yuzu Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included
|
|
|
|
|
2021-10-31 03:23:10 +00:00
|
|
|
#include <algorithm>
|
2021-09-21 00:43:16 +00:00
|
|
|
#include <fmt/format.h>
|
|
|
|
|
|
|
|
#include "core/hid/emulated_devices.h"
|
|
|
|
#include "core/hid/input_converter.h"
|
|
|
|
|
|
|
|
namespace Core::HID {
|
|
|
|
|
2021-10-23 04:04:06 +00:00
|
|
|
EmulatedDevices::EmulatedDevices() = default;
|
2021-09-21 00:43:16 +00:00
|
|
|
|
|
|
|
EmulatedDevices::~EmulatedDevices() = default;
|
|
|
|
|
|
|
|
void EmulatedDevices::ReloadFromSettings() {
|
|
|
|
ReloadInput();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::ReloadInput() {
|
2021-11-14 20:09:29 +00:00
|
|
|
// If you load any device here add the equivalent to the UnloadInput() function
|
2021-11-14 16:45:07 +00:00
|
|
|
std::size_t key_index = 0;
|
2021-11-14 20:09:29 +00:00
|
|
|
for (auto& mouse_device : mouse_button_devices) {
|
|
|
|
Common::ParamPackage mouse_params;
|
|
|
|
mouse_params.Set("engine", "mouse");
|
|
|
|
mouse_params.Set("button", static_cast<int>(key_index));
|
|
|
|
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
|
|
|
|
key_index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mouse_stick_device = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
|
|
|
"engine:mouse,axis_x:0,axis_y:1");
|
|
|
|
|
|
|
|
// First two axis are reserved for mouse position
|
|
|
|
key_index = 2;
|
|
|
|
for (auto& mouse_device : mouse_analog_devices) {
|
|
|
|
Common::ParamPackage mouse_params;
|
|
|
|
mouse_params.Set("engine", "mouse");
|
|
|
|
mouse_params.Set("axis", static_cast<int>(key_index));
|
|
|
|
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
|
|
|
|
key_index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
key_index = 0;
|
2021-11-14 16:45:07 +00:00
|
|
|
for (auto& keyboard_device : keyboard_devices) {
|
|
|
|
// Keyboard keys are only mapped on port 1, pad 0
|
|
|
|
Common::ParamPackage keyboard_params;
|
|
|
|
keyboard_params.Set("engine", "keyboard");
|
|
|
|
keyboard_params.Set("button", static_cast<int>(key_index));
|
|
|
|
keyboard_params.Set("port", 1);
|
|
|
|
keyboard_params.Set("pad", 0);
|
|
|
|
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
|
|
|
|
key_index++;
|
|
|
|
}
|
2021-09-21 00:43:16 +00:00
|
|
|
|
2021-11-14 16:45:07 +00:00
|
|
|
key_index = 0;
|
|
|
|
for (auto& keyboard_device : keyboard_modifier_devices) {
|
|
|
|
// Keyboard moddifiers are only mapped on port 1, pad 1
|
|
|
|
Common::ParamPackage keyboard_params;
|
|
|
|
keyboard_params.Set("engine", "keyboard");
|
|
|
|
keyboard_params.Set("button", static_cast<int>(key_index));
|
|
|
|
keyboard_params.Set("port", 1);
|
|
|
|
keyboard_params.Set("pad", 1);
|
|
|
|
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
|
|
|
|
key_index++;
|
|
|
|
}
|
2021-09-21 00:43:16 +00:00
|
|
|
|
|
|
|
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
|
|
|
if (!mouse_button_devices[index]) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-12-14 02:09:28 +00:00
|
|
|
mouse_button_devices[index]->SetCallback({
|
|
|
|
.on_change =
|
|
|
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
|
|
|
SetMouseButton(callback, index);
|
|
|
|
},
|
|
|
|
});
|
2021-09-21 00:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 20:09:29 +00:00
|
|
|
for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) {
|
|
|
|
if (!mouse_analog_devices[index]) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-12-14 02:09:28 +00:00
|
|
|
mouse_analog_devices[index]->SetCallback({
|
|
|
|
.on_change =
|
|
|
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
|
|
|
SetMouseAnalog(callback, index);
|
|
|
|
},
|
|
|
|
});
|
2021-11-14 20:09:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mouse_stick_device) {
|
2021-12-14 02:09:28 +00:00
|
|
|
mouse_stick_device->SetCallback({
|
|
|
|
.on_change =
|
|
|
|
[this](const Common::Input::CallbackStatus& callback) { SetMouseStick(callback); },
|
|
|
|
});
|
2021-11-14 20:09:29 +00:00
|
|
|
}
|
|
|
|
|
2021-09-21 00:43:16 +00:00
|
|
|
for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
|
|
|
|
if (!keyboard_devices[index]) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-12-14 02:09:28 +00:00
|
|
|
keyboard_devices[index]->SetCallback({
|
|
|
|
.on_change =
|
|
|
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
|
|
|
SetKeyboardButton(callback, index);
|
|
|
|
},
|
|
|
|
});
|
2021-09-21 00:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
|
|
|
|
if (!keyboard_modifier_devices[index]) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-12-14 02:09:28 +00:00
|
|
|
keyboard_modifier_devices[index]->SetCallback({
|
|
|
|
.on_change =
|
|
|
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
|
|
|
SetKeyboardModifier(callback, index);
|
|
|
|
},
|
|
|
|
});
|
2021-09-21 00:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::UnloadInput() {
|
|
|
|
for (auto& button : mouse_button_devices) {
|
|
|
|
button.reset();
|
|
|
|
}
|
2021-11-14 20:09:29 +00:00
|
|
|
for (auto& analog : mouse_analog_devices) {
|
|
|
|
analog.reset();
|
|
|
|
}
|
|
|
|
mouse_stick_device.reset();
|
2021-09-21 00:43:16 +00:00
|
|
|
for (auto& button : keyboard_devices) {
|
|
|
|
button.reset();
|
|
|
|
}
|
|
|
|
for (auto& button : keyboard_modifier_devices) {
|
|
|
|
button.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::EnableConfiguration() {
|
|
|
|
is_configuring = true;
|
|
|
|
SaveCurrentConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::DisableConfiguration() {
|
|
|
|
is_configuring = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EmulatedDevices::IsConfiguring() const {
|
|
|
|
return is_configuring;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::SaveCurrentConfig() {
|
|
|
|
if (!is_configuring) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::RestoreConfig() {
|
|
|
|
if (!is_configuring) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ReloadFromSettings();
|
|
|
|
}
|
|
|
|
|
2021-12-14 02:09:28 +00:00
|
|
|
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
|
|
|
std::size_t index) {
|
2021-09-21 00:43:16 +00:00
|
|
|
if (index >= device_status.keyboard_values.size()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::lock_guard lock{mutex};
|
|
|
|
bool value_changed = false;
|
|
|
|
const auto new_status = TransformToButton(callback);
|
|
|
|
auto& current_status = device_status.keyboard_values[index];
|
|
|
|
current_status.toggle = new_status.toggle;
|
|
|
|
|
2021-10-17 05:33:00 +00:00
|
|
|
// Update button status with current status
|
2021-09-21 00:43:16 +00:00
|
|
|
if (!current_status.toggle) {
|
|
|
|
current_status.locked = false;
|
|
|
|
if (current_status.value != new_status.value) {
|
|
|
|
current_status.value = new_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Toggle button and lock status
|
|
|
|
if (new_status.value && !current_status.locked) {
|
|
|
|
current_status.locked = true;
|
|
|
|
current_status.value = !current_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
|
2021-10-17 05:33:00 +00:00
|
|
|
// Unlock button, ready for next press
|
2021-09-21 00:43:16 +00:00
|
|
|
if (!new_status.value && current_status.locked) {
|
|
|
|
current_status.locked = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value_changed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_configuring) {
|
|
|
|
TriggerOnChange(DeviceTriggerType::Keyboard);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-14 05:25:45 +00:00
|
|
|
// Index should be converted from NativeKeyboard to KeyboardKeyIndex
|
2021-10-24 16:22:20 +00:00
|
|
|
UpdateKey(index, current_status.value);
|
2021-09-21 00:43:16 +00:00
|
|
|
|
|
|
|
TriggerOnChange(DeviceTriggerType::Keyboard);
|
|
|
|
}
|
|
|
|
|
2021-10-24 16:22:20 +00:00
|
|
|
void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
|
2021-11-14 05:25:45 +00:00
|
|
|
constexpr std::size_t KEYS_PER_BYTE = 8;
|
2021-10-24 16:22:20 +00:00
|
|
|
auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
|
2021-10-31 03:23:10 +00:00
|
|
|
const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
|
2021-10-24 16:22:20 +00:00
|
|
|
if (status) {
|
|
|
|
entry = entry | mask;
|
|
|
|
} else {
|
2021-10-25 04:23:54 +00:00
|
|
|
entry = static_cast<u8>(entry & ~mask);
|
2021-10-24 16:22:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-14 02:09:28 +00:00
|
|
|
void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& callback,
|
2021-10-31 03:23:10 +00:00
|
|
|
std::size_t index) {
|
2021-09-21 00:43:16 +00:00
|
|
|
if (index >= device_status.keyboard_moddifier_values.size()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::lock_guard lock{mutex};
|
|
|
|
bool value_changed = false;
|
|
|
|
const auto new_status = TransformToButton(callback);
|
|
|
|
auto& current_status = device_status.keyboard_moddifier_values[index];
|
|
|
|
current_status.toggle = new_status.toggle;
|
|
|
|
|
|
|
|
// Update button status with current
|
|
|
|
if (!current_status.toggle) {
|
|
|
|
current_status.locked = false;
|
|
|
|
if (current_status.value != new_status.value) {
|
|
|
|
current_status.value = new_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Toggle button and lock status
|
|
|
|
if (new_status.value && !current_status.locked) {
|
|
|
|
current_status.locked = true;
|
|
|
|
current_status.value = !current_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock button ready for next press
|
|
|
|
if (!new_status.value && current_status.locked) {
|
|
|
|
current_status.locked = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value_changed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_configuring) {
|
|
|
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (index) {
|
|
|
|
case Settings::NativeKeyboard::LeftControl:
|
|
|
|
case Settings::NativeKeyboard::RightControl:
|
|
|
|
device_status.keyboard_moddifier_state.control.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::LeftShift:
|
|
|
|
case Settings::NativeKeyboard::RightShift:
|
|
|
|
device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::LeftAlt:
|
|
|
|
device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::RightAlt:
|
|
|
|
device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::CapsLock:
|
|
|
|
device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::ScrollLock:
|
|
|
|
device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeKeyboard::NumLock:
|
|
|
|
device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
|
|
|
|
}
|
|
|
|
|
2021-12-14 02:09:28 +00:00
|
|
|
void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callback,
|
|
|
|
std::size_t index) {
|
2021-09-21 00:43:16 +00:00
|
|
|
if (index >= device_status.mouse_button_values.size()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::lock_guard lock{mutex};
|
|
|
|
bool value_changed = false;
|
|
|
|
const auto new_status = TransformToButton(callback);
|
|
|
|
auto& current_status = device_status.mouse_button_values[index];
|
|
|
|
current_status.toggle = new_status.toggle;
|
|
|
|
|
|
|
|
// Update button status with current
|
|
|
|
if (!current_status.toggle) {
|
|
|
|
current_status.locked = false;
|
|
|
|
if (current_status.value != new_status.value) {
|
|
|
|
current_status.value = new_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Toggle button and lock status
|
|
|
|
if (new_status.value && !current_status.locked) {
|
|
|
|
current_status.locked = true;
|
|
|
|
current_status.value = !current_status.value;
|
|
|
|
value_changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock button ready for next press
|
|
|
|
if (!new_status.value && current_status.locked) {
|
|
|
|
current_status.locked = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value_changed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_configuring) {
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (index) {
|
|
|
|
case Settings::NativeMouseButton::Left:
|
|
|
|
device_status.mouse_button_state.left.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeMouseButton::Right:
|
|
|
|
device_status.mouse_button_state.right.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeMouseButton::Middle:
|
|
|
|
device_status.mouse_button_state.middle.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeMouseButton::Forward:
|
|
|
|
device_status.mouse_button_state.forward.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeMouseButton::Back:
|
|
|
|
device_status.mouse_button_state.back.Assign(current_status.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
}
|
|
|
|
|
2021-12-14 02:09:28 +00:00
|
|
|
void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callback,
|
|
|
|
std::size_t index) {
|
2021-11-14 20:09:29 +00:00
|
|
|
if (index >= device_status.mouse_analog_values.size()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::lock_guard lock{mutex};
|
|
|
|
const auto analog_value = TransformToAnalog(callback);
|
|
|
|
|
|
|
|
device_status.mouse_analog_values[index] = analog_value;
|
|
|
|
|
|
|
|
if (is_configuring) {
|
|
|
|
device_status.mouse_position_state = {};
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (index) {
|
|
|
|
case Settings::NativeMouseWheel::X:
|
|
|
|
device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
|
|
|
|
break;
|
|
|
|
case Settings::NativeMouseWheel::Y:
|
|
|
|
device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
}
|
|
|
|
|
2021-12-14 02:09:28 +00:00
|
|
|
void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callback) {
|
2021-11-14 20:09:29 +00:00
|
|
|
std::lock_guard lock{mutex};
|
2021-11-15 03:28:38 +00:00
|
|
|
const auto touch_value = TransformToTouch(callback);
|
2021-11-14 20:09:29 +00:00
|
|
|
|
2021-11-15 03:28:38 +00:00
|
|
|
device_status.mouse_stick_value = touch_value;
|
2021-11-14 20:09:29 +00:00
|
|
|
|
|
|
|
if (is_configuring) {
|
|
|
|
device_status.mouse_position_state = {};
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-15 03:28:38 +00:00
|
|
|
device_status.mouse_position_state.x = touch_value.x.value;
|
|
|
|
device_status.mouse_position_state.y = touch_value.y.value;
|
2021-11-14 20:09:29 +00:00
|
|
|
|
|
|
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
|
|
|
}
|
|
|
|
|
2021-10-17 05:33:00 +00:00
|
|
|
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
|
|
|
return device_status.keyboard_values;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
|
|
|
|
return device_status.keyboard_moddifier_values;
|
|
|
|
}
|
|
|
|
|
2021-09-21 00:43:16 +00:00
|
|
|
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
|
|
|
|
return device_status.mouse_button_values;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
|
|
|
return device_status.keyboard_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
|
|
|
|
return device_status.keyboard_moddifier_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
MouseButton EmulatedDevices::GetMouseButtons() const {
|
|
|
|
return device_status.mouse_button_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
MousePosition EmulatedDevices::GetMousePosition() const {
|
|
|
|
return device_status.mouse_position_state;
|
|
|
|
}
|
|
|
|
|
2021-11-15 03:28:38 +00:00
|
|
|
AnalogStickState EmulatedDevices::GetMouseWheel() const {
|
2021-11-14 20:09:29 +00:00
|
|
|
return device_status.mouse_wheel_state;
|
|
|
|
}
|
|
|
|
|
2021-09-21 00:43:16 +00:00
|
|
|
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
2021-10-23 04:04:06 +00:00
|
|
|
for (const auto& poller_pair : callback_list) {
|
2021-09-21 00:43:16 +00:00
|
|
|
const InterfaceUpdateCallback& poller = poller_pair.second;
|
|
|
|
if (poller.on_change) {
|
|
|
|
poller.on_change(type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
|
|
|
|
std::lock_guard lock{mutex};
|
2021-12-14 02:09:28 +00:00
|
|
|
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
|
2021-09-21 00:43:16 +00:00
|
|
|
return last_callback_key++;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmulatedDevices::DeleteCallback(int key) {
|
|
|
|
std::lock_guard lock{mutex};
|
2021-11-01 20:17:53 +00:00
|
|
|
const auto& iterator = callback_list.find(key);
|
|
|
|
if (iterator == callback_list.end()) {
|
2021-09-21 00:43:16 +00:00
|
|
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
|
|
|
|
return;
|
|
|
|
}
|
2021-11-01 20:17:53 +00:00
|
|
|
callback_list.erase(iterator);
|
2021-09-21 00:43:16 +00:00
|
|
|
}
|
|
|
|
} // namespace Core::HID
|