From 4868f595ca0cb214b0ad626a5fe024b93f5295b1 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 13 Aug 2022 09:56:04 +0200 Subject: [PATCH] evdev: add motion device --- rpcs3/Emu/Io/Null/NullPadHandler.h | 8 +- rpcs3/Emu/Io/PadHandler.cpp | 145 ++++-- rpcs3/Emu/Io/PadHandler.h | 51 ++- rpcs3/Emu/Io/pad_config.cpp | 1 + rpcs3/Emu/Io/pad_config.h | 17 + rpcs3/Emu/Io/pad_types.h | 46 +- rpcs3/Input/ds3_pad_handler.cpp | 11 +- rpcs3/Input/ds3_pad_handler.h | 4 +- rpcs3/Input/ds4_pad_handler.cpp | 11 +- rpcs3/Input/ds4_pad_handler.h | 4 +- rpcs3/Input/dualsense_pad_handler.cpp | 11 +- rpcs3/Input/dualsense_pad_handler.h | 4 +- rpcs3/Input/evdev_joystick_handler.cpp | 452 +++++++++++++++---- rpcs3/Input/evdev_joystick_handler.h | 64 ++- rpcs3/Input/hid_pad_handler.cpp | 6 +- rpcs3/Input/hid_pad_handler.h | 2 +- rpcs3/Input/keyboard_pad_handler.cpp | 34 +- rpcs3/Input/keyboard_pad_handler.h | 5 +- rpcs3/Input/mm_joystick_handler.cpp | 36 +- rpcs3/Input/mm_joystick_handler.h | 2 +- rpcs3/Input/pad_thread.cpp | 4 +- rpcs3/Input/xinput_pad_handler.cpp | 16 +- rpcs3/Input/xinput_pad_handler.h | 6 +- rpcs3/rpcs3.vcxproj | 29 ++ rpcs3/rpcs3.vcxproj.filters | 21 + rpcs3/rpcs3qt/CMakeLists.txt | 2 + rpcs3/rpcs3qt/pad_device_info.h | 12 + rpcs3/rpcs3qt/pad_led_settings_dialog.h | 2 +- rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp | 288 ++++++++++++ rpcs3/rpcs3qt/pad_motion_settings_dialog.h | 67 +++ rpcs3/rpcs3qt/pad_motion_settings_dialog.ui | 297 ++++++++++++ rpcs3/rpcs3qt/pad_settings_dialog.cpp | 189 +++++--- rpcs3/rpcs3qt/pad_settings_dialog.h | 16 +- rpcs3/rpcs3qt/pad_settings_dialog.ui | 38 +- rpcs3/rpcs3qt/tooltips.h | 1 + 35 files changed, 1587 insertions(+), 315 deletions(-) create mode 100644 rpcs3/rpcs3qt/pad_device_info.h create mode 100644 rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/pad_motion_settings_dialog.h create mode 100644 rpcs3/rpcs3qt/pad_motion_settings_dialog.ui diff --git a/rpcs3/Emu/Io/Null/NullPadHandler.h b/rpcs3/Emu/Io/Null/NullPadHandler.h index dcba40505924..22e5fdfc9346 100644 --- a/rpcs3/Emu/Io/Null/NullPadHandler.h +++ b/rpcs3/Emu/Io/Null/NullPadHandler.h @@ -52,14 +52,14 @@ class NullPadHandler final : public PadHandlerBase cfg->from_default(); } - std::vector ListDevices() override + std::vector list_devices() override { - std::vector nulllist; - nulllist.emplace_back("Default Null Device"); + std::vector nulllist; + nulllist.emplace_back("Default Null Device", false); return nulllist; } - bool bindPadToDevice(std::shared_ptr /*pad*/, const std::string& /*device*/, u8 /*player_id*/) override + bool bindPadToDevice(std::shared_ptr /*pad*/, u8 /*player_id*/) override { return true; } diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 50125db173d4..422c5b77a75c 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -112,19 +112,19 @@ s32 PadHandlerBase::MultipliedInput(s32 raw_value, s32 multiplier) } // Get new scaled value between 0 and 255 based on its minimum and maximum -float PadHandlerBase::ScaledInput(s32 raw_value, int minimum, int maximum) +f32 PadHandlerBase::ScaledInput(s32 raw_value, int minimum, int maximum, f32 range) { // value based on max range converted to [0, 1] - const float val = static_cast(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum)); - return 255.0f * val; + const f32 val = static_cast(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum)); + return range * val; } // Get new scaled value between -255 and 255 based on its minimum and maximum -float PadHandlerBase::ScaledInput2(s32 raw_value, int minimum, int maximum) +f32 PadHandlerBase::ScaledInput2(s32 raw_value, int minimum, int maximum, f32 range) { // value based on max range converted to [0, 1] - const float val = static_cast(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum)); - return (510.0f * val) - 255.0f; + const f32 val = static_cast(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum)); + return (2.0f * range * val) - range; } // Get normalized trigger value based on the range defined by a threshold @@ -140,7 +140,7 @@ u16 PadHandlerBase::NormalizeTriggerInput(u16 value, int threshold) const } else { - const s32 val = static_cast(static_cast(trigger_max) * (value - threshold) / (trigger_max - threshold)); + const s32 val = static_cast(static_cast(trigger_max) * (value - threshold) / (trigger_max - threshold)); return static_cast(ScaledInput(val, trigger_min, trigger_max)); } } @@ -154,7 +154,7 @@ u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 max return static_cast(0); } - const float val = static_cast(std::clamp(raw_value, 0, maximum)) / maximum; // value based on max range converted to [0, 1] + const f32 val = static_cast(std::clamp(raw_value, 0, maximum)) / maximum; // value based on max range converted to [0, 1] if (threshold <= 0) { @@ -162,7 +162,7 @@ u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 max } else { - const float thresh = static_cast(threshold) / maximum; // threshold converted to [0, 1] + const f32 thresh = static_cast(threshold) / maximum; // threshold converted to [0, 1] return static_cast(255.0f * std::min(1.0f, (val - thresh) / (1.0f - thresh))); } } @@ -186,14 +186,14 @@ u16 PadHandlerBase::NormalizeStickInput(u16 raw_value, int threshold, int multip // return is new x and y values in 0-255 range std::tuple PadHandlerBase::NormalizeStickDeadzone(s32 inX, s32 inY, u32 deadzone) const { - const float dz_range = deadzone / static_cast(std::abs(thumb_max)); // NOTE: thumb_max should be positive anyway + const f32 dz_range = deadzone / static_cast(std::abs(thumb_max)); // NOTE: thumb_max should be positive anyway - float X = inX / 255.0f; - float Y = inY / 255.0f; + f32 X = inX / 255.0f; + f32 Y = inY / 255.0f; if (dz_range > 0.f) { - const float mag = std::min(sqrtf(X * X + Y * Y), 1.f); + const f32 mag = std::min(sqrtf(X * X + Y * Y), 1.f); if (mag <= 0) { @@ -202,15 +202,15 @@ std::tuple PadHandlerBase::NormalizeStickDeadzone(s32 inX, s32 inY, u3 if (mag > dz_range) { - const float pos = std::lerp(0.13f, 1.f, (mag - dz_range) / (1 - dz_range)); - const float scale = pos / mag; + const f32 pos = std::lerp(0.13f, 1.f, (mag - dz_range) / (1 - dz_range)); + const f32 scale = pos / mag; X = X * scale; Y = Y * scale; } else { - const float pos = std::lerp(0.f, 0.13f, mag / dz_range); - const float scale = pos / mag; + const f32 pos = std::lerp(0.f, 0.13f, mag / dz_range); + const f32 scale = pos / mag; X = X * scale; Y = Y * scale; } @@ -231,7 +231,7 @@ u16 PadHandlerBase::Clamp0To1023(f32 input) } // input has to be [-1,1]. result will be [0,255] -u16 PadHandlerBase::ConvertAxis(float value) +u16 PadHandlerBase::ConvertAxis(f32 value) { return static_cast((value + 1.0)*(255.0 / 2.0)); } @@ -280,6 +280,11 @@ bool PadHandlerBase::has_rumble() const return b_has_rumble; } +bool PadHandlerBase::has_motion() const +{ + return b_has_motion; +} + bool PadHandlerBase::has_deadzones() const { return b_has_deadzones; @@ -338,8 +343,13 @@ void PadHandlerBase::get_next_button_press(const std::string& pad_id, const pad_ // Check for each button in our list if its corresponding (maybe remapped) button or axis was pressed. // Return the new value if the button was pressed (aka. its value was bigger than 0 or the defined threshold) - // Use a pair to get all the legally pressed buttons and use the one with highest value (prioritize first) - std::pair pressed_button = { 0, "" }; + // Get all the legally pressed buttons and use the one with highest value (prioritize first) + struct + { + u16 value = 0; + std::string name; + } pressed_button{}; + for (const auto& [keycode, name] : button_list) { const u16& value = data[keycode]; @@ -358,8 +368,10 @@ void PadHandlerBase::get_next_button_press(const std::string& pad_id, const pad_ blacklist.emplace_back(keycode); input_log.error("%s Calibration: Added key [ %d = %s ] to blacklist. Value = %d", m_type, keycode, name, value); } - else if (value > pressed_button.first) - pressed_button = { value, name }; + else if (value > pressed_button.value) + { + pressed_button = { .value = value, .name = name }; + } } } @@ -375,13 +387,48 @@ void PadHandlerBase::get_next_button_press(const std::string& pad_id, const pad_ if (callback) { - if (pressed_button.first > 0) - return callback(pressed_button.first, pressed_button.second, pad_id, battery_level, preview_values); + if (pressed_button.value > 0) + return callback(pressed_button.value, pressed_button.name, pad_id, battery_level, preview_values); else return callback(0, "", pad_id, battery_level, preview_values); } +} - return; +void PadHandlerBase::get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& /*sensors*/) +{ + if (!b_has_motion) + { + return; + } + + // Reset sensors + auto device = get_device(pad_id); + + const auto status = update_connection(device); + if (status == connection::disconnected) + { + if (fail_callback) + fail_callback(pad_id, std::move(preview_values)); + return; + } + + if (status == connection::no_data || !callback) + { + return; + } + + // Get the current motion values + std::shared_ptr pad = std::make_shared(m_type, 0, 0, 0); + pad->m_sensors.resize(preview_values.size(), AnalogSensor(0, 0, 0, 0, 0)); + pad_ensemble binding{pad, device, nullptr}; + get_extended_info(binding); + + for (usz i = 0; i < preview_values.size(); i++) + { + preview_values[i] = pad->m_sensors[i].m_value; + } + + callback(pad_id, std::move(preview_values)); } void PadHandlerBase::convert_stick_values(u16& x_out, u16& y_out, const s32& x_in, const s32& y_in, const s32& deadzone, const s32& padsquircling) const @@ -431,27 +478,33 @@ void PadHandlerBase::TranslateButtonPress(const std::shared_ptr& devi } } -bool PadHandlerBase::bindPadToDevice(std::shared_ptr pad, const std::string& device, u8 player_id) +bool PadHandlerBase::bindPadToDevice(std::shared_ptr pad, u8 player_id) { - if (!pad) + if (!pad || player_id >= g_cfg_input.player.size()) { return false; } - std::shared_ptr pad_device = get_device(device); + const cfg_player* player_config = g_cfg_input.player[player_id]; + if (!player_config) + { + return false; + } + + std::shared_ptr pad_device = get_device(player_config->device); if (!pad_device) { - input_log.error("PadHandlerBase::bindPadToDevice: no PadDevice found for device '%s'", device); + input_log.error("PadHandlerBase::bindPadToDevice: no PadDevice found for device '%s'", player_config->device.to_string()); return false; } - m_pad_configs[player_id].from_string(g_cfg_input.player[player_id]->config.to_string()); + m_pad_configs[player_id].from_string(player_config->config.to_string()); pad_device->config = &m_pad_configs[player_id]; pad_device->player_id = player_id; cfg_pad* config = pad_device->config; if (config == nullptr) { - input_log.error("PadHandlerBase::bindPadToDevice: no profile found for device %d '%s'", bindings.size(), device); + input_log.error("PadHandlerBase::bindPadToDevice: no profile found for device %d '%s'", m_bindings.size(), player_config->device.to_string()); return false; } @@ -505,15 +558,15 @@ bool PadHandlerBase::bindPadToDevice(std::shared_ptr pad, const std::string pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, mapping[button::rs_left], mapping[button::rs_right]); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, mapping[button::rs_down], mapping[button::rs_up]); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 399); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 512); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 0, 0, 0, DEFAULT_MOTION_X); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 0, 0, 0, DEFAULT_MOTION_Y); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 0, 0, 0, DEFAULT_MOTION_Z); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 0, 0, 0, DEFAULT_MOTION_G); pad->m_vibrateMotors.emplace_back(true, 0); pad->m_vibrateMotors.emplace_back(false, 0); - bindings.emplace_back(pad_device, pad); + m_bindings.emplace_back(pad, pad_device, nullptr); return true; } @@ -555,8 +608,11 @@ std::array PadHandlerBase::get_mapped return mapping; } -void PadHandlerBase::get_mapping(const std::shared_ptr& device, const std::shared_ptr& pad) +void PadHandlerBase::get_mapping(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + if (!device || !pad) return; @@ -638,10 +694,10 @@ void PadHandlerBase::get_mapping(const std::shared_ptr& device, const void PadHandlerBase::ThreadProc() { - for (usz i = 0; i < bindings.size(); ++i) + for (usz i = 0; i < m_bindings.size(); ++i) { - auto& device = bindings[i].first; - auto& pad = bindings[i].second; + auto& device = m_bindings[i].device; + auto& pad = m_bindings[i].pad; if (!device || !pad) continue; @@ -663,7 +719,10 @@ void PadHandlerBase::ThreadProc() } if (status == connection::no_data) + { + // TODO: don't skip entirely if buddy device has data continue; + } break; } @@ -683,8 +742,8 @@ void PadHandlerBase::ThreadProc() break; } - get_mapping(device, pad); - get_extended_info(device, pad); - apply_pad_data(device, pad); + get_mapping(m_bindings[i]); + get_extended_info(m_bindings[i]); + apply_pad_data(m_bindings[i]); } } diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 56bd97387010..66fe2a243757 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -20,10 +20,35 @@ class PadDevice u8 player_id{0}; }; +struct pad_ensemble +{ + std::shared_ptr pad; + std::shared_ptr device; + std::shared_ptr buddy_device; + + explicit pad_ensemble(std::shared_ptr _pad, std::shared_ptr _device, std::shared_ptr _buddy_device) + : pad(_pad), device(_device), buddy_device(_buddy_device) + {} +}; + +struct pad_list_entry +{ + std::string name; + bool is_buddy_only = false; + + explicit pad_list_entry(std::string _name, bool _is_buddy_only) + : name(_name), is_buddy_only(_is_buddy_only) + {} +}; + using pad_preview_values = std::array; using pad_callback = std::function; using pad_fail_callback = std::function; +using motion_preview_values = std::array; +using motion_callback = std::function; +using motion_fail_callback = std::function; + class PadHandlerBase { protected: @@ -68,7 +93,7 @@ class PadHandlerBase disconnected }; - static const u32 MAX_GAMEPADS = 7; + static constexpr u32 MAX_GAMEPADS = 7; std::array last_connection_status{{ false, false, false, false, false, false, false }}; @@ -82,10 +107,11 @@ class PadHandlerBase bool b_has_battery = false; bool b_has_deadzones = false; bool b_has_rumble = false; + bool b_has_motion = false; bool b_has_config = false; bool b_has_pressure_intensity_button = true; std::array m_pad_configs; - std::vector, std::shared_ptr>> bindings; + std::vector m_bindings; std::unordered_map button_list; std::vector blacklist; @@ -105,10 +131,10 @@ class PadHandlerBase static s32 MultipliedInput(s32 raw_value, s32 multiplier); // Get new scaled value between 0 and 255 based on its minimum and maximum - static float ScaledInput(s32 raw_value, int minimum, int maximum); + static f32 ScaledInput(s32 raw_value, int minimum, int maximum, f32 range = 255.0f); // Get new scaled value between -255 and 255 based on its minimum and maximum - static float ScaledInput2(s32 raw_value, int minimum, int maximum); + static f32 ScaledInput2(s32 raw_value, int minimum, int maximum, f32 range = 255.0f); // Get normalized trigger value based on the range defined by a threshold u16 NormalizeTriggerInput(u16 value, int threshold) const; @@ -128,7 +154,7 @@ class PadHandlerBase static u16 Clamp0To1023(f32 input); // input has to be [-1,1]. result will be [0,255] - static u16 ConvertAxis(float value); + static u16 ConvertAxis(f32 value); // The DS3, (and i think xbox controllers) give a 'square-ish' type response, so that the corners will give (almost)max x/y instead of the ~30x30 from a perfect circle // using a simple scale/sensitivity increase would *work* although it eats a chunk of our usable range in exchange @@ -151,6 +177,7 @@ class PadHandlerBase usz max_devices() const; bool has_config() const; bool has_rumble() const; + bool has_motion() const; bool has_deadzones() const; bool has_led() const; bool has_rgb() const; @@ -167,13 +194,15 @@ class PadHandlerBase virtual void SetPadData(const std::string& /*padId*/, u8 /*player_id*/, u32 /*largeMotor*/, u32 /*smallMotor*/, s32 /*r*/, s32 /*g*/, s32 /*b*/, bool /*battery_led*/, u32 /*battery_led_brightness*/) {} virtual u32 get_battery_level(const std::string& /*padId*/) { return 0; } // Return list of devices for that handler - virtual std::vector ListDevices() = 0; + virtual std::vector list_devices() = 0; // Callback called during pad_thread::ThreadFunc virtual void ThreadProc(); // Binds a Pad to a device - virtual bool bindPadToDevice(std::shared_ptr pad, const std::string& device, u8 player_id); - virtual void init_config(cfg_pad* /*cfg*/) = 0; + virtual bool bindPadToDevice(std::shared_ptr pad, u8 player_id); + virtual void init_config(cfg_pad* cfg) = 0; virtual void get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist, const std::vector& buttons = {}); + virtual void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors); + virtual std::unordered_map get_motion_axis_list() const { return {}; } private: virtual std::shared_ptr get_device(const std::string& /*device*/) { return nullptr; } @@ -182,14 +211,14 @@ class PadHandlerBase virtual bool get_is_left_stick(u64 /*keyCode*/) { return false; } virtual bool get_is_right_stick(u64 /*keyCode*/) { return false; } virtual PadHandlerBase::connection update_connection(const std::shared_ptr& /*device*/) { return connection::disconnected; } - virtual void get_extended_info(const std::shared_ptr& /*device*/, const std::shared_ptr& /*pad*/) {} - virtual void apply_pad_data(const std::shared_ptr& /*device*/, const std::shared_ptr& /*pad*/) {} + virtual void get_extended_info(const pad_ensemble& /*binding*/) {} + virtual void apply_pad_data(const pad_ensemble& /*binding*/) {} virtual std::unordered_map get_button_values(const std::shared_ptr& /*device*/) { return {}; } virtual pad_preview_values get_preview_values(const std::unordered_map& /*data*/) { return {}; } protected: virtual std::array get_mapped_key_codes(const std::shared_ptr& device, const cfg_pad* cfg); - virtual void get_mapping(const std::shared_ptr& device, const std::shared_ptr& pad); + virtual void get_mapping(const pad_ensemble& binding); void TranslateButtonPress(const std::shared_ptr& device, u64 keyCode, bool& pressed, u16& val, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false); void init_configs(); }; diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index 37ae06f0964b..c28679b984ed 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -44,6 +44,7 @@ bool cfg_input::load(const std::string& title_id, const std::string& profile, bo input_log.notice("Pad profile empty. Adding default keyboard pad handler"); player[0]->handler.from_string(fmt::format("%s", pad_handler::keyboard)); player[0]->device.from_string(pad::keyboard_device_name.data()); + player[0]->buddy_device.from_string(""sv); return false; } diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index cec5e11e77c4..5fc04e6f625c 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -11,6 +11,15 @@ namespace pad constexpr static std::string_view keyboard_device_name = "Keyboard"; } +struct cfg_sensor final : cfg::node +{ + cfg_sensor(node* owner, const std::string& name) : cfg::node(owner, name) {} + + cfg::string axis{ this, "Axis", "" }; + cfg::_bool mirrored{ this, "Mirrored", false }; + cfg::_int<-1023, 1023> shift{ this, "Shift", 0 }; +}; + struct cfg_pad final : cfg::node { cfg_pad() {}; @@ -42,6 +51,11 @@ struct cfg_pad final : cfg::node cfg::string l2{ this, "L2", "" }; cfg::string l3{ this, "L3", "" }; + cfg_sensor motion_sensor_x{ this, "Motion Sensor X" }; + cfg_sensor motion_sensor_y{ this, "Motion Sensor Y" }; + cfg_sensor motion_sensor_z{ this, "Motion Sensor Z" }; + cfg_sensor motion_sensor_g{ this, "Motion Sensor G" }; + cfg::string pressure_intensity_button{ this, "Pressure Intensity Button", "" }; cfg::uint<0, 100> pressure_intensity{ this, "Pressure Intensity Percent", 50 }; @@ -88,8 +102,11 @@ struct cfg_player final : cfg::node cfg_player(node* owner, const std::string& name, pad_handler type) : cfg::node(owner, name), def_handler(type) {} cfg::_enum handler{ this, "Handler", def_handler }; + cfg::string device{ this, "Device", handler.to_string() }; cfg_pad config{ this, "Config" }; + + cfg::string buddy_device{ this, "Buddy Device", handler.to_string() }; }; struct cfg_input final : cfg::node diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index 80ec4be05e67..1fde10bd597e 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -165,6 +165,10 @@ enum CELL_MAX_PADS = 127, }; +static constexpr u16 DEFAULT_MOTION_X = 512; +static constexpr u16 DEFAULT_MOTION_Y = 399; +static constexpr u16 DEFAULT_MOTION_Z = 512; +static constexpr u16 DEFAULT_MOTION_G = 512; constexpr u32 special_button_offset = 666; // Must not conflict with other CELL offsets like ButtonDataOffset @@ -175,9 +179,9 @@ enum special_button_value struct Button { - u32 m_offset; - u32 m_keyCode; - u32 m_outKeyCode; + u32 m_offset = 0; + u32 m_keyCode = 0; + u32 m_outKeyCode = 0; u16 m_value = 0; bool m_pressed = false; @@ -214,34 +218,40 @@ struct Button struct AnalogStick { - u32 m_offset; - u32 m_keyCodeMin; - u32 m_keyCodeMax; + u32 m_offset = 0; + u32 m_keyCodeMin = 0; + u32 m_keyCodeMax = 0; u16 m_value = 128; AnalogStick(u32 offset, u32 keyCodeMin, u32 keyCodeMax) : m_offset(offset) , m_keyCodeMin(keyCodeMin) , m_keyCodeMax(keyCodeMax) - { - } + {} }; struct AnalogSensor { - u32 m_offset; - u16 m_value; - - AnalogSensor(u32 offset, u16 value) + u32 m_offset = 0; + u32 m_keyCode = 0; + b8 m_mirrored = false; + s16 m_shift = 0; + u16 m_value = 0; + + AnalogSensor() {} + AnalogSensor(u32 offset, u32 key_code, b8 mirrored, s16 shift, u16 value) : m_offset(offset) + , m_keyCode(key_code) + , m_mirrored(mirrored) + , m_shift(shift) , m_value(value) {} }; struct VibrateMotor { - bool m_isLargeMotor; - u16 m_value; + bool m_isLargeMotor = false; + u16 m_value = 0; VibrateMotor(bool largeMotor, u16 value) : m_isLargeMotor(largeMotor) @@ -303,10 +313,10 @@ struct Pad // Except for these...0-1023 // ~399 on sensor y is a level non moving controller - u16 m_sensor_x{512}; - u16 m_sensor_y{399}; - u16 m_sensor_z{512}; - u16 m_sensor_g{512}; + u16 m_sensor_x{DEFAULT_MOTION_X}; + u16 m_sensor_y{DEFAULT_MOTION_Y}; + u16 m_sensor_z{DEFAULT_MOTION_Z}; + u16 m_sensor_g{DEFAULT_MOTION_G}; bool ldd{false}; u8 ldd_data[132] = {}; diff --git a/rpcs3/Input/ds3_pad_handler.cpp b/rpcs3/Input/ds3_pad_handler.cpp index 5b3f814c1074..39d848bcf8b1 100644 --- a/rpcs3/Input/ds3_pad_handler.cpp +++ b/rpcs3/Input/ds3_pad_handler.cpp @@ -81,6 +81,7 @@ ds3_pad_handler::ds3_pad_handler() // set capabilities b_has_config = true; b_has_rumble = true; + b_has_motion = true; b_has_deadzones = true; b_has_battery = true; b_has_led = true; @@ -445,8 +446,11 @@ pad_preview_values ds3_pad_handler::get_preview_values(const std::unordered_map< }; } -void ds3_pad_handler::get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) +void ds3_pad_handler::get_extended_info(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + ds3_device* ds3dev = static_cast(device.get()); if (!ds3dev || !pad) return; @@ -561,8 +565,11 @@ PadHandlerBase::connection ds3_pad_handler::update_connection(const std::shared_ return connection::connected; } -void ds3_pad_handler::apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) +void ds3_pad_handler::apply_pad_data(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + ds3_device* dev = static_cast(device.get()); if (!dev || !dev->hidDevice || !dev->config || !pad) return; diff --git a/rpcs3/Input/ds3_pad_handler.h b/rpcs3/Input/ds3_pad_handler.h index 2fc351db50ae..af61acfbf869 100644 --- a/rpcs3/Input/ds3_pad_handler.h +++ b/rpcs3/Input/ds3_pad_handler.h @@ -94,8 +94,8 @@ class ds3_pad_handler final : public hid_pad_handler bool get_is_left_stick(u64 keyCode) override; bool get_is_right_stick(u64 keyCode) override; PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; - void get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) override; - void apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; std::unordered_map get_button_values(const std::shared_ptr& device) override; pad_preview_values get_preview_values(const std::unordered_map& data) override; }; diff --git a/rpcs3/Input/ds4_pad_handler.cpp b/rpcs3/Input/ds4_pad_handler.cpp index 3f29b2108950..e1f98fd62468 100644 --- a/rpcs3/Input/ds4_pad_handler.cpp +++ b/rpcs3/Input/ds4_pad_handler.cpp @@ -119,6 +119,7 @@ ds4_pad_handler::ds4_pad_handler() // set capabilities b_has_config = true; b_has_rumble = true; + b_has_motion = true; b_has_deadzones = true; b_has_led = true; b_has_rgb = true; @@ -831,8 +832,11 @@ PadHandlerBase::connection ds4_pad_handler::update_connection(const std::shared_ return connection::connected; } -void ds4_pad_handler::get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) +void ds4_pad_handler::get_extended_info(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + DS4Device* ds4_device = static_cast(device.get()); if (!ds4_device || !pad) return; @@ -869,8 +873,11 @@ void ds4_pad_handler::get_extended_info(const std::shared_ptr& device pad->m_sensors[3].m_value = Clamp0To1023(gyroX); } -void ds4_pad_handler::apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) +void ds4_pad_handler::apply_pad_data(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + DS4Device* ds4_dev = static_cast(device.get()); if (!ds4_dev || !ds4_dev->hidDevice || !ds4_dev->config || !pad) return; diff --git a/rpcs3/Input/ds4_pad_handler.h b/rpcs3/Input/ds4_pad_handler.h index 816eda727ab1..d4e21e7f1456 100644 --- a/rpcs3/Input/ds4_pad_handler.h +++ b/rpcs3/Input/ds4_pad_handler.h @@ -74,8 +74,8 @@ class ds4_pad_handler final : public hid_pad_handler bool get_is_left_stick(u64 keyCode) override; bool get_is_right_stick(u64 keyCode) override; PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; - void get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) override; - void apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; std::unordered_map get_button_values(const std::shared_ptr& device) override; pad_preview_values get_preview_values(const std::unordered_map& data) override; }; diff --git a/rpcs3/Input/dualsense_pad_handler.cpp b/rpcs3/Input/dualsense_pad_handler.cpp index ccc9a5886932..c647f1cafbc8 100644 --- a/rpcs3/Input/dualsense_pad_handler.cpp +++ b/rpcs3/Input/dualsense_pad_handler.cpp @@ -137,6 +137,7 @@ dualsense_pad_handler::dualsense_pad_handler() // Set capabilities b_has_config = true; b_has_rumble = true; + b_has_motion = true; b_has_deadzones = true; b_has_led = true; b_has_rgb = true; @@ -625,8 +626,11 @@ PadHandlerBase::connection dualsense_pad_handler::update_connection(const std::s return connection::connected; } -void dualsense_pad_handler::get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) +void dualsense_pad_handler::get_extended_info(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + DualSenseDevice* dualsense_device = static_cast(device.get()); if (!dualsense_device || !pad) return; @@ -1013,8 +1017,11 @@ int dualsense_pad_handler::send_output_report(DualSenseDevice* device) } } -void dualsense_pad_handler::apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) +void dualsense_pad_handler::apply_pad_data(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + DualSenseDevice* dualsense_dev = static_cast(device.get()); if (!dualsense_dev || !dualsense_dev->hidDevice || !dualsense_dev->config || !pad) return; diff --git a/rpcs3/Input/dualsense_pad_handler.h b/rpcs3/Input/dualsense_pad_handler.h index 39f3c2951901..b661a0e91622 100644 --- a/rpcs3/Input/dualsense_pad_handler.h +++ b/rpcs3/Input/dualsense_pad_handler.h @@ -87,6 +87,6 @@ class dualsense_pad_handler final : public hid_pad_handler PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; std::unordered_map get_button_values(const std::shared_ptr& device) override; pad_preview_values get_preview_values(const std::unordered_map& data) override; - void get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) override; - void apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; }; diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 04bd0f228bd5..9d6d22c674c6 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -34,6 +34,7 @@ evdev_joystick_handler::evdev_joystick_handler() // set capabilities b_has_config = true; b_has_rumble = true; + b_has_motion = true; b_has_deadzones = true; m_trigger_threshold = trigger_max / 2; @@ -44,7 +45,7 @@ evdev_joystick_handler::evdev_joystick_handler() evdev_joystick_handler::~evdev_joystick_handler() { - Close(); + close_devices(); } void evdev_joystick_handler::init_config(cfg_pad* cfg) @@ -78,6 +79,11 @@ void evdev_joystick_handler::init_config(cfg_pad* cfg) cfg->l2.def = axis_list.at(ABS_Z); cfg->l3.def = button_list.at(BTN_THUMBL); + cfg->motion_sensor_x.axis.def = motion_axis_list.at(ABS_X); + cfg->motion_sensor_y.axis.def = motion_axis_list.at(ABS_Y); + cfg->motion_sensor_z.axis.def = motion_axis_list.at(ABS_Z); + cfg->motion_sensor_g.axis.def = motion_axis_list.at(ABS_RX); + cfg->pressure_intensity_button.def = button_list.at(NO_BUTTON); // Set default misc variables @@ -92,6 +98,11 @@ void evdev_joystick_handler::init_config(cfg_pad* cfg) cfg->from_default(); } +std::unordered_map evdev_joystick_handler::get_motion_axis_list() const +{ + return motion_axis_list; +} + bool evdev_joystick_handler::Init() { if (m_is_init) @@ -124,11 +135,14 @@ std::string evdev_joystick_handler::get_device_name(const libevdev* dev) std::string name = libevdev_get_name(dev); const auto unique = libevdev_get_uniq(dev); - if (name == "" && unique != nullptr) - name = unique; + if (name.empty()) + { + if (unique) + name = unique; - if (name == "") - name = "Unknown Device"; + if (name.empty()) + name = "Unknown Device"; + } return name; } @@ -180,31 +194,34 @@ bool evdev_joystick_handler::update_device(const std::shared_ptr& dev return true; } -void evdev_joystick_handler::update_devs() +void evdev_joystick_handler::close_devices() { - for (auto& binding : bindings) + const auto free_device = [](EvdevDevice* evdev_device) + { + if (evdev_device && evdev_device->device) + { + const int fd = libevdev_get_fd(evdev_device->device); + if (evdev_device->effect_id != -1) + ioctl(fd, EVIOCRMFF, evdev_device->effect_id); + libevdev_free(evdev_device->device); + close(fd); + } + }; + + for (auto& binding : m_bindings) { - update_device(binding.first); + free_device(static_cast(binding.device.get())); + free_device(static_cast(binding.buddy_device.get())); } -} -void evdev_joystick_handler::Close() -{ - for (auto& binding : bindings) + for (auto [name, device] : m_settings_added) { - EvdevDevice* evdev_device = static_cast(binding.first.get()); - if (evdev_device) - { - auto& dev = evdev_device->device; - if (dev != nullptr) - { - const int fd = libevdev_get_fd(dev); - if (evdev_device->effect_id != -1) - ioctl(fd, EVIOCRMFF, evdev_device->effect_id); - libevdev_free(dev); - close(fd); - } - } + free_device(static_cast(device.get())); + } + + for (auto [name, device] : m_motion_settings_added) + { + free_device(static_cast(device.get())); } } @@ -266,17 +283,15 @@ std::unordered_map> evdev_joystick_handler::GetButtonV std::shared_ptr evdev_joystick_handler::get_evdev_device(const std::string& device) { // Add device if not yet present - const int pad_index = add_device(device, nullptr, true); - if (pad_index < 0) + std::shared_ptr evdev_device = add_device(device, true); + if (!evdev_device) return nullptr; - auto dev = bindings[pad_index]; - // Check if our device is connected - if (!update_device(dev.first)) + if (!update_device(evdev_device)) return nullptr; - return std::static_pointer_cast(dev.first); + return evdev_device; } void evdev_joystick_handler::get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist, const std::vector& buttons) @@ -286,7 +301,7 @@ void evdev_joystick_handler::get_next_button_press(const std::string& padId, con // Get our evdev device auto device = get_evdev_device(padId); - if (!device || device->device == nullptr) + if (!device || !device->device) { if (fail_callback) fail_callback(padId); @@ -351,7 +366,7 @@ void evdev_joystick_handler::get_next_button_press(const std::string& padId, con { u16 value = 0; std::string name; - } pressed_button; + } pressed_button{}; for (const auto& [code, name] : button_list) { @@ -447,6 +462,62 @@ void evdev_joystick_handler::get_next_button_press(const std::string& padId, con } } +void evdev_joystick_handler::get_motion_sensors(const std::string& padId, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors) +{ + // Add device if not yet present + auto device = add_motion_device(padId, true); + if (!device || !update_device(device) || !device->device) + { + if (fail_callback) + fail_callback(padId, std::move(preview_values)); + return; + } + + auto& dev = device->device; + + // Try to fetch all new events from the joystick. + bool is_dirty = false; + int ret = LIBEVDEV_READ_STATUS_SUCCESS; + while (ret >= 0) + { + input_event evt; + + if (ret == LIBEVDEV_READ_STATUS_SYNC) + { + // Grab any pending sync event. + ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_SYNC, &evt); + } + else + { + ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &evt); + } + + if (ret == LIBEVDEV_READ_STATUS_SUCCESS && evt.type == EV_ABS) + { + for (usz i = 0; i < sensors.size(); i++) + { + const AnalogSensor& sensor = sensors[i]; + + if (sensor.m_keyCode != evt.code) + continue; + + preview_values[i] = get_sensor_value(dev, sensor, evt);; + is_dirty = true; + } + } + } + + if (ret < 0) + { + // -EAGAIN signifies no available events, not an actual *error*. + if (ret != -EAGAIN) + evdev_log.error("Failed to read latest event from motion device: %s [errno %d]", strerror(-ret), -ret); + } + + if (callback && is_dirty) + callback(padId, std::move(preview_values)); +} + // https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp // https://github.com/reicast/reicast-emulator/blob/master/core/linux-dist/evdev.cpp // http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf @@ -596,21 +667,19 @@ u32 evdev_joystick_handler::GetButtonInfo(const input_event& evt, const std::sha } } -std::vector evdev_joystick_handler::ListDevices() +std::vector evdev_joystick_handler::list_devices() { Init(); std::unordered_map unique_names; - std::vector evdev_joystick_list; - fs::dir devdir{"/dev/input/"}; - fs::dir_entry et; + std::vector evdev_joystick_list; - while (devdir.read(et)) + for (auto&& et : fs::dir{"/dev/input/"}) { // Check if the entry starts with event (a 5-letter word) - if (et.name.size() > 5 && et.name.compare(0, 5, "event") == 0) + if (et.name.size() > 5 && et.name.starts_with("event")) { - const int fd = open(("/dev/input/" + et.name).c_str(), O_RDWR | O_NONBLOCK); + const int fd = open(("/dev/input/" + et.name).c_str(), O_RDONLY | O_NONBLOCK); struct libevdev* dev = NULL; const int rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) @@ -622,19 +691,32 @@ std::vector evdev_joystick_handler::ListDevices() close(fd); continue; } - if (libevdev_has_event_type(dev, EV_KEY) && - libevdev_has_event_type(dev, EV_ABS)) + + if (libevdev_has_event_type(dev, EV_ABS)) { - // It's a joystick. - std::string name = get_device_name(dev); + bool is_motion_device = false; + bool is_pad_device = libevdev_has_event_type(dev, EV_KEY); - if (unique_names.find(name) == unique_names.end()) - unique_names.emplace(name, 1); - else - name = fmt::format("%d. %s", ++unique_names[name], name); + if (!is_pad_device) + { + // Check if it's a motion device. + is_motion_device = libevdev_has_property(dev, INPUT_PROP_ACCELEROMETER); + } + + if (is_pad_device || is_motion_device) + { + // It's a joystick or motion device. + std::string name = get_device_name(dev); + + if (unique_names.find(name) == unique_names.end()) + unique_names.emplace(name, 1); + else + name = fmt::format("%d. %s", ++unique_names[name], name); - evdev_joystick_list.push_back(name); + evdev_joystick_list.emplace_back(name, is_motion_device); + } } + libevdev_free(dev); close(fd); } @@ -642,19 +724,18 @@ std::vector evdev_joystick_handler::ListDevices() return evdev_joystick_list; } -int evdev_joystick_handler::add_device(const std::string& device, const std::shared_ptr& pad, bool in_settings) +std::shared_ptr evdev_joystick_handler::add_device(const std::string& device, bool in_settings) { if (in_settings && m_settings_added.count(device)) return m_settings_added.at(device); // Now we need to find the device with the same name, and make sure not to grab any duplicates. std::unordered_map unique_names; - fs::dir devdir{ "/dev/input/" }; - fs::dir_entry et; - while (devdir.read(et)) + + for (auto&& et : fs::dir{"/dev/input/"}) { // Check if the entry starts with event (a 5-letter word) - if (et.name.size() > 5 && et.name.compare(0, 5, "event") == 0) + if (et.name.size() > 5 && et.name.starts_with("event")) { const std::string path = "/dev/input/" + et.name; const int fd = open(path.c_str(), O_RDWR | O_NONBLOCK); @@ -677,26 +758,16 @@ int evdev_joystick_handler::add_device(const std::string& device, const std::sha else name = fmt::format("%d. %s", ++unique_names[name], name); - if (libevdev_has_event_type(dev, EV_KEY) && - libevdev_has_event_type(dev, EV_ABS) && - name == device) + if (name == device && + libevdev_has_event_type(dev, EV_KEY) && + libevdev_has_event_type(dev, EV_ABS)) { - // It's a joystick. Now let's make sure we don't already have this one. - if (std::any_of(bindings.begin(), bindings.end(), [&path](std::pair, std::shared_ptr> binding) - { - EvdevDevice* device = static_cast(binding.first.get()); - return device && path == device->path; - })) - { - libevdev_free(dev); - close(fd); - continue; - } + // It's a joystick. if (in_settings) { - m_dev = std::make_shared(); - m_settings_added[device] = bindings.size(); + m_dev = std::make_shared(); + m_settings_added[device] = m_dev; // Let's log axis information while we are in the settings in order to identify problems more easily. for (const auto& [code, axis_name] : axis_list) @@ -704,8 +775,8 @@ int evdev_joystick_handler::add_device(const std::string& device, const std::sha if (const input_absinfo *info = libevdev_get_abs_info(dev, code)) { const auto code_name = libevdev_event_code_get_name(EV_ABS, code); - evdev_log.notice("Axis info for %s: %s (%s) => minimum=%d, maximum=%d, fuzz=%d, resolution=%d", - name, code_name, axis_name, info->minimum, info->maximum, info->fuzz, info->resolution); + evdev_log.notice("Axis info for %s: %s (%s) => minimum=%d, maximum=%d, fuzz=%d, flat=%d, resolution=%d", + name, code_name, axis_name, info->minimum, info->maximum, info->fuzz, info->flat, info->resolution); } } } @@ -714,14 +785,93 @@ int evdev_joystick_handler::add_device(const std::string& device, const std::sha m_dev->device = dev; m_dev->path = path; m_dev->has_rumble = libevdev_has_event_type(dev, EV_FF); - bindings.emplace_back(m_dev, pad); - return bindings.size() - 1; + m_dev->has_motion = libevdev_has_property(dev, INPUT_PROP_ACCELEROMETER); + + evdev_log.notice("Capability info for %s: rumble=%d, motion=%d", name, m_dev->has_rumble, m_dev->has_motion); + + return m_dev; } libevdev_free(dev); close(fd); } } - return -1; + return nullptr; +} + +std::shared_ptr evdev_joystick_handler::add_motion_device(const std::string& device, bool in_settings) +{ + if (device.empty()) + return nullptr; + + if (in_settings && m_motion_settings_added.count(device)) + return m_motion_settings_added.at(device); + + // Now we need to find the device with the same name, and make sure not to grab any duplicates. + std::unordered_map unique_names; + + for (auto&& et : fs::dir{"/dev/input/"}) + { + // Check if the entry starts with event (a 5-letter word) + if (et.name.size() > 5 && et.name.starts_with("event")) + { + const std::string path = "/dev/input/" + et.name; + const int fd = open(path.c_str(), O_RDWR | O_NONBLOCK); + struct libevdev* dev = NULL; + const int rc = libevdev_new_from_fd(fd, &dev); + if (rc < 0) + { + // If it's just a bad file descriptor, don't bother logging, but otherwise, log it. + if (rc != -9) + evdev_log.warning("Failed to connect to device at %s, the error was: %s", path, strerror(-rc)); + libevdev_free(dev); + close(fd); + continue; + } + + std::string name = get_device_name(dev); + + if (unique_names.find(name) == unique_names.end()) + unique_names.emplace(name, 1); + else + name = fmt::format("%d. %s", ++unique_names[name], name); + + if (name == device && + libevdev_has_property(dev, INPUT_PROP_ACCELEROMETER) && + libevdev_has_event_type(dev, EV_ABS)) + { + // Let's log axis information while we are in the settings in order to identify problems more easily. + // Directional axes on this device (absolute and/or relative x, y, z) represent accelerometer data. + // Some devices also report gyroscope data, which devices can report through the rotational axes (absolute and/or relative rx, ry, rz). + // All other axes retain their meaning. + // A device must not mix regular directional axes and accelerometer axes on the same event node. + for (const auto& [code, axis_name] : axis_list) + { + if (const input_absinfo *info = libevdev_get_abs_info(dev, code)) + { + const bool is_accel = code == ABS_X || code == ABS_Y || code == ABS_Z; + const auto code_name = libevdev_event_code_get_name(EV_ABS, code); + evdev_log.notice("Axis info for %s: %s (%s, %s) => minimum=%d, maximum=%d, fuzz=%d, flat=%d, resolution=%d", + name, code_name, axis_name, is_accel ? "accelerometer" : "gyro", info->minimum, info->maximum, info->fuzz, info->flat, info->resolution); + } + } + + std::shared_ptr motion_device = std::make_shared(); + motion_device->device = dev; + motion_device->path = path; + motion_device->has_motion = true; + + if (in_settings) + { + m_motion_settings_added[device] = motion_device; + } + + return motion_device; + } + libevdev_free(dev); + close(fd); + } + } + return nullptr; } PadHandlerBase::connection evdev_joystick_handler::update_connection(const std::shared_ptr& device) @@ -736,14 +886,16 @@ PadHandlerBase::connection evdev_joystick_handler::update_connection(const std:: return connection::connected; } -void evdev_joystick_handler::get_mapping(const std::shared_ptr& device, const std::shared_ptr& pad) +void evdev_joystick_handler::get_mapping(const pad_ensemble& binding) { - m_dev = std::static_pointer_cast(device); + m_dev = std::static_pointer_cast(binding.device); + const auto& pad = binding.pad; + if (!m_dev || !pad) return; auto& dev = m_dev->device; - if (dev == nullptr) + if (!dev) return; // Try to fetch all new events from the joystick. @@ -772,10 +924,77 @@ void evdev_joystick_handler::get_mapping(const std::shared_ptr& devic // -EAGAIN signifies no available events, not an actual *error*. if (ret != -EAGAIN) evdev_log.error("Failed to read latest event from joystick: %s [errno %d]", strerror(-ret), -ret); + } +} + +void evdev_joystick_handler::get_extended_info(const pad_ensemble& binding) +{ + // We use this to get motion controls from our buddy device + const auto& device = std::static_pointer_cast(binding.buddy_device); + const auto& pad = binding.pad; + + if (!pad || !device || !update_device(device)) + return; + + auto& dev = device->device; + if (!dev) return; + + // Try to fetch all new events from the joystick. + input_event evt; + int ret = LIBEVDEV_READ_STATUS_SUCCESS; + while (ret >= 0) + { + if (ret == LIBEVDEV_READ_STATUS_SYNC) + { + // Grab any pending sync event. + ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_SYNC, &evt); + } + else + { + ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &evt); + } + + if (ret == LIBEVDEV_READ_STATUS_SUCCESS && evt.type == EV_ABS) + { + for (AnalogSensor& sensor : pad->m_sensors) + { + if (sensor.m_keyCode != evt.code) + continue; + + sensor.m_value = get_sensor_value(dev, sensor, evt); + } + } + } + + if (ret < 0) + { + // -EAGAIN signifies no available events, not an actual *error*. + if (ret != -EAGAIN) + evdev_log.error("Failed to read latest event from buddy device: %s [errno %d]", strerror(-ret), -ret); } } +u16 evdev_joystick_handler::get_sensor_value(const libevdev* dev, const AnalogSensor& sensor, const input_event& evt) const +{ + if (dev) + { + const int min = libevdev_get_abs_minimum(dev, evt.code); + const int max = libevdev_get_abs_maximum(dev, evt.code); + + s16 value = ScaledInput(evt.value, min, max, 1023.0f); + + if (sensor.m_mirrored) + { + value = 1023 - value; + } + + return Clamp0To1023(value + sensor.m_shift); + } + + return 0; +} + void evdev_joystick_handler::handle_input_event(const input_event& evt, const std::shared_ptr& pad) { if (!pad) @@ -815,7 +1034,8 @@ void evdev_joystick_handler::handle_input_event(const input_event& evt, const st evdev_log.error("FindAxisDirection = %d, Button Nr.%d, value = %d", direction, i, value); continue; } - else if (direction != (m_is_negative ? 1 : 0)) + + if (direction != (m_is_negative ? 1 : 0)) { button.m_value = 0; button.m_pressed = 0; @@ -867,7 +1087,9 @@ void evdev_joystick_handler::handle_input_event(const input_event& evt, const st TranslateButtonPress(m_dev, button_code, pressed_min, m_dev->val_min[idx], true); } else // set to 0 to avoid remnant counter axis values + { m_dev->val_min[idx] = 0; + } } // m_keyCodeMax is the mapped key for right or up @@ -893,7 +1115,9 @@ void evdev_joystick_handler::handle_input_event(const input_event& evt, const st TranslateButtonPress(m_dev, button_code, pressed_max, m_dev->val_max[idx], true); } else // set to 0 to avoid remnant counter axis values + { m_dev->val_max[idx] = 0; + } } // cancel out opposing values and get the resulting difference. if there was no change, use the old value. @@ -901,6 +1125,8 @@ void evdev_joystick_handler::handle_input_event(const input_event& evt, const st } const auto cfg = m_dev->config; + if (!cfg) + return; u16 lx, ly, rx, ry; @@ -914,13 +1140,18 @@ void evdev_joystick_handler::handle_input_event(const input_event& evt, const st pad->m_sticks[3].m_value = 255 - ry; } -void evdev_joystick_handler::apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) +void evdev_joystick_handler::apply_pad_data(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + EvdevDevice* evdev_device = static_cast(device.get()); if (!evdev_device) return; auto cfg = device->config; + if (!cfg) + return; // Handle vibration const int idx_l = cfg->switch_vibration_motors ? 1 : 0; @@ -940,8 +1171,12 @@ int evdev_joystick_handler::FindAxisDirection(const std::unordered_map pad, const std::string& device, u8 player_id) +bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad, u8 player_id) { + if (!pad || player_id >= g_cfg_input.player.size()) + return false; + + const cfg_player* player_config = g_cfg_input.player[player_id]; if (!pad) return false; @@ -949,11 +1184,11 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad, const std m_dev = std::make_shared(); - m_pad_configs[player_id].from_string(g_cfg_input.player[player_id]->config.to_string()); + m_pad_configs[player_id].from_string(player_config->config.to_string()); m_dev->config = &m_pad_configs[player_id]; m_dev->player_id = player_id; cfg_pad* cfg = m_dev->config; - if (cfg == nullptr) + if (!cfg) return false; std::unordered_map axis_orientations; @@ -990,6 +1225,17 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad, const std return button; }; + const auto find_motion_button = [&](const cfg_sensor& sensor) -> evdev_sensor + { + evdev_sensor e_sensor{}; + e_sensor.type = EV_ABS; + e_sensor.mirrored = sensor.mirrored.get(); + e_sensor.shift = sensor.shift.get(); + const int key = FindKeyCode(motion_axis_list, sensor.axis, false); + if (key >= 0) e_sensor.code = static_cast(key); + return e_sensor; + }; + u32 pclass_profile = 0x0; for (const auto product : input::get_products_by_class(cfg->device_class_type)) @@ -1052,20 +1298,44 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad, const std pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, m_dev->axis_right[1].code, m_dev->axis_right[0].code); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, m_dev->axis_right[3].code, m_dev->axis_right[2].code); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 399); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 512); + m_dev->axis_motion[0] = find_motion_button(cfg->motion_sensor_x); + m_dev->axis_motion[1] = find_motion_button(cfg->motion_sensor_y); + m_dev->axis_motion[2] = find_motion_button(cfg->motion_sensor_z); + m_dev->axis_motion[3] = find_motion_button(cfg->motion_sensor_g); + + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, m_dev->axis_motion[0].code, m_dev->axis_motion[0].mirrored, m_dev->axis_motion[0].shift, DEFAULT_MOTION_X); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, m_dev->axis_motion[1].code, m_dev->axis_motion[1].mirrored, m_dev->axis_motion[1].shift, DEFAULT_MOTION_Y); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, m_dev->axis_motion[2].code, m_dev->axis_motion[2].mirrored, m_dev->axis_motion[2].shift, DEFAULT_MOTION_Z); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, m_dev->axis_motion[3].code, m_dev->axis_motion[3].mirrored, m_dev->axis_motion[3].shift, DEFAULT_MOTION_G); pad->m_vibrateMotors.emplace_back(true, 0); pad->m_vibrateMotors.emplace_back(false, 0); m_dev->axis_orientations = axis_orientations; - if (add_device(device, pad, false) < 0) - evdev_log.warning("evdev add_device in bindPadToDevice failed for device %s", device); + if (auto evdev_device = add_device(player_config->device, false)) + { + if (auto motion_device = add_motion_device(player_config->buddy_device, false)) + { + m_bindings.emplace_back(pad, evdev_device, motion_device); + } + else + { + m_bindings.emplace_back(pad, evdev_device, nullptr); + evdev_log.warning("evdev add_motion_device in bindPadToDevice failed for device %s", player_config->buddy_device.to_string()); + } + } + else + { + evdev_log.warning("evdev add_device in bindPadToDevice failed for device %s", player_config->device.to_string()); + } + + for (auto& binding : m_bindings) + { + update_device(binding.device); + update_device(binding.buddy_device); + } - update_devs(); return true; } @@ -1074,7 +1344,7 @@ bool evdev_joystick_handler::check_button(const EvdevButton& b, const u32 code) return m_dev && b.code == code && b.type == m_dev->cur_type && b.dir == m_dev->cur_dir; } -bool evdev_joystick_handler::check_buttons(const std::vector& b, const u32 code) +bool evdev_joystick_handler::check_buttons(const std::array& b, const u32 code) { return std::any_of(b.begin(), b.end(), [this, code](const EvdevButton& b) { return check_button(b, code); }); }; diff --git a/rpcs3/Input/evdev_joystick_handler.h b/rpcs3/Input/evdev_joystick_handler.h index bf242bb9685c..0e05a097b841 100644 --- a/rpcs3/Input/evdev_joystick_handler.h +++ b/rpcs3/Input/evdev_joystick_handler.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -327,11 +328,28 @@ class evdev_joystick_handler final : public PadHandlerBase { ABS_MT_TOOL_Y , "MT Tool Y-" }, }; + // Unique motion axis names for the config files and our pad settings dialog + const std::unordered_map motion_axis_list = + { + { ABS_X , "X" }, + { ABS_Y , "Y" }, + { ABS_Z , "Z" }, + { ABS_RX , "RX" }, + { ABS_RY , "RY" }, + { ABS_RZ , "RZ" }, + }; + struct EvdevButton { - u32 code; - int dir; - int type; + u32 code = 0; + int dir = 0; + int type = 0; + }; + + struct evdev_sensor : public EvdevButton + { + bool mirrored = false; + s32 shift = 0; }; struct EvdevDevice : public PadDevice @@ -339,17 +357,19 @@ class evdev_joystick_handler final : public PadHandlerBase libevdev* device{ nullptr }; std::string path; std::unordered_map axis_orientations; // value is true if key was found in rev_axis_list - s32 stick_val[4] = { 0, 0, 0, 0 }; - u16 val_min[4] = { 0, 0, 0, 0 }; - u16 val_max[4] = { 0, 0, 0, 0 }; - EvdevButton trigger_left = { 0, 0, 0 }; - EvdevButton trigger_right = { 0, 0, 0 }; - std::vector axis_left = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } }; - std::vector axis_right = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } }; + std::array stick_val{}; + std::array val_min{}; + std::array val_max{}; + EvdevButton trigger_left{}; + EvdevButton trigger_right{}; + std::array axis_left{}; + std::array axis_right{}; + std::array axis_motion{}; int cur_dir = 0; int cur_type = 0; int effect_id = -1; bool has_rumble = false; + bool has_motion = false; u16 force_large = 0; u16 force_small = 0; clock_t last_vibration = 0; @@ -361,18 +381,20 @@ class evdev_joystick_handler final : public PadHandlerBase void init_config(cfg_pad* cfg) override; bool Init() override; - std::vector ListDevices() override; - bool bindPadToDevice(std::shared_ptr pad, const std::string& device, u8 player_id) override; - void Close(); + std::vector list_devices(); + bool bindPadToDevice(std::shared_ptr pad, u8 player_id) override; void get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist = false, const std::vector& buttons = {}) override; + void get_motion_sensors(const std::string& padId, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors) override; + std::unordered_map get_motion_axis_list() const override; void SetPadData(const std::string& padId, u8 player_id, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b, bool battery_led, u32 battery_led_brightness) override; private: + void close_devices(); std::shared_ptr get_evdev_device(const std::string& device); std::string get_device_name(const libevdev* dev); bool update_device(const std::shared_ptr& device); - void update_devs(); - int add_device(const std::string& device, const std::shared_ptr& pad, bool in_settings = false); + std::shared_ptr add_device(const std::string& device, bool in_settings = false); + std::shared_ptr add_motion_device(const std::string& device, bool in_settings); u32 GetButtonInfo(const input_event& evt, const std::shared_ptr& device, int& button_code); std::unordered_map> GetButtonValues(const std::shared_ptr& device); void SetRumble(EvdevDevice* device, u16 large, u16 small); @@ -383,21 +405,25 @@ class evdev_joystick_handler final : public PadHandlerBase positive_axis m_pos_axis_config; std::vector m_positive_axis; std::vector m_blacklist; - std::unordered_map m_settings_added; + std::unordered_map> m_settings_added; + std::unordered_map> m_motion_settings_added; std::shared_ptr m_dev; bool m_is_button_or_trigger; bool m_is_negative; bool m_is_init = false; bool check_button(const EvdevButton& b, const u32 code); - bool check_buttons(const std::vector& b, const u32 code); + bool check_buttons(const std::array& b, const u32 code); void handle_input_event(const input_event& evt, const std::shared_ptr& pad); + u16 get_sensor_value(const libevdev* dev, const AnalogSensor& sensor, const input_event& evt) const; + protected: PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; - void get_mapping(const std::shared_ptr& device, const std::shared_ptr& pad) override; - void apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) override; + void get_mapping(const pad_ensemble& binding) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; bool get_is_left_trigger(u64 keyCode) override; bool get_is_right_trigger(u64 keyCode) override; bool get_is_left_stick(u64 keyCode) override; diff --git a/rpcs3/Input/hid_pad_handler.cpp b/rpcs3/Input/hid_pad_handler.cpp index c848ed159d9d..c46332b30ade 100644 --- a/rpcs3/Input/hid_pad_handler.cpp +++ b/rpcs3/Input/hid_pad_handler.cpp @@ -106,16 +106,16 @@ void hid_pad_handler::ThreadProc() } template -std::vector hid_pad_handler::ListDevices() +std::vector hid_pad_handler::list_devices() { - std::vector pads_list; + std::vector pads_list; if (!Init()) return pads_list; for (const auto& controller : m_controllers) // Controllers 1-n in GUI { - pads_list.emplace_back(controller.first); + pads_list.emplace_back(controller.first, false); } return pads_list; diff --git a/rpcs3/Input/hid_pad_handler.h b/rpcs3/Input/hid_pad_handler.h index 9c8b13d1e667..8235e8551108 100644 --- a/rpcs3/Input/hid_pad_handler.h +++ b/rpcs3/Input/hid_pad_handler.h @@ -60,7 +60,7 @@ class hid_pad_handler : public PadHandlerBase bool Init() override; void ThreadProc() override; - std::vector ListDevices() override; + std::vector list_devices() override; protected: enum class DataStatus diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index 0daf411fa5ba..f47a6560ce73 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -571,10 +571,10 @@ void keyboard_pad_handler::mouseWheelEvent(QWheelEvent* event) } } -std::vector keyboard_pad_handler::ListDevices() +std::vector keyboard_pad_handler::list_devices() { - std::vector list_devices; - list_devices.emplace_back(pad::keyboard_device_name); + std::vector list_devices; + list_devices.emplace_back(std::string(pad::keyboard_device_name), false); return list_devices; } @@ -784,12 +784,16 @@ std::string keyboard_pad_handler::native_scan_code_to_string(int native_scan_cod } } -bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr pad, const std::string& device, u8 player_id) +bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr pad, u8 player_id) { - if (!pad || device != pad::keyboard_device_name) + if (!pad || player_id >= g_cfg_input.player.size()) return false; - m_pad_configs[player_id].from_string(g_cfg_input.player[player_id]->config.to_string()); + const cfg_player* player_config = g_cfg_input.player[player_id]; + if (!player_config || player_config->device.to_string() != pad::keyboard_device_name) + return false; + + m_pad_configs[player_id].from_string(player_config->config.to_string()); cfg_pad* cfg = &m_pad_configs[player_id]; if (cfg == nullptr) return false; @@ -871,15 +875,15 @@ bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr pad, const std:: pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, find_key(cfg->rs_left), find_key(cfg->rs_right)); pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, find_key(cfg->rs_up), find_key(cfg->rs_down)); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 399); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 512); - pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 512); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 0, 0, 0, DEFAULT_MOTION_X); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 0, 0, 0, DEFAULT_MOTION_Y); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 0, 0, 0, DEFAULT_MOTION_Z); + pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 0, 0, 0, DEFAULT_MOTION_G); pad->m_vibrateMotors.emplace_back(true, 0); pad->m_vibrateMotors.emplace_back(false, 0); - m_bindings.push_back(pad); + m_bindings.emplace_back(pad, nullptr, nullptr); m_pads_internal.push_back(*pad); return true; @@ -955,8 +959,9 @@ void keyboard_pad_handler::ThreadProc() if (last_connection_status[i] == false) { - m_bindings[i]->m_port_status |= CELL_PAD_STATUS_CONNECTED; - m_bindings[i]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES; + ensure(m_bindings[i].pad); + m_bindings[i].pad->m_port_status |= CELL_PAD_STATUS_CONNECTED; + m_bindings[i].pad->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES; last_connection_status[i] = true; connected_devices++; } @@ -1046,7 +1051,8 @@ void keyboard_pad_handler::ThreadProc() for (uint i = 0; i < m_bindings.size(); i++) { - auto& pad = m_bindings[i]; + auto& pad = m_bindings[i].pad; + ensure(pad); pad->m_buttons = m_pads_internal[i].m_buttons; pad->m_sticks = m_pads_internal[i].m_sticks; } diff --git a/rpcs3/Input/keyboard_pad_handler.h b/rpcs3/Input/keyboard_pad_handler.h index 692a5c0ddbaf..cf3ae5fcb5cc 100644 --- a/rpcs3/Input/keyboard_pad_handler.h +++ b/rpcs3/Input/keyboard_pad_handler.h @@ -83,9 +83,9 @@ class keyboard_pad_handler final : public QObject, public PadHandlerBase bool eventFilter(QObject* target, QEvent* ev) override; void init_config(cfg_pad* cfg) override; - std::vector ListDevices() override; + std::vector list_devices() override; void get_next_button_press(const std::string& /*padId*/, const pad_callback& /*callback*/, const pad_fail_callback& /*fail_callback*/, bool /*get_blacklist*/ = false, const std::vector& /*buttons*/ = {}) override {} - bool bindPadToDevice(std::shared_ptr pad, const std::string& device, u8 player_id) override; + bool bindPadToDevice(std::shared_ptr pad, u8 player_id) override; void ThreadProc() override; std::string GetMouseName(const QMouseEvent* event) const; @@ -110,7 +110,6 @@ class keyboard_pad_handler final : public QObject, public PadHandlerBase bool get_mouse_lock_state() const; void release_all_keys(); - std::vector> m_bindings; std::vector m_pads_internal; // Accumulates input until the next poll. Only used for user input! // Button Movements diff --git a/rpcs3/Input/mm_joystick_handler.cpp b/rpcs3/Input/mm_joystick_handler.cpp index b421a3f07e00..9bcdd5a6638a 100644 --- a/rpcs3/Input/mm_joystick_handler.cpp +++ b/rpcs3/Input/mm_joystick_handler.cpp @@ -101,16 +101,16 @@ bool mm_joystick_handler::Init() return true; } -std::vector mm_joystick_handler::ListDevices() +std::vector mm_joystick_handler::list_devices() { - std::vector devices; + std::vector devices; if (!Init()) return devices; for (const auto& dev : m_devices) { - devices.emplace_back(dev.second.device_name); + devices.emplace_back(dev.second.device_name, false); } return devices; @@ -226,8 +226,12 @@ void mm_joystick_handler::get_next_button_press(const std::string& padId, const // Check for each button in our list if its corresponding (maybe remapped) button or axis was pressed. // Return the new value if the button was pressed (aka. its value was bigger than 0 or the defined threshold) - // Use a pair to get all the legally pressed buttons and use the one with highest value (prioritize first) - std::pair pressed_button = { 0, "" }; + // Get all the legally pressed buttons and use the one with highest value (prioritize first) + struct + { + u16 value = 0; + std::string name; + } pressed_button{}; for (const auto& button : axis_list) { @@ -244,8 +248,10 @@ void mm_joystick_handler::get_next_button_press(const std::string& padId, const m_blacklist.emplace_back(keycode); input_log.error("MMJOY Calibration: Added axis [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value); } - else if (value > pressed_button.first) - pressed_button = { value, button.second }; + else if (value > pressed_button.value) + { + pressed_button = { .value = value, .name = button.second }; + } } } @@ -264,8 +270,10 @@ void mm_joystick_handler::get_next_button_press(const std::string& padId, const m_blacklist.emplace_back(keycode); input_log.error("MMJOY Calibration: Added pov [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value); } - else if (value > pressed_button.first) - pressed_button = { value, button.second }; + else if (value > pressed_button.value) + { + pressed_button = { .value = value, .name = button.second }; + } } } @@ -288,8 +296,10 @@ void mm_joystick_handler::get_next_button_press(const std::string& padId, const m_blacklist.emplace_back(keycode); input_log.error("MMJOY Calibration: Added button [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value); } - else if (value > pressed_button.first) - pressed_button = { value, button.second }; + else if (value > pressed_button.value) + { + pressed_button = { .value = value, .name = button.second }; + } } } @@ -313,8 +323,8 @@ void mm_joystick_handler::get_next_button_press(const std::string& padId, const if (callback) { - if (pressed_button.first > 0) - return callback(pressed_button.first, pressed_button.second, padId, 0, preview_values); + if (pressed_button.value > 0) + return callback(pressed_button.value, pressed_button.name, padId, 0, preview_values); else return callback(0, "", padId, 0, preview_values); } diff --git a/rpcs3/Input/mm_joystick_handler.h b/rpcs3/Input/mm_joystick_handler.h index 87a1dcb9d451..2bd2945f6b4b 100644 --- a/rpcs3/Input/mm_joystick_handler.h +++ b/rpcs3/Input/mm_joystick_handler.h @@ -116,7 +116,7 @@ class mm_joystick_handler final : public PadHandlerBase bool Init() override; - std::vector ListDevices() override; + std::vector list_devices() override; void get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist = false, const std::vector& buttons = {}) override; void init_config(cfg_pad* cfg) override; diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index 01e7357cb15b..2fca12205ee6 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -178,11 +178,11 @@ void pad_thread::Init() { InitLddPad(pad_settings[i].ldd_handle); } - else if (!cur_pad_handler->bindPadToDevice(m_pads[i], g_cfg_input.player[i]->device.to_string(), i)) + else if (!cur_pad_handler->bindPadToDevice(m_pads[i], i)) { // Failed to bind the device to cur_pad_handler so binds to NullPadHandler input_log.error("Failed to bind device %s to handler %s", g_cfg_input.player[i]->device.to_string(), handler_type); - nullpad->bindPadToDevice(m_pads[i], g_cfg_input.player[i]->device.to_string(), i); + nullpad->bindPadToDevice(m_pads[i], i); } input_log.notice("Pad %d: %s", i, g_cfg_input.player[i]->device.to_string()); diff --git a/rpcs3/Input/xinput_pad_handler.cpp b/rpcs3/Input/xinput_pad_handler.cpp index ae8e31bfedac..09e29cdc87f8 100644 --- a/rpcs3/Input/xinput_pad_handler.cpp +++ b/rpcs3/Input/xinput_pad_handler.cpp @@ -379,9 +379,9 @@ bool xinput_pad_handler::Init() return true; } -std::vector xinput_pad_handler::ListDevices() +std::vector xinput_pad_handler::list_devices() { - std::vector xinput_pads_list; + std::vector xinput_pads_list; if (!Init()) return xinput_pads_list; @@ -404,7 +404,7 @@ std::vector xinput_pad_handler::ListDevices() } if (result == ERROR_SUCCESS) - xinput_pads_list.push_back(m_name_string + std::to_string(i + 1)); // Controllers 1-n in GUI + xinput_pads_list.emplace_back(m_name_string + std::to_string(i + 1), false); // Controllers 1-n in GUI } return xinput_pads_list; } @@ -485,8 +485,11 @@ PadHandlerBase::connection xinput_pad_handler::update_connection(const std::shar return connection::disconnected; } -void xinput_pad_handler::get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) +void xinput_pad_handler::get_extended_info(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + XInputDevice* dev = static_cast(device.get()); if (!dev || !pad) return; @@ -512,8 +515,11 @@ void xinput_pad_handler::get_extended_info(const std::shared_ptr& dev } } -void xinput_pad_handler::apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) +void xinput_pad_handler::apply_pad_data(const pad_ensemble& binding) { + const auto& device = binding.device; + const auto& pad = binding.pad; + XInputDevice* dev = static_cast(device.get()); if (!dev || !pad) return; diff --git a/rpcs3/Input/xinput_pad_handler.h b/rpcs3/Input/xinput_pad_handler.h index 2ceddfdc35be..3dcb5ba56ace 100644 --- a/rpcs3/Input/xinput_pad_handler.h +++ b/rpcs3/Input/xinput_pad_handler.h @@ -109,7 +109,7 @@ class xinput_pad_handler final : public PadHandlerBase bool Init() override; - std::vector ListDevices() override; + std::vector list_devices() override; void SetPadData(const std::string& padId, u8 player_id, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b, bool battery_led, u32 battery_led_brightness) override; u32 get_battery_level(const std::string& padId) override; void init_config(cfg_pad* cfg) override; @@ -140,8 +140,8 @@ class xinput_pad_handler final : public PadHandlerBase bool get_is_left_stick(u64 keyCode) override; bool get_is_right_stick(u64 keyCode) override; PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; - void get_extended_info(const std::shared_ptr& device, const std::shared_ptr& pad) override; - void apply_pad_data(const std::shared_ptr& device, const std::shared_ptr& pad) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; std::unordered_map get_button_values(const std::shared_ptr& device) override; pad_preview_values get_preview_values(const std::unordered_map& data) override; }; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index f1cada13b2ec..eeb405e9301b 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -294,6 +294,9 @@ true + + true + true @@ -513,6 +516,9 @@ true + + true + true @@ -634,6 +640,7 @@ + @@ -879,6 +886,7 @@ + @@ -1173,6 +1181,17 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + @@ -1600,6 +1619,16 @@ .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 3f4230c5e495..f363477f150f 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -864,6 +864,15 @@ Gui\utils + + Gui\settings + + + Generated Files\Debug + + + Generated Files\Release + @@ -1016,6 +1025,12 @@ Gui\utils + + Generated Files + + + Gui\settings + @@ -1267,6 +1282,12 @@ Gui\ipc + + Gui\settings + + + Form Files + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 9d2a06df9c3c..3ac4639e2389 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -45,6 +45,7 @@ set(SRC_FILES msg_dialog_frame.cpp osk_dialog_frame.cpp pad_led_settings_dialog.cpp + pad_motion_settings_dialog.cpp pad_settings_dialog.cpp patch_creator_dialog.cpp patch_manager_dialog.cpp @@ -95,6 +96,7 @@ set(UI_FILES camera_settings_dialog.ui main_window.ui pad_led_settings_dialog.ui + pad_motion_settings_dialog.ui pad_settings_dialog.ui patch_creator_dialog.ui patch_manager_dialog.ui diff --git a/rpcs3/rpcs3qt/pad_device_info.h b/rpcs3/rpcs3qt/pad_device_info.h new file mode 100644 index 000000000000..34f723abe5cb --- /dev/null +++ b/rpcs3/rpcs3qt/pad_device_info.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct pad_device_info +{ + std::string name; + bool is_connected{false}; +}; + +Q_DECLARE_METATYPE(pad_device_info) diff --git a/rpcs3/rpcs3qt/pad_led_settings_dialog.h b/rpcs3/rpcs3qt/pad_led_settings_dialog.h index bde7f3222946..45be1265c6ac 100644 --- a/rpcs3/rpcs3qt/pad_led_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_led_settings_dialog.h @@ -18,7 +18,7 @@ class pad_led_settings_dialog : public QDialog ~pad_led_settings_dialog(); Q_SIGNALS: - void pass_led_settings(int m_cR, int m_cG, int m_cB, bool m_low_battery_blink, bool m_battery_indicator, int m_battery_indicator_brightness); + void pass_led_settings(int cR, int cG, int cB, bool low_battery_blink, bool battery_indicator, int battery_indicator_brightness); private Q_SLOTS: void update_slider_label(int val) const; diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp new file mode 100644 index 000000000000..aefc2c86d3e1 --- /dev/null +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp @@ -0,0 +1,288 @@ +#include "stdafx.h" +#include "pad_motion_settings_dialog.h" + +#include +#include + +LOG_CHANNEL(cfg_log, "CFG"); + +pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::shared_ptr handler, cfg_player* cfg) + : QDialog(parent) + , ui(new Ui::pad_motion_settings_dialog) + , m_handler(handler) + , m_cfg(cfg) +{ + ui->setupUi(this); + setModal(true); + + ensure(m_handler); + ensure(m_cfg); + + cfg_pad& pad = m_cfg->config; + + m_preview_sliders = {{ ui->slider_x, ui->slider_y, ui->slider_z, ui->slider_g }}; + m_preview_labels = {{ ui->label_x, ui->label_y, ui->label_z, ui->label_g }}; + m_axis_names = {{ ui->combo_x, ui->combo_y, ui->combo_z, ui->combo_g }}; + m_mirrors = {{ ui->mirror_x, ui->mirror_y, ui->mirror_z, ui->mirror_g }}; + m_shifts = {{ ui->shift_x, ui->shift_y, ui->shift_z, ui->shift_g }}; + m_config_entries = {{ &pad.motion_sensor_x, &pad.motion_sensor_y, &pad.motion_sensor_z, &pad.motion_sensor_g }}; + + for (usz i = 0; i < m_preview_sliders.size(); i++) + { + m_preview_sliders[i]->setRange(0, 1023); + m_preview_labels[i]->setText("0"); + } + +#if HAVE_LIBEVDEV + const bool has_device_list = m_handler->m_type == pad_handler::evdev; +#else + const bool has_device_list = false; +#endif + + if (has_device_list) + { + // Combobox: Motion Devices + m_device_name = m_cfg->buddy_device.to_string(); + + ui->cb_choose_device->addItem(tr("Disabled"), QVariant::fromValue(pad_device_info{})); + + const std::vector device_list = m_handler->list_devices(); + for (const pad_list_entry& device : device_list) + { + if (device.is_buddy_only) + { + const QString device_name = QString::fromStdString(device.name); + const QVariant user_data = QVariant::fromValue(pad_device_info{ device.name, true }); + + ui->cb_choose_device->addItem(device_name, user_data); + } + } + + for (int i = 0; i < ui->cb_choose_device->count(); i++) + { + const QVariant user_data = ui->cb_choose_device->itemData(i); + ensure(user_data.canConvert()); + + if (const pad_device_info info = user_data.value(); info.name == m_device_name) + { + ui->cb_choose_device->setCurrentIndex(i); + break; + } + } + + connect(ui->cb_choose_device, QOverload::of(&QComboBox::currentIndexChanged), this, &pad_motion_settings_dialog::change_device); + + // Combobox: Configure Axis + m_motion_axis_list = m_handler->get_motion_axis_list(); + + for (const auto& [code, axis] : m_motion_axis_list) + { + const QString q_axis = QString::fromStdString(axis); + + for (usz i = 0; i < m_axis_names.size(); i++) + { + m_axis_names[i]->addItem(q_axis, code); + + if (m_config_entries[i]->axis.to_string() == axis) + { + m_axis_names[i]->setCurrentIndex(m_axis_names[i]->findData(code)); + } + } + } + + for (usz i = 0; i < m_axis_names.size(); i++) + { + const cfg_sensor* config = m_config_entries[i]; + + m_mirrors[i]->setChecked(config->mirrored.get()); + + m_shifts[i]->setRange(config->shift.min, config->shift.max); + m_shifts[i]->setValue(config->shift.get()); + + connect(m_mirrors[i], &QCheckBox::stateChanged, this, [this, i](int state) + { + std::lock_guard lock(m_config_mutex); + m_config_entries[i]->mirrored.set(state != Qt::Unchecked); + }); + + connect(m_shifts[i], QOverload::of(&QSpinBox::valueChanged), this, [this, i](int value) + { + std::lock_guard lock(m_config_mutex); + m_config_entries[i]->shift.set(value); + }); + + connect(m_axis_names[i], QOverload::of(&QComboBox::currentIndexChanged), this, [this, i](int index) + { + std::lock_guard lock(m_config_mutex); + if (!m_config_entries[i]->axis.from_string(m_axis_names[i]->itemText(index).toStdString())) + { + cfg_log.error("Failed to convert motion axis string: %s", m_axis_names[i]->itemData(index).toString().toStdString()); + } + }); + } + } + else + { + m_device_name = m_cfg->device.to_string(); + ui->gb_device->setVisible(false); + ui->cancelButton->setVisible(false); + for (usz i = 0; i < m_axis_names.size(); i++) + { + m_axis_names[i]->setVisible(false); + m_mirrors[i]->setVisible(false); + m_shifts[i]->setVisible(false); + } + } + + // Use timer to display button input + connect(&m_timer_input, &QTimer::timeout, this, [this]() + { + motion_callback_data data; + { + std::lock_guard lock(m_input_mutex); + data = m_motion_callback_data; + m_motion_callback_data.has_new_data = false; + } + + if (data.has_new_data) + { + // Starting with 1 because the first entry is the Disabled entry. + for (int i = 1; i < ui->cb_choose_device->count(); i++) + { + const QVariant user_data = ui->cb_choose_device->itemData(i); + ensure(user_data.canConvert()); + + if (const pad_device_info info = user_data.value(); info.name == data.pad_name) + { + switch_buddy_pad_info(i, info, data.success); + break; + } + } + + for (usz i = 0; i < data.preview_values.size(); i++) + { + m_preview_sliders[i]->setValue(data.preview_values[i]); + m_preview_labels[i]->setText(QString::number(data.preview_values[i])); + } + } + }); + m_timer_input.start(1); + + // Use thread to get button input + m_input_thread = std::make_unique>>("UI Pad Motion Thread", [this]() + { + while (thread_ctrl::state() != thread_state::aborting) + { + thread_ctrl::wait_for(1000); + + if (m_input_thread_state != input_thread_state::active) + { + if (m_input_thread_state == input_thread_state::pausing) + { + m_input_thread_state = input_thread_state::paused; + } + + continue; + } + + std::array sensors{}; + { + std::lock_guard lock(m_config_mutex); + for (usz i = 0; i < sensors.size(); i++) + { + AnalogSensor& sensor = sensors[i]; + const cfg_sensor* config = m_config_entries[i]; + const std::string cfgname = config->axis.to_string(); + for (const auto& [code, name] : m_motion_axis_list) + { + if (cfgname == name) + { + sensor.m_keyCode = code; + sensor.m_mirrored = config->mirrored.get(); + sensor.m_shift = config->shift.get(); + break; + } + } + } + } + + m_handler->get_motion_sensors(m_device_name, + [this](std::string pad_name, motion_preview_values preview_values) + { + std::lock_guard lock(m_input_mutex); + m_motion_callback_data.pad_name = std::move(pad_name); + m_motion_callback_data.preview_values = std::move(preview_values); + m_motion_callback_data.has_new_data = true; + m_motion_callback_data.success = true; + }, + [this](std::string pad_name, motion_preview_values preview_values) + { + std::lock_guard lock(m_input_mutex); + m_motion_callback_data.pad_name = std::move(pad_name); + m_motion_callback_data.preview_values = std::move(preview_values); + m_motion_callback_data.has_new_data = true; + m_motion_callback_data.success = false; + }, + m_motion_callback_data.preview_values, sensors); + } + }); + start_input_thread(); +} + +pad_motion_settings_dialog::~pad_motion_settings_dialog() +{ + if (m_input_thread) + { + m_input_thread_state = input_thread_state::pausing; + auto& thread = *m_input_thread; + thread = thread_state::aborting; + thread(); + } +} + +void pad_motion_settings_dialog::change_device(int index) +{ + if (index < 0) + return; + + const QVariant user_data = ui->cb_choose_device->itemData(index); + ensure(user_data.canConvert()); + + const pad_device_info info = user_data.value(); + + if (!m_cfg->buddy_device.from_string(info.name)) + { + cfg_log.error("Failed to convert motion device string: %s", info.name); + } + + m_device_name = m_cfg->buddy_device.to_string(); +} + +void pad_motion_settings_dialog::switch_buddy_pad_info(int index, pad_device_info info, bool is_connected) +{ + if (index >= 0 && info.is_connected != is_connected) + { + info.is_connected = is_connected; + + ui->cb_choose_device->setItemData(index, QVariant::fromValue(info)); + ui->cb_choose_device->setItemText(index, is_connected ? QString::fromStdString(info.name) : (QString::fromStdString(info.name) + Disconnected_suffix)); + } +} + +void pad_motion_settings_dialog::start_input_thread() +{ + m_input_thread_state = input_thread_state::active; +} + +void pad_motion_settings_dialog::pause_input_thread() +{ + if (m_input_thread) + { + m_input_thread_state = input_thread_state::pausing; + + while (m_input_thread_state != input_thread_state::paused) + { + std::this_thread::sleep_for(1ms); + } + } +} diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.h b/rpcs3/rpcs3qt/pad_motion_settings_dialog.h new file mode 100644 index 000000000000..eeb413453ba2 --- /dev/null +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.h @@ -0,0 +1,67 @@ +#pragma once + +#include "ui_pad_motion_settings_dialog.h" +#include "pad_device_info.h" +#include "Emu/Io/PadHandler.h" +#include "Utilities/Thread.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace Ui +{ + class pad_motion_settings_dialog; +} + +class pad_motion_settings_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit pad_motion_settings_dialog(QDialog* parent, std::shared_ptr handler, cfg_player* cfg); + ~pad_motion_settings_dialog(); + +private Q_SLOTS: + void change_device(int index); + +private: + void switch_buddy_pad_info(int index, pad_device_info info, bool is_connected); + void start_input_thread(); + void pause_input_thread(); + + std::unique_ptr ui; + std::shared_ptr m_handler; + std::string m_device_name; + cfg_player* m_cfg = nullptr; + std::mutex m_config_mutex; + std::unordered_map m_motion_axis_list; + + // Input thread. Its Callback handles the input + std::unique_ptr>> m_input_thread; + enum class input_thread_state { paused, pausing, active }; + atomic_t m_input_thread_state{input_thread_state::paused}; + struct motion_callback_data + { + bool success = false; + bool has_new_data = false; + std::string pad_name; + std::array preview_values{{ DEFAULT_MOTION_X, DEFAULT_MOTION_Y, DEFAULT_MOTION_Z, DEFAULT_MOTION_G}};; + } m_motion_callback_data; + QTimer m_timer_input; + std::mutex m_input_mutex; + + std::array m_preview_sliders; + std::array m_preview_labels; + std::array m_axis_names; + std::array m_mirrors; + std::array m_shifts; + std::array m_config_entries; + + const QString Disconnected_suffix = tr(" (disconnected)"); +}; diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.ui b/rpcs3/rpcs3qt/pad_motion_settings_dialog.ui new file mode 100644 index 000000000000..7f021ef2f002 --- /dev/null +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.ui @@ -0,0 +1,297 @@ + + + pad_motion_settings_dialog + + + + 0 + 0 + 574 + 464 + + + + Motion Controls + + + + + + Device + + + + + + + + + + + + X + + + + + + Qt::Horizontal + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + Mirrored + + + + + + + Shift: + + + + + + + + + + Y + + + + + + Qt::Horizontal + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + Mirrored + + + + + + + Shift: + + + + + + + + + + Z + + + + + + Qt::Horizontal + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + Mirrored + + + + + + + Shift: + + + + + + + + + + G + + + + + + Qt::Horizontal + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + Mirrored + + + + + + + Shift: + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + + okButton + clicked() + pad_motion_settings_dialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + pad_motion_settings_dialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 15e95f0c20e8..0c1f4a43bddf 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -9,6 +9,7 @@ #include "qt_utils.h" #include "pad_settings_dialog.h" #include "pad_led_settings_dialog.h" +#include "pad_motion_settings_dialog.h" #include "ui_pad_settings_dialog.h" #include "tooltips.h" #include "gui_settings.h" @@ -174,7 +175,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti connect(ui->chb_show_emulated_values, &QCheckBox::clicked, [this](bool checked) { m_gui_settings->SetValue(gui::pads_show_emulated, checked); - const auto& cfg = GetPlayerConfig(); + const cfg_pad& cfg = GetPlayerConfig(); RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, cfg.lpadsquircling, cfg.lstickmultiplier / 100.0); RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, cfg.rpadsquircling, cfg.rstickmultiplier / 100.0); }); @@ -364,7 +365,7 @@ void pad_settings_dialog::InitButtons() { // Allow LED battery indication while the dialog is open ensure(m_handler); - const auto& cfg = GetPlayerConfig(); + const cfg_pad& cfg = GetPlayerConfig(); SetPadData(0, 0, cfg.led_battery_indicator.get()); pad_led_settings_dialog dialog(this, cfg.colorR, cfg.colorG, cfg.colorB, m_handler->has_rgb(), m_handler->has_battery(), cfg.led_low_battery_blink.get(), cfg.led_battery_indicator.get(), cfg.led_battery_indicator_brightness); connect(&dialog, &pad_led_settings_dialog::pass_led_settings, this, &pad_settings_dialog::apply_led_settings); @@ -372,6 +373,30 @@ void pad_settings_dialog::InitButtons() SetPadData(0, 0); }); + // Open Motion settings + connect(ui->b_motion_controls, &QPushButton::clicked, this, [this]() + { + if (m_timer_input.isActive()) + { + m_timer_input.stop(); + } + if (m_timer_pad_refresh.isActive()) + { + m_timer_pad_refresh.stop(); + } + pause_input_thread(); + + pad_motion_settings_dialog dialog(this, m_handler, g_cfg_input.player[GetPlayerIndex()]); + dialog.exec(); + + if (ui->chooseDevice->isEnabled() && ui->chooseDevice->currentIndex() >= 0) + { + start_input_thread(); + m_timer_input.start(1); + m_timer_pad_refresh.start(1000); + } + }); + // Enable Button Remapping const auto callback = [this](u16 val, std::string name, std::string pad_name, u32 battery_level, pad_preview_values preview_values) { @@ -474,25 +499,7 @@ void pad_settings_dialog::InitButtons() }); // Use timer to refresh pad connection status - connect(&m_timer_pad_refresh, &QTimer::timeout, this, [this]() - { - for (int i = 0; i < ui->chooseDevice->count(); i++) - { - if (!ui->chooseDevice->itemData(i).canConvert()) - { - cfg_log.fatal("Cannot convert itemData for index %d and itemText %s", i, sstr(ui->chooseDevice->itemText(i))); - continue; - } - - const pad_device_info info = ui->chooseDevice->itemData(i).value(); - - std::lock_guard lock(m_handler_mutex); - - m_handler->get_next_button_press(info.name, - [this](u16, std::string, std::string pad_name, u32, pad_preview_values) { SwitchPadInfo(pad_name, true); }, - [this](std::string pad_name) { SwitchPadInfo(pad_name, false); }, false); - } - }); + connect(&m_timer_pad_refresh, &QTimer::timeout, this, &pad_settings_dialog::RefreshPads); // Use thread to get button input m_input_thread = std::make_unique>>("Pad Settings Thread", [this]() @@ -520,6 +527,7 @@ void pad_settings_dialog::InitButtons() m_cfg_entries[button_ids::id_pad_rstick_left].key, m_cfg_entries[button_ids::id_pad_rstick_right].key, m_cfg_entries[button_ids::id_pad_rstick_down].key, m_cfg_entries[button_ids::id_pad_rstick_up].key }; + m_handler->get_next_button_press(m_device_name, [this](u16 val, std::string name, std::string pad_name, u32 battery_level, pad_preview_values preview_values) { @@ -544,10 +552,37 @@ void pad_settings_dialog::InitButtons() }); } +void pad_settings_dialog::RefreshPads() +{ + for (int i = 0; i < ui->chooseDevice->count(); i++) + { + pad_device_info info = get_pad_info(ui->chooseDevice, i); + + if (info.name.empty()) + { + continue; + } + + std::lock_guard lock(m_handler_mutex); + + m_handler->get_next_button_press(info.name, + [&](u16, std::string, std::string pad_name, u32, pad_preview_values) + { + info.name = std::move(pad_name); + switch_pad_info(i, info, true); + }, + [&](std::string pad_name) + { + info.name = std::move(pad_name); + switch_pad_info(i, info, false); + }, false); + } +} + void pad_settings_dialog::SetPadData(u32 large_motor, u32 small_motor, bool led_battery_indicator) { ensure(m_handler); - const auto& cfg = GetPlayerConfig(); + const cfg_pad& cfg = GetPlayerConfig(); std::lock_guard lock(m_handler_mutex); m_handler->SetPadData(m_device_name, GetPlayerIndex(), large_motor, small_motor, cfg.colorR, cfg.colorG, cfg.colorB, led_battery_indicator, cfg.led_battery_indicator_brightness); @@ -557,7 +592,7 @@ void pad_settings_dialog::SetPadData(u32 large_motor, u32 small_motor, bool led_ void pad_settings_dialog::apply_led_settings(int colorR, int colorG, int colorB, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness) { ensure(m_handler); - auto& cfg = GetPlayerConfig(); + cfg_pad& cfg = GetPlayerConfig(); cfg.colorR.set(colorR); cfg.colorG.set(colorG); cfg.colorB.set(colorB); @@ -567,23 +602,48 @@ void pad_settings_dialog::apply_led_settings(int colorR, int colorG, int colorB, SetPadData(0, 0, led_battery_indicator); } +pad_device_info pad_settings_dialog::get_pad_info(QComboBox* combo, int index) +{ + if (!combo || index < 0) + { + cfg_log.fatal("get_pad_info: Invalid combo box or index (combo=%d, index=%d)", !!combo, index); + return {}; + } + + const QVariant user_data = combo->itemData(index); + + if (!user_data.canConvert()) + { + cfg_log.fatal("get_pad_info: Cannot convert itemData for index %d and itemText %s", index, sstr(combo->itemText(index))); + return {}; + } + + return user_data.value(); +} + +void pad_settings_dialog::switch_pad_info(int index, pad_device_info info, bool is_connected) +{ + if (index >= 0 && info.is_connected != is_connected) + { + info.is_connected = is_connected; + + ui->chooseDevice->setItemData(index, QVariant::fromValue(info)); + ui->chooseDevice->setItemText(index, is_connected ? qstr(info.name) : (qstr(info.name) + Disconnected_suffix)); + } + + if (!is_connected && m_timer.isActive() && ui->chooseDevice->currentIndex() == index) + { + ReactivateButtons(); + } +} + void pad_settings_dialog::SwitchPadInfo(const std::string& pad_name, bool is_connected) { for (int i = 0; i < ui->chooseDevice->count(); i++) { - const pad_device_info info = ui->chooseDevice->itemData(i).value(); - if (info.name == pad_name) + if (pad_device_info info = get_pad_info(ui->chooseDevice, i); info.name == pad_name) { - if (info.is_connected != is_connected) - { - ui->chooseDevice->setItemData(i, QVariant::fromValue(pad_device_info{ pad_name, is_connected })); - ui->chooseDevice->setItemText(i, is_connected ? qstr(pad_name) : (qstr(pad_name) + Disconnected_suffix)); - } - - if (!is_connected && m_timer.isActive() && ui->chooseDevice->currentIndex() == i) - { - ReactivateButtons(); - } + switch_pad_info(i, info, is_connected); break; } } @@ -600,7 +660,7 @@ void pad_settings_dialog::ReloadButtons() button->setText(text); }; - auto& cfg = GetPlayerConfig(); + cfg_pad& cfg = GetPlayerConfig(); updateButton(button_ids::id_pad_lstick_left, ui->b_lstick_left, &cfg.ls_left); updateButton(button_ids::id_pad_lstick_down, ui->b_lstick_down, &cfg.ls_down); @@ -1120,6 +1180,7 @@ void pad_settings_dialog::SwitchButtons(bool is_enabled) ui->squircle_right->setEnabled(is_enabled); ui->gb_pressure_intensity->setEnabled(is_enabled && m_enable_pressure_intensity_button); ui->gb_vibration->setEnabled(is_enabled && m_enable_rumble); + ui->gb_motion_controls->setEnabled(is_enabled && m_enable_motion); ui->gb_sticks->setEnabled(is_enabled && m_enable_deadzones); ui->gb_triggers->setEnabled(is_enabled && m_enable_deadzones); ui->gb_battery->setEnabled(is_enabled && (m_enable_battery || m_enable_led)); @@ -1215,9 +1276,11 @@ void pad_settings_dialog::ChangeHandler() bool force_enable = false; // enable configs even with disconnected devices const u32 player = GetPlayerIndex(); const bool is_ldd_pad = GetIsLddPad(player); + cfg_player* player_config = g_cfg_input.player[player]; std::string handler; std::string device; + std::string buddy_device; if (is_ldd_pad) { @@ -1226,13 +1289,14 @@ void pad_settings_dialog::ChangeHandler() else { handler = sstr(ui->chooseHandler->currentData().toString()); - device = g_cfg_input.player[player]->device.to_string(); + device = player_config->device.to_string(); + buddy_device = player_config->buddy_device.to_string(); } - - auto& cfg = g_cfg_input.player[player]->config; + + cfg_pad& cfg = player_config->config; // Change and get this player's current handler. - if (auto& cfg_handler = g_cfg_input.player[player]->handler; handler != cfg_handler.to_string()) + if (auto& cfg_handler = player_config->handler; handler != cfg_handler.to_string()) { if (!cfg_handler.from_string(handler)) { @@ -1241,18 +1305,18 @@ void pad_settings_dialog::ChangeHandler() } // Initialize the new pad config's defaults - m_handler = pad_thread::GetHandler(g_cfg_input.player[player]->handler); + m_handler = pad_thread::GetHandler(player_config->handler); pad_thread::InitPadConfig(cfg, cfg_handler, m_handler); } else { - m_handler = pad_thread::GetHandler(g_cfg_input.player[player]->handler); + m_handler = pad_thread::GetHandler(player_config->handler); } ensure(m_handler); // Get the handler's currently available devices. - const auto device_list = m_handler->ListDevices(); + const std::vector device_list = m_handler->list_devices(); // Localized tooltips const Tooltips tooltips; @@ -1315,6 +1379,9 @@ void pad_settings_dialog::ChangeHandler() // Enable Vibration Checkboxes m_enable_rumble = m_handler->has_rumble(); + // Enable Motion Settings + m_enable_motion = m_handler->has_motion(); + // Enable Deadzone Settings m_enable_deadzones = m_handler->has_deadzones(); @@ -1360,9 +1427,15 @@ void pad_settings_dialog::ChangeHandler() } default: { - for (const auto& device_name : device_list) + for (const pad_list_entry& device : device_list) { - ui->chooseDevice->addItem(qstr(device_name), QVariant::fromValue(pad_device_info{ device_name, true })); + if (!device.is_buddy_only) + { + const QString device_name = QString::fromStdString(device.name); + const QVariant user_data = QVariant::fromValue(pad_device_info{ device.name, true }); + + ui->chooseDevice->addItem(device_name, user_data); + } } break; } @@ -1376,19 +1449,11 @@ void pad_settings_dialog::ChangeHandler() if (config_enabled) { + RefreshPads(); + for (int i = 0; i < ui->chooseDevice->count(); i++) { - if (!ui->chooseDevice->itemData(i).canConvert()) - { - cfg_log.fatal("Cannot convert itemData for index %d and itemText %s", i, sstr(ui->chooseDevice->itemText(i))); - continue; - } - const pad_device_info info = ui->chooseDevice->itemData(i).value(); - m_handler->get_next_button_press(info.name, - [this](u16, std::string, std::string pad_name, u32, pad_preview_values) { SwitchPadInfo(pad_name, true); }, - [this](std::string pad_name) { SwitchPadInfo(pad_name, false); }, false); - - if (info.name == device) + if (pad_device_info info = get_pad_info(ui->chooseDevice, i); info.name == device) { ui->chooseDevice->setCurrentIndex(i); } @@ -1484,12 +1549,19 @@ void pad_settings_dialog::ChangeDevice(int index) { if (index < 0) return; + + const QVariant user_data = ui->chooseDevice->itemData(index); + + if (!user_data.canConvert()) + { + cfg_log.fatal("ChangeDevice: Cannot convert itemData for index %d and itemText %s", index, sstr(ui->chooseDevice->itemText(index))); + return; + } - const pad_device_info info = ui->chooseDevice->itemData(index).value(); + const pad_device_info info = user_data.value(); m_device_name = info.name; if (!g_cfg_input.player[GetPlayerIndex()]->device.from_string(m_device_name)) { - // Something went wrong cfg_log.error("Failed to convert device string: %s", m_device_name); } } @@ -1843,6 +1915,7 @@ void pad_settings_dialog::SubscribeTooltips() SubscribeTooltip(ui->gb_stick_multi, tooltips.gamepad_settings.stick_multiplier); SubscribeTooltip(ui->gb_kb_stick_multi, tooltips.gamepad_settings.stick_multiplier); SubscribeTooltip(ui->gb_vibration, tooltips.gamepad_settings.vibration); + SubscribeTooltip(ui->gb_motion_controls, tooltips.gamepad_settings.motion_controls); SubscribeTooltip(ui->gb_sticks, tooltips.gamepad_settings.stick_deadzones); SubscribeTooltip(ui->gb_stick_preview, tooltips.gamepad_settings.emulated_preview); SubscribeTooltip(ui->gb_triggers, tooltips.gamepad_settings.trigger_deadzones); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index da6404fc2e98..5b25fcebd541 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -4,12 +4,14 @@ #include #include #include +#include #include #include "Emu/Io/pad_config.h" #include "Emu/GameInfo.h" #include "Utilities/Thread.h" +#include "pad_device_info.h" class gui_settings; class PadHandlerBase; @@ -19,14 +21,6 @@ namespace Ui class pad_settings_dialog; } -struct pad_device_info -{ - std::string name; - bool is_connected{false}; -}; - -Q_DECLARE_METATYPE(pad_device_info) - class pad_settings_dialog : public QDialog { Q_OBJECT @@ -109,6 +103,7 @@ private Q_SLOTS: void AddProfile(); /** Update the current player config with the GUI values. */ void ApplyCurrentPlayerConfig(int new_player_id); + void RefreshPads(); private: std::unique_ptr ui; @@ -125,6 +120,7 @@ private Q_SLOTS: bool m_enable_deadzones{ false }; bool m_enable_led{ false }; bool m_enable_battery{ false }; + bool m_enable_motion{ false }; bool m_enable_pressure_intensity_button{ true }; // Button Mapping @@ -150,6 +146,7 @@ private Q_SLOTS: std::shared_ptr m_handler; std::mutex m_handler_mutex; std::string m_device_name; + std::string m_buddy_device_name; std::string m_profile; QTimer m_timer_pad_refresh; int m_last_player_id = 0; @@ -192,6 +189,9 @@ private Q_SLOTS: /** Update all the Button Labels with current button mapping */ void UpdateLabels(bool is_reset = false); + + pad_device_info get_pad_info(QComboBox* combo, int index); + void switch_pad_info(int index, pad_device_info info, bool is_connected); void SwitchPadInfo(const std::string& name, bool is_connected); /** Enable/Disable Buttons while trying to remap an other */ diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.ui b/rpcs3/rpcs3qt/pad_settings_dialog.ui index a3789803f73c..2030e6253108 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.ui +++ b/rpcs3/rpcs3qt/pad_settings_dialog.ui @@ -9,8 +9,8 @@ 0 0 - 500 - 380 + 1322 + 884 @@ -37,8 +37,8 @@ 0 0 - 858 - 715 + 1304 + 837 @@ -59,7 +59,7 @@ 5 - + @@ -118,6 +118,34 @@ + + + + Motion Controls + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Configure + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 55affe0572af..b30852f4fa1c 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -259,6 +259,7 @@ class Tooltips : public QObject const QString stick_multiplier = tr("The stick multipliers can be used to change the sensitivity of your stick movements.
The default setting is 1 and represents normal input."); const QString stick_deadzones = tr("A stick's deadzone determines how far the stick has to be moved until it is fully recognized by the game. The resulting range will be projected onto the full input range in order to give you a smooth experience. Movement inside the deadzone is actually simulated as a real DualShock 3's deadzone of ~13%, so don't worry if there is still movement shown in the emulated stick preview."); const QString vibration = tr("The PS3 activates two motors (large and small) to handle controller vibrations.
You can enable, disable or even switch these signals for the currently selected pad here."); + const QString motion_controls = tr("Use this to configure the gamepad motion controls."); const QString emulated_preview = tr("The emulated stick values (red dots) in the stick preview represent the actual stick positions as they will be visible to the game. The actual DualShock 3's stick range is not circular but formed like a rounded square (or squircle) which represents the maximum range of the emulated sticks. The blue regular dots represent the raw stick values (including stick multipliers) before they are converted for ingame usage."); const QString trigger_deadzones = tr("A trigger's deadzone determines how far the trigger has to be moved until it is recognized by the game. The resulting range will be projected onto the full input range in order to give you a smooth experience."); const QString stick_lerp = tr("With keyboards, you are inevitably restricted to 8 stick directions (4 straight + 4 diagonal). Furthermore, the stick will jump to the maximum value of the chosen direction immediately when a key is pressed. The stick interpolation can be used to work-around both of these issues by smoothening out these directional changes. The lower the value, the longer you have to press or release a key until the maximum amplitude is reached.");