1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 10:42:36 +01:00

evdev: add motion device

This commit is contained in:
Megamouse 2022-08-13 09:56:04 +02:00
parent a3007e11ca
commit 829af30568
35 changed files with 1588 additions and 316 deletions

View File

@ -52,14 +52,14 @@ public:
cfg->from_default();
}
std::vector<std::string> ListDevices() override
std::vector<pad_list_entry> list_devices() override
{
std::vector<std::string> nulllist;
nulllist.emplace_back("Default Null Device");
std::vector<pad_list_entry> nulllist;
nulllist.emplace_back("Default Null Device", false);
return nulllist;
}
bool bindPadToDevice(std::shared_ptr<Pad> /*pad*/, const std::string& /*device*/, u8 /*player_id*/) override
bool bindPadToDevice(std::shared_ptr<Pad> /*pad*/, u8 /*player_id*/) override
{
return true;
}

View File

@ -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<float>(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum));
return 255.0f * val;
const f32 val = static_cast<f32>(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<float>(std::clamp(raw_value, minimum, maximum) - minimum) / (abs(maximum) + abs(minimum));
return (510.0f * val) - 255.0f;
const f32 val = static_cast<f32>(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<s32>(static_cast<float>(trigger_max) * (value - threshold) / (trigger_max - threshold));
const s32 val = static_cast<s32>(static_cast<f32>(trigger_max) * (value - threshold) / (trigger_max - threshold));
return static_cast<u16>(ScaledInput(val, trigger_min, trigger_max));
}
}
@ -154,7 +154,7 @@ u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 max
return static_cast<u16>(0);
}
const float val = static_cast<float>(std::clamp(raw_value, 0, maximum)) / maximum; // value based on max range converted to [0, 1]
const f32 val = static_cast<f32>(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<float>(threshold) / maximum; // threshold converted to [0, 1]
const f32 thresh = static_cast<f32>(threshold) / maximum; // threshold converted to [0, 1]
return static_cast<u16>(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<u16, u16> PadHandlerBase::NormalizeStickDeadzone(s32 inX, s32 inY, u32 deadzone) const
{
const float dz_range = deadzone / static_cast<float>(std::abs(thumb_max)); // NOTE: thumb_max should be positive anyway
const f32 dz_range = deadzone / static_cast<f32>(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<u16, u16> 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<u16>((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<u16, std::string> 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<AnalogSensor, 4>& /*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> pad = std::make_shared<Pad>(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<PadDevice>& devi
}
}
bool PadHandlerBase::bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device, u8 player_id)
bool PadHandlerBase::bindPadToDevice(std::shared_ptr<Pad> pad, u8 player_id)
{
if (!pad)
if (!pad || player_id >= g_cfg_input.player.size())
{
return false;
}
std::shared_ptr<PadDevice> pad_device = get_device(device);
const cfg_player* player_config = g_cfg_input.player[player_id];
if (!player_config)
{
return false;
}
std::shared_ptr<PadDevice> 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> 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<u32, PadHandlerBase::button::button_count> PadHandlerBase::get_mapped
return mapping;
}
void PadHandlerBase::get_mapping(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& 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<PadDevice>& 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]);
}
}

View File

@ -20,10 +20,35 @@ public:
u8 player_id{0};
};
struct pad_ensemble
{
std::shared_ptr<Pad> pad;
std::shared_ptr<PadDevice> device;
std::shared_ptr<PadDevice> buddy_device;
explicit pad_ensemble(std::shared_ptr<Pad> _pad, std::shared_ptr<PadDevice> _device, std::shared_ptr<PadDevice> _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<int, 6>;
using pad_callback = std::function<void(u16 /*button_value*/, std::string /*button_name*/, std::string /*pad_name*/, u32 /*battery_level*/, pad_preview_values /*preview_values*/)>;
using pad_fail_callback = std::function<void(std::string /*pad_name*/)>;
using motion_preview_values = std::array<u16, 4>;
using motion_callback = std::function<void(std::string /*pad_name*/, motion_preview_values /*preview_values*/)>;
using motion_fail_callback = std::function<void(std::string /*pad_name*/, motion_preview_values /*preview_values*/)>;
class PadHandlerBase
{
protected:
@ -68,7 +93,7 @@ protected:
disconnected
};
static const u32 MAX_GAMEPADS = 7;
static constexpr u32 MAX_GAMEPADS = 7;
std::array<bool, MAX_GAMEPADS> last_connection_status{{ false, false, false, false, false, false, false }};
@ -82,10 +107,11 @@ protected:
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<cfg_pad, MAX_GAMEPADS> m_pad_configs;
std::vector<std::pair<std::shared_ptr<PadDevice>, std::shared_ptr<Pad>>> bindings;
std::vector<pad_ensemble> m_bindings;
std::unordered_map<u32, std::string> button_list;
std::vector<u32> blacklist;
@ -105,10 +131,10 @@ protected:
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 @@ protected:
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 @@ public:
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 @@ public:
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<std::string> ListDevices() = 0;
virtual std::vector<pad_list_entry> 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> pad, const std::string& device, u8 player_id);
virtual void init_config(cfg_pad* /*cfg*/) = 0;
virtual bool bindPadToDevice(std::shared_ptr<Pad> 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<std::string>& 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<AnalogSensor, 4>& sensors);
virtual std::unordered_map<u32, std::string> get_motion_axis_list() const { return {}; }
private:
virtual std::shared_ptr<PadDevice> get_device(const std::string& /*device*/) { return nullptr; }
@ -182,14 +211,14 @@ private:
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<PadDevice>& /*device*/) { return connection::disconnected; }
virtual void get_extended_info(const std::shared_ptr<PadDevice>& /*device*/, const std::shared_ptr<Pad>& /*pad*/) {}
virtual void apply_pad_data(const std::shared_ptr<PadDevice>& /*device*/, const std::shared_ptr<Pad>& /*pad*/) {}
virtual void get_extended_info(const pad_ensemble& /*binding*/) {}
virtual void apply_pad_data(const pad_ensemble& /*binding*/) {}
virtual std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& /*device*/) { return {}; }
virtual pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& /*data*/) { return {}; }
protected:
virtual std::array<u32, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const cfg_pad* cfg);
virtual void get_mapping(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad);
virtual void get_mapping(const pad_ensemble& binding);
void TranslateButtonPress(const std::shared_ptr<PadDevice>& device, u64 keyCode, bool& pressed, u16& val, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false);
void init_configs();
};

View File

@ -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;
}

View File

@ -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<pad_handler> 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

View File

@ -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;
u32 m_offset = 0;
u32 m_keyCode = 0;
b8 m_mirrored = false;
s16 m_shift = 0;
u16 m_value = 0;
AnalogSensor(u32 offset, u16 value)
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] = {};

View File

@ -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<PadDevice>& device, const std::shared_ptr<Pad>& 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<ds3_device*>(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<PadDevice>& device, const std::shared_ptr<Pad>& 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<ds3_device*>(device.get());
if (!dev || !dev->hidDevice || !dev->config || !pad)
return;

View File

@ -94,8 +94,8 @@ private:
bool get_is_left_stick(u64 keyCode) override;
bool get_is_right_stick(u64 keyCode) override;
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void get_extended_info(const pad_ensemble& binding) override;
void apply_pad_data(const pad_ensemble& binding) override;
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& data) override;
};

View File

@ -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<PadDevice>& device, const std::shared_ptr<Pad>& 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<DS4Device*>(device.get());
if (!ds4_device || !pad)
return;
@ -869,8 +873,11 @@ void ds4_pad_handler::get_extended_info(const std::shared_ptr<PadDevice>& device
pad->m_sensors[3].m_value = Clamp0To1023(gyroX);
}
void ds4_pad_handler::apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& 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<DS4Device*>(device.get());
if (!ds4_dev || !ds4_dev->hidDevice || !ds4_dev->config || !pad)
return;

View File

@ -74,8 +74,8 @@ private:
bool get_is_left_stick(u64 keyCode) override;
bool get_is_right_stick(u64 keyCode) override;
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void get_extended_info(const pad_ensemble& binding) override;
void apply_pad_data(const pad_ensemble& binding) override;
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& data) override;
};

View File

@ -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<PadDevice>& device, const std::shared_ptr<Pad>& 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<DualSenseDevice*>(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<PadDevice>& device, const std::shared_ptr<Pad>& 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<DualSenseDevice*>(device.get());
if (!dualsense_dev || !dualsense_dev->hidDevice || !dualsense_dev->config || !pad)
return;

View File

@ -87,6 +87,6 @@ private:
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& data) override;
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void get_extended_info(const pad_ensemble& binding) override;
void apply_pad_data(const pad_ensemble& binding) override;
};

View File

@ -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<u32, std::string> 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<PadDevice>& 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)
{
update_device(binding.first);
}
}
void evdev_joystick_handler::Close()
{
for (auto& binding : bindings)
{
EvdevDevice* evdev_device = static_cast<EvdevDevice*>(binding.first.get());
if (evdev_device)
if (evdev_device && evdev_device->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);
}
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)
{
free_device(static_cast<EvdevDevice*>(binding.device.get()));
free_device(static_cast<EvdevDevice*>(binding.buddy_device.get()));
}
for (auto [name, device] : m_settings_added)
{
free_device(static_cast<EvdevDevice*>(device.get()));
}
for (auto [name, device] : m_motion_settings_added)
{
free_device(static_cast<EvdevDevice*>(device.get()));
}
}
@ -266,17 +283,15 @@ std::unordered_map<u64, std::pair<u16, bool>> evdev_joystick_handler::GetButtonV
std::shared_ptr<evdev_joystick_handler::EvdevDevice> 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<EvdevDevice> 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<EvdevDevice>(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<std::string>& 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<AnalogSensor, 4>& 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<std::string> evdev_joystick_handler::ListDevices()
std::vector<pad_list_entry> evdev_joystick_handler::list_devices()
{
Init();
std::unordered_map<std::string, u32> unique_names;
std::vector<std::string> evdev_joystick_list;
fs::dir devdir{"/dev/input/"};
fs::dir_entry et;
std::vector<pad_list_entry> 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<std::string> 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);
}
evdev_joystick_list.push_back(name);
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.emplace_back(name, is_motion_device);
}
}
libevdev_free(dev);
close(fd);
}
@ -642,19 +724,18 @@ std::vector<std::string> evdev_joystick_handler::ListDevices()
return evdev_joystick_list;
}
int evdev_joystick_handler::add_device(const std::string& device, const std::shared_ptr<Pad>& pad, bool in_settings)
std::shared_ptr<evdev_joystick_handler::EvdevDevice> 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<std::string, u32> 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<PadDevice>, std::shared_ptr<Pad>> binding)
{
EvdevDevice* device = static_cast<EvdevDevice*>(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<EvdevDevice>();
m_settings_added[device] = bindings.size();
m_dev = std::make_shared<EvdevDevice>();
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::EvdevDevice> 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<std::string, u32> 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<EvdevDevice> motion_device = std::make_shared<EvdevDevice>();
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<PadDevice>& 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<PadDevice>& device, const std::shared_ptr<Pad>& pad)
void evdev_joystick_handler::get_mapping(const pad_ensemble& binding)
{
m_dev = std::static_pointer_cast<EvdevDevice>(device);
m_dev = std::static_pointer_cast<EvdevDevice>(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<PadDevice>& 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);
return;
}
}
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<EvdevDevice>(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>& 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<PadDevice>& device, const std::shared_ptr<Pad>& 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<EvdevDevice*>(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<int, bool
return -1;
}
bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device, u8 player_id)
bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> 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> pad, const std
m_dev = std::make_shared<EvdevDevice>();
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<int, bool> axis_orientations;
@ -990,6 +1225,17 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> 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<u32>(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> 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<EvdevButton>& b, const u32 code)
bool evdev_joystick_handler::check_buttons(const std::array<EvdevButton, 4>& b, const u32 code)
{
return std::any_of(b.begin(), b.end(), [this, code](const EvdevButton& b) { return check_button(b, code); });
};

View File

@ -7,6 +7,7 @@
#include <libevdev/libevdev.h>
#include <memory>
#include <unordered_map>
#include <array>
#include <vector>
#include <thread>
#include <ctime>
@ -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<u32, std::string> 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<int, bool> 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<EvdevButton> axis_left = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
std::vector<EvdevButton> axis_right = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
std::array<s32, 4> stick_val{};
std::array<u16, 4> val_min{};
std::array<u16, 4> val_max{};
EvdevButton trigger_left{};
EvdevButton trigger_right{};
std::array<EvdevButton, 4> axis_left{};
std::array<EvdevButton, 4> axis_right{};
std::array<evdev_sensor, 4> 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 @@ public:
void init_config(cfg_pad* cfg) override;
bool Init() override;
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device, u8 player_id) override;
void Close();
std::vector<pad_list_entry> list_devices();
bool bindPadToDevice(std::shared_ptr<Pad> 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<std::string>& 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<AnalogSensor, 4>& sensors) override;
std::unordered_map<u32, std::string> 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<EvdevDevice> get_evdev_device(const std::string& device);
std::string get_device_name(const libevdev* dev);
bool update_device(const std::shared_ptr<PadDevice>& device);
void update_devs();
int add_device(const std::string& device, const std::shared_ptr<Pad>& pad, bool in_settings = false);
std::shared_ptr<evdev_joystick_handler::EvdevDevice> add_device(const std::string& device, bool in_settings = false);
std::shared_ptr<evdev_joystick_handler::EvdevDevice> add_motion_device(const std::string& device, bool in_settings);
u32 GetButtonInfo(const input_event& evt, const std::shared_ptr<EvdevDevice>& device, int& button_code);
std::unordered_map<u64, std::pair<u16, bool>> GetButtonValues(const std::shared_ptr<EvdevDevice>& device);
void SetRumble(EvdevDevice* device, u16 large, u16 small);
@ -383,21 +405,25 @@ private:
positive_axis m_pos_axis_config;
std::vector<u32> m_positive_axis;
std::vector<std::string> m_blacklist;
std::unordered_map<std::string, int> m_settings_added;
std::unordered_map<std::string, std::shared_ptr<evdev_joystick_handler::EvdevDevice>> m_settings_added;
std::unordered_map<std::string, std::shared_ptr<evdev_joystick_handler::EvdevDevice>> m_motion_settings_added;
std::shared_ptr<EvdevDevice> 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<EvdevButton>& b, const u32 code);
bool check_buttons(const std::array<EvdevButton, 4>& b, const u32 code);
void handle_input_event(const input_event& evt, const std::shared_ptr<Pad>& 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<PadDevice>& device) override;
void get_mapping(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& 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;

View File

@ -106,16 +106,16 @@ void hid_pad_handler<Device>::ThreadProc()
}
template <class Device>
std::vector<std::string> hid_pad_handler<Device>::ListDevices()
std::vector<pad_list_entry> hid_pad_handler<Device>::list_devices()
{
std::vector<std::string> pads_list;
std::vector<pad_list_entry> 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;

View File

@ -60,7 +60,7 @@ public:
bool Init() override;
void ThreadProc() override;
std::vector<std::string> ListDevices() override;
std::vector<pad_list_entry> list_devices() override;
protected:
enum class DataStatus

View File

@ -571,10 +571,10 @@ void keyboard_pad_handler::mouseWheelEvent(QWheelEvent* event)
}
}
std::vector<std::string> keyboard_pad_handler::ListDevices()
std::vector<pad_list_entry> keyboard_pad_handler::list_devices()
{
std::vector<std::string> list_devices;
list_devices.emplace_back(pad::keyboard_device_name);
std::vector<pad_list_entry> 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> pad, const std::string& device, u8 player_id)
bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr<Pad> 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> 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;
}

View File

@ -83,9 +83,9 @@ public:
bool eventFilter(QObject* target, QEvent* ev) override;
void init_config(cfg_pad* cfg) override;
std::vector<std::string> ListDevices() override;
std::vector<pad_list_entry> 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<std::string>& /*buttons*/ = {}) override {}
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device, u8 player_id) override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, u8 player_id) override;
void ThreadProc() override;
std::string GetMouseName(const QMouseEvent* event) const;
@ -110,7 +110,6 @@ private:
bool get_mouse_lock_state() const;
void release_all_keys();
std::vector<std::shared_ptr<Pad>> m_bindings;
std::vector<Pad> m_pads_internal; // Accumulates input until the next poll. Only used for user input!
// Button Movements

View File

@ -101,16 +101,16 @@ bool mm_joystick_handler::Init()
return true;
}
std::vector<std::string> mm_joystick_handler::ListDevices()
std::vector<pad_list_entry> mm_joystick_handler::list_devices()
{
std::vector<std::string> devices;
std::vector<pad_list_entry> 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<u16, std::string> 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);
}

View File

@ -116,7 +116,7 @@ public:
bool Init() override;
std::vector<std::string> ListDevices() override;
std::vector<pad_list_entry> 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<std::string>& buttons = {}) override;
void init_config(cfg_pad* cfg) override;

View File

@ -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());

View File

@ -379,9 +379,9 @@ bool xinput_pad_handler::Init()
return true;
}
std::vector<std::string> xinput_pad_handler::ListDevices()
std::vector<pad_list_entry> xinput_pad_handler::list_devices()
{
std::vector<std::string> xinput_pads_list;
std::vector<pad_list_entry> xinput_pads_list;
if (!Init())
return xinput_pads_list;
@ -404,7 +404,7 @@ std::vector<std::string> 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<PadDevice>& device, const std::shared_ptr<Pad>& 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<XInputDevice*>(device.get());
if (!dev || !pad)
return;
@ -512,8 +515,11 @@ void xinput_pad_handler::get_extended_info(const std::shared_ptr<PadDevice>& dev
}
}
void xinput_pad_handler::apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& 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<XInputDevice*>(device.get());
if (!dev || !pad)
return;

View File

@ -109,7 +109,7 @@ public:
bool Init() override;
std::vector<std::string> ListDevices() override;
std::vector<pad_list_entry> 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 @@ private:
bool get_is_left_stick(u64 keyCode) override;
bool get_is_right_stick(u64 keyCode) override;
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
void get_extended_info(const pad_ensemble& binding) override;
void apply_pad_data(const pad_ensemble& binding) override;
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& data) override;
};

View File

@ -294,6 +294,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_led_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_motion_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -513,6 +516,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_led_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_motion_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -634,6 +640,7 @@
<ClCompile Include="rpcs3qt\microphone_creator.cpp" />
<ClCompile Include="rpcs3qt\osk_dialog_frame.cpp" />
<ClCompile Include="rpcs3qt\pad_led_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\pad_motion_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\patch_creator_dialog.cpp" />
<ClCompile Include="rpcs3qt\patch_manager_dialog.cpp" />
<ClCompile Include="rpcs3qt\pkg_install_dialog.cpp" />
@ -879,6 +886,7 @@
<ClInclude Include="QTGeneratedFiles\ui_camera_settings_dialog.h" />
<ClInclude Include="QTGeneratedFiles\ui_main_window.h" />
<ClInclude Include="QTGeneratedFiles\ui_pad_led_settings_dialog.h" />
<ClInclude Include="QTGeneratedFiles\ui_pad_motion_settings_dialog.h" />
<ClInclude Include="QTGeneratedFiles\ui_pad_settings_dialog.h" />
<ClInclude Include="QTGeneratedFiles\ui_patch_creator_dialog.h" />
<ClInclude Include="QTGeneratedFiles\ui_patch_manager_dialog.h" />
@ -1173,6 +1181,17 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\pad_device_info.h" />
<ClInclude Include="rpcs3qt\qt_camera_handler.h" />
<ClInclude Include="rpcs3qt\qt_music_handler.h" />
<CustomBuild Include="rpcs3qt\qt_music_error_handler.h">
@ -1600,6 +1619,16 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.ui">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
</CustomBuild>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />

View File

@ -864,6 +864,15 @@
<ClCompile Include="rpcs3qt\uuid.cpp">
<Filter>Gui\utils</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\pad_motion_settings_dialog.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_pad_motion_settings_dialog.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_pad_motion_settings_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1016,6 +1025,12 @@
<ClInclude Include="rpcs3qt\uuid.h">
<Filter>Gui\utils</Filter>
</ClInclude>
<ClInclude Include="QTGeneratedFiles\ui_pad_motion_settings_dialog.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\pad_device_info.h">
<Filter>Gui\settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -1267,6 +1282,12 @@
<CustomBuild Include="rpcs3qt\ipc_settings_dialog.h">
<Filter>Gui\ipc</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.ui">
<Filter>Form Files</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="rpcs3.ico" />

View File

@ -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

View File

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <QObject>
struct pad_device_info
{
std::string name;
bool is_connected{false};
};
Q_DECLARE_METATYPE(pad_device_info)

View File

@ -18,7 +18,7 @@ public:
~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;

View File

@ -0,0 +1,288 @@
#include "stdafx.h"
#include "pad_motion_settings_dialog.h"
#include <QComboBox>
#include <thread>
LOG_CHANNEL(cfg_log, "CFG");
pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::shared_ptr<PadHandlerBase> 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<pad_list_entry> 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<pad_device_info>());
if (const pad_device_info info = user_data.value<pad_device_info>(); info.name == m_device_name)
{
ui->cb_choose_device->setCurrentIndex(i);
break;
}
}
connect(ui->cb_choose_device, QOverload<int>::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<int>::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<int>::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<pad_device_info>());
if (const pad_device_info info = user_data.value<pad_device_info>(); 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<named_thread<std::function<void()>>>("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<AnalogSensor, 4> 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<pad_device_info>());
const pad_device_info info = user_data.value<pad_device_info>();
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);
}
}
}

View File

@ -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 <QDialog>
#include <QTimer>
#include <QLabel>
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <unordered_map>
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<PadHandlerBase> 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::pad_motion_settings_dialog> ui;
std::shared_ptr<PadHandlerBase> m_handler;
std::string m_device_name;
cfg_player* m_cfg = nullptr;
std::mutex m_config_mutex;
std::unordered_map<u32, std::string> m_motion_axis_list;
// Input thread. Its Callback handles the input
std::unique_ptr<named_thread<std::function<void()>>> m_input_thread;
enum class input_thread_state { paused, pausing, active };
atomic_t<input_thread_state> 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<u16, 4> 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<QSlider*, 4> m_preview_sliders;
std::array<QLabel*, 4> m_preview_labels;
std::array<QComboBox*, 4> m_axis_names;
std::array<QCheckBox*, 4> m_mirrors;
std::array<QSpinBox*, 4> m_shifts;
std::array<cfg_sensor*, 4> m_config_entries;
const QString Disconnected_suffix = tr(" (disconnected)");
};

View File

@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>pad_motion_settings_dialog</class>
<widget class="QDialog" name="pad_motion_settings_dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>574</width>
<height>464</height>
</rect>
</property>
<property name="windowTitle">
<string>Motion Controls</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_main">
<item>
<widget class="QGroupBox" name="gb_device">
<property name="title">
<string>Device</string>
</property>
<layout class="QHBoxLayout" name="gb_device_layout">
<item>
<widget class="QComboBox" name="cb_choose_device"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_axis_x">
<property name="title">
<string>X</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_x" stretch="2,1,1,0,1">
<item>
<widget class="QSlider" name="slider_x">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_x">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_x"/>
</item>
<item>
<widget class="QCheckBox" name="mirror_x">
<property name="text">
<string>Mirrored</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="shift_x">
<property name="prefix">
<string>Shift: </string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_axis_y">
<property name="title">
<string>Y</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_y" stretch="2,1,1,0,1">
<item>
<widget class="QSlider" name="slider_y">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_y">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_y"/>
</item>
<item>
<widget class="QCheckBox" name="mirror_y">
<property name="text">
<string>Mirrored</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="shift_y">
<property name="prefix">
<string>Shift: </string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_axis_z">
<property name="title">
<string>Z</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_z" stretch="2,1,1,0,1">
<item>
<widget class="QSlider" name="slider_z">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_z">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_z"/>
</item>
<item>
<widget class="QCheckBox" name="mirror_z">
<property name="text">
<string>Mirrored</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="shift_z">
<property name="prefix">
<string>Shift: </string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_axis_g">
<property name="title">
<string>G</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_g" stretch="2,1,1,0,1">
<item>
<widget class="QSlider" name="slider_g">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_g">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_g"/>
</item>
<item>
<widget class="QCheckBox" name="mirror_g">
<property name="text">
<string>Mirrored</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="shift_g">
<property name="prefix">
<string>Shift: </string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="main_vertical_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="button_box_layout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="button_box_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>31</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>okButton</sender>
<signal>clicked()</signal>
<receiver>pad_motion_settings_dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>278</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>254</y>
</hint>
</hints>
</connection>
<connection>
<sender>cancelButton</sender>
<signal>clicked()</signal>
<receiver>pad_motion_settings_dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>369</x>
<y>253</y>
</hint>
<hint type="destinationlabel">
<x>179</x>
<y>282</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -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_settings> 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<pad_device_info>())
{
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<pad_device_info>();
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<named_thread<std::function<void()>>>("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<pad_device_info>())
{
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<pad_device_info>();
}
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<pad_device_info>();
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<pad_list_entry> 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<pad_device_info>())
{
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<pad_device_info>();
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);
const pad_device_info info = ui->chooseDevice->itemData(index).value<pad_device_info>();
if (!user_data.canConvert<pad_device_info>())
{
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 = user_data.value<pad_device_info>();
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);

View File

@ -4,12 +4,14 @@
#include <QDialog>
#include <QLabel>
#include <QTimer>
#include <QComboBox>
#include <mutex>
#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::pad_settings_dialog> ui;
@ -125,6 +120,7 @@ private:
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:
std::shared_ptr<PadHandlerBase> 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:
/** 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 */

View File

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>380</height>
<width>1322</width>
<height>884</height>
</rect>
</property>
<property name="windowTitle">
@ -37,8 +37,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>858</width>
<height>715</height>
<width>1304</width>
<height>837</height>
</rect>
</property>
<widget class="QWidget" name="tab">
@ -59,7 +59,7 @@
<number>5</number>
</property>
<item>
<layout class="QHBoxLayout" name="topLayout">
<layout class="QHBoxLayout" name="topLayout" stretch="1,1,0,1">
<item>
<widget class="QGroupBox" name="gb_handlers">
<property name="title">
@ -118,6 +118,34 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_motion_controls">
<property name="title">
<string>Motion Controls</string>
</property>
<layout class="QHBoxLayout" name="gb_buddy_device_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_motion_controls">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_profiles">
<property name="title">

View File

@ -259,6 +259,7 @@ public:
const QString stick_multiplier = tr("The stick multipliers can be used to change the sensitivity of your stick movements.<br>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.<br>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.");