// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included #include #include #include "core/hid/emulated_devices.h" #include "core/hid/input_converter.h" namespace Core::HID { EmulatedDevices::EmulatedDevices() = default; EmulatedDevices::~EmulatedDevices() = default; void EmulatedDevices::ReloadFromSettings() { ReloadInput(); } void EmulatedDevices::ReloadInput() { // If you load any device here add the equivalent to the UnloadInput() function std::size_t key_index = 0; for (auto& mouse_device : mouse_button_devices) { Common::ParamPackage mouse_params; mouse_params.Set("engine", "mouse"); mouse_params.Set("button", static_cast(key_index)); mouse_device = Common::Input::CreateDevice(mouse_params); key_index++; } mouse_stick_device = Common::Input::CreateDeviceFromString( "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(key_index)); mouse_device = Common::Input::CreateDevice(mouse_params); key_index++; } key_index = 0; 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(key_index)); keyboard_params.Set("port", 1); keyboard_params.Set("pad", 0); keyboard_device = Common::Input::CreateDevice(keyboard_params); key_index++; } 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(key_index)); keyboard_params.Set("port", 1); keyboard_params.Set("pad", 1); keyboard_device = Common::Input::CreateDevice(keyboard_params); key_index++; } for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { if (!mouse_button_devices[index]) { continue; } Common::Input::InputCallback button_callback{ [this, index](Common::Input::CallbackStatus callback) { SetMouseButton(callback, index); }}; mouse_button_devices[index]->SetCallback(button_callback); } for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) { if (!mouse_analog_devices[index]) { continue; } Common::Input::InputCallback button_callback{ [this, index](Common::Input::CallbackStatus callback) { SetMouseAnalog(callback, index); }}; mouse_analog_devices[index]->SetCallback(button_callback); } if (mouse_stick_device) { Common::Input::InputCallback button_callback{ [this](Common::Input::CallbackStatus callback) { SetMouseStick(callback); }}; mouse_stick_device->SetCallback(button_callback); } for (std::size_t index = 0; index < keyboard_devices.size(); ++index) { if (!keyboard_devices[index]) { continue; } Common::Input::InputCallback button_callback{ [this, index](Common::Input::CallbackStatus callback) { SetKeyboardButton(callback, index); }}; keyboard_devices[index]->SetCallback(button_callback); } for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) { if (!keyboard_modifier_devices[index]) { continue; } Common::Input::InputCallback button_callback{ [this, index](Common::Input::CallbackStatus callback) { SetKeyboardModifier(callback, index); }}; keyboard_modifier_devices[index]->SetCallback(button_callback); } } void EmulatedDevices::UnloadInput() { for (auto& button : mouse_button_devices) { button.reset(); } for (auto& analog : mouse_analog_devices) { analog.reset(); } mouse_stick_device.reset(); 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(); } void EmulatedDevices::SetKeyboardButton(Common::Input::CallbackStatus callback, std::size_t index) { 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; // Update button status with current status 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::Keyboard); return; } // Index should be converted from NativeKeyboard to KeyboardKeyIndex UpdateKey(index, current_status.value); TriggerOnChange(DeviceTriggerType::Keyboard); } void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) { constexpr std::size_t KEYS_PER_BYTE = 8; auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE]; const u8 mask = static_cast(1 << (key_index % KEYS_PER_BYTE)); if (status) { entry = entry | mask; } else { entry = static_cast(entry & ~mask); } } void EmulatedDevices::SetKeyboardModifier(Common::Input::CallbackStatus callback, std::size_t index) { 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); } void EmulatedDevices::SetMouseButton(Common::Input::CallbackStatus callback, std::size_t index) { 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); } void EmulatedDevices::SetMouseAnalog(Common::Input::CallbackStatus callback, std::size_t index) { 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; } LOG_ERROR(Input, "{}", analog_value.value); switch (index) { case Settings::NativeMouseWheel::X: device_status.mouse_wheel_state.x = static_cast(analog_value.value); break; case Settings::NativeMouseWheel::Y: device_status.mouse_wheel_state.y = static_cast(analog_value.value); break; } TriggerOnChange(DeviceTriggerType::Mouse); } void EmulatedDevices::SetMouseStick(Common::Input::CallbackStatus callback) { std::lock_guard lock{mutex}; const auto stick_value = TransformToStick(callback); device_status.mouse_stick_value = stick_value; if (is_configuring) { device_status.mouse_position_state = {}; TriggerOnChange(DeviceTriggerType::Mouse); return; } device_status.mouse_position_state.x = stick_value.x.value; device_status.mouse_position_state.y = stick_value.y.value; TriggerOnChange(DeviceTriggerType::Mouse); } KeyboardValues EmulatedDevices::GetKeyboardValues() const { return device_status.keyboard_values; } KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const { return device_status.keyboard_moddifier_values; } 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; } AnalogStickState EmulatedDevices::GetMouseDeltaWheel() const { return device_status.mouse_wheel_state; } void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { for (const auto& poller_pair : callback_list) { 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}; callback_list.insert_or_assign(last_callback_key, update_callback); return last_callback_key++; } void EmulatedDevices::DeleteCallback(int key) { std::lock_guard lock{mutex}; const auto& iterator = callback_list.find(key); if (iterator == callback_list.end()) { LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); return; } callback_list.erase(iterator); } } // namespace Core::HID