[Qt/Input] Improve pad_settings_dialog a bit (#3611)

* Input: further work on remapping Xinput and begin work on remapping DS4

* Input: Improve pad_settings_dialog a bit and begin Remapping for XInput

* Input: begin evdev remapping and change all handlers to use cfg::string

* Input: finish work on remapping evdev

and some more crap

* Input: finish work on remapping Xinput and DS4

* Input: add DS4 Colors to DS4 config

* Input: Improve DS4 deadzone scaling

Jarves made some mistakes, so I'll fix them in the follow up commit

* Input: fix Jarves fixes on DS4 deadzone

and remove unnecessary usage of toUtf8

* Input: add primitive batterychecks to XInput and DS4

* Input: add mmjoystick remapping

* Input: Fix evdev and some Vibration issues

* Input: adjust capabilities to fix stick input for games like LoS 2

also fix threshold slider minimum
also add ps button to all the handlers

* Input: Further evdev work

based on danilaml code review and own debugging:
Fixed path issue, <= 0 issue, some captures, const, axis with same codes.
Adds a map to each device that differentiates negative and positive axis mappings.
adjusted rest of the file to tabs (ListDevices and beginning of threadProc)

* Input: use 20ms vibration update time for xbox one elite controllers.

* Input: Fix return type of Clamp()

* Input: Evdev Fix

* Input: Evdev Optional GetNextButtonPress

presumably better than the other

* Input: review changes

* Input: evdev: fix wrong index in axis handling

move bindpadtodevice down to keep consistency between handlers and not get crazy

* Input: evdev: fix expensive add_device in GetNextButtonPress

* cleanup

* Input: mmjoy: fix type

* Input: evdev: final fixes

* Input: evdev: exclude unnecessary buttons while mapping Xbox 360 or DS4

* Input: add deadzone preview by passing necessary values in callback

use 0.5 of max value for threshold in pad dialog

* Input: get rid of all-uppercase variables
This commit is contained in:
Megamouse 2017-11-27 22:31:15 +01:00 committed by Ivan
parent 695b4c1f06
commit 662fe8cc95
33 changed files with 3573 additions and 1796 deletions

View file

@ -118,7 +118,7 @@ namespace cfg
bool m_value;
public:
const bool def;
bool def;
_bool(node* owner, const std::string& name, bool def = false)
: _base(type::_bool, owner, name)
@ -150,6 +150,11 @@ namespace cfg
return true;
}
void set(const bool& value)
{
m_value = value;
}
};
// Value node with fixed set of possible values, each maps to an enum value of type T.
@ -217,7 +222,7 @@ namespace cfg
int_type m_value;
public:
const int_type def;
int_type def;
_int(node* owner, const std::string& name, int_type def = std::min<int_type>(Max, std::max<int_type>(Min, 0)))
: _base(type::_int, owner, name)
@ -253,6 +258,11 @@ namespace cfg
return false;
}
void set(const s64& value)
{
m_value = static_cast<int_type>(value);
}
std::vector<std::string> to_list() const override
{
return make_int_range(Min, Max);
@ -271,7 +281,7 @@ namespace cfg
std::string m_value;
public:
const std::string def;
std::string def;
string(node* owner, const std::string& name, const std::string& def = {})
: _base(type::string, owner, name)

View file

@ -1,7 +1,10 @@
#pragma once
#include <cmath>
#include <vector>
#include <memory>
#include "stdafx.h"
#include "../../Utilities/Config.h"
#include "../../Utilities/types.h"
// TODO: HLE info (constants, structs, etc.) should not be available here
@ -159,6 +162,13 @@ struct Pad
u32 m_device_capability;
u32 m_device_type;
// Cable State: 0 - 1 plugged in ?
u8 m_cable_state;
// DS4: 0 - 9 while unplugged, 0 - 10 while plugged in, 11 charge complete
// XInput: 0 = Empty, 1 = Low, 2 = Medium, 3 = Full
u8 m_battery_level;
std::vector<Button> m_buttons;
std::vector<AnalogStick> m_sticks;
std::vector<AnalogSensor> m_sensors;
@ -238,23 +248,280 @@ struct Pad
}
};
struct pad_config : cfg::node
{
std::string cfg_type = "";
std::string cfg_name = "";
cfg::string ls_left { this, "Left Stick Left", "" };
cfg::string ls_down { this, "Left Stick Down", "" };
cfg::string ls_right{ this, "Left Stick Right", "" };
cfg::string ls_up { this, "Left Stick Up", "" };
cfg::string rs_left { this, "Right Stick Left", "" };
cfg::string rs_down { this, "Right Stick Down", "" };
cfg::string rs_right{ this, "Right Stick Right", "" };
cfg::string rs_up { this, "Right Stick Up", "" };
cfg::string start { this, "Start", "" };
cfg::string select { this, "Select", "" };
cfg::string ps { this, "PS Button", "" };
cfg::string square { this, "Square", "" };
cfg::string cross { this, "Cross", "" };
cfg::string circle { this, "Circle", "" };
cfg::string triangle{ this, "Triangle", "" };
cfg::string left { this, "Left", "" };
cfg::string down { this, "Down", "" };
cfg::string right { this, "Right", "" };
cfg::string up { this, "Up", "" };
cfg::string r1 { this, "R1", "" };
cfg::string r2 { this, "R2", "" };
cfg::string r3 { this, "R3", "" };
cfg::string l1 { this, "L1", "" };
cfg::string l2 { this, "L2", "" };
cfg::string l3 { this, "L3", "" };
cfg::_int<0, 1000000> lstickdeadzone{ this, "Left Stick Deadzone", 0 };
cfg::_int<0, 1000000> rstickdeadzone{ this, "Right Stick Deadzone", 0 };
cfg::_int<0, 1000000> ltriggerthreshold{ this, "Left Trigger Threshold", 0 };
cfg::_int<0, 1000000> rtriggerthreshold{ this, "Right Trigger Threshold", 0 };
cfg::_int<0, 1000000> padsquircling{ this, "Pad Squircling Factor", 0 };
cfg::_int<0, 255> colorR{ this, "Color Value R", 0 };
cfg::_int<0, 255> colorG{ this, "Color Value G", 0 };
cfg::_int<0, 255> colorB{ this, "Color Value B", 0 };
cfg::_bool enable_vibration_motor_large{ this, "Enable Large Vibration Motor", true };
cfg::_bool enable_vibration_motor_small{ this, "Enable Small Vibration Motor", true };
cfg::_bool switch_vibration_motors{ this, "Switch Vibration Motors", false };
bool load()
{
if (fs::file cfg_file{ cfg_name, fs::read })
{
return from_string(cfg_file.to_string());
}
return false;
}
void save()
{
fs::file(cfg_name, fs::rewrite).write(to_string());
}
bool exist()
{
return fs::is_file(cfg_name);
}
};
class PadHandlerBase
{
protected:
static const u32 MAX_GAMEPADS = 7;
int m_trigger_threshold = 0;
int m_thumb_threshold = 0;
bool b_has_deadzones = false;
bool b_has_rumble = false;
bool b_has_config = false;
pad_config m_pad_config;
template <typename T>
T lerp(T v0, T v1, T t) {
return std::fma(t, v1, std::fma(-t, v0, v0));
}
// Search an unordered map for a string value and return found keycode
int FindKeyCode(std::unordered_map<u32, std::string> map, const std::string& name)
{
for (auto it = map.begin(); it != map.end(); ++it)
{
if (it->second == name) return it->first;
}
return -1;
};
long FindKeyCode(std::unordered_map<u64, std::string> map, const std::string& name)
{
for (auto it = map.begin(); it != map.end(); ++it)
{
if (it->second == name) return it->first;
}
return -1;
};
// Get normalized trigger value based on the range defined by a threshold
u16 NormalizeTriggerInput(u16 value, int threshold)
{
if (value <= threshold || threshold >= trigger_max)
{
return static_cast<u16>(0);
}
else if (threshold <= trigger_min)
{
return value;
}
else
{
return (u16)(float(trigger_max) * float(value - threshold) / float(trigger_max - threshold));
}
};
// Get new scaled value between 0 and 255 based on its minimum and maximum
float ScaleStickInput(s32 raw_value, int minimum, int maximum)
{
// value based on max range converted to [0, 1]
float val = float(Clamp(raw_value, minimum, maximum) - minimum) / float(abs(maximum) + abs(minimum));
return 255.0f * val;
};
// normalizes a directed input, meaning it will correspond to a single "button" and not an axis with two directions
// the input values must lie in 0+
u16 NormalizeDirectedInput(u16 raw_value, float threshold, float maximum)
{
if (threshold >= maximum || maximum <= 0)
{
return static_cast<u16>(0);
}
float val = float(Clamp(raw_value, 0, maximum)) / maximum; // value based on max range converted to [0, 1]
if (threshold <= 0)
{
return static_cast<u16>(255.0f * val);
}
else
{
float thresh = threshold / maximum; // threshold converted to [0, 1]
return static_cast<u16>(255.0f * std::min(1.0f, (val - thresh) / (1.0f - thresh)));
}
};
u16 NormalizeStickInput(s32 raw_value, int threshold, bool ignore_threshold = false)
{
if (ignore_threshold)
{
return static_cast<u16>(ScaleStickInput(raw_value, 0, thumb_max));
}
else
{
return NormalizeDirectedInput(raw_value, threshold, thumb_max);
}
}
// This function normalizes stick deadzone based on the DS3's deadzone, which is ~13%
// X and Y is expected to be in (-255) to 255 range, deadzone should be in terms of thumb stick range
// return is new x and y values in 0-255 range
std::tuple<u16, u16> NormalizeStickDeadzone(s32 inX, s32 inY, u32 deadzone)
{
const float dzRange = deadzone / float((std::abs(thumb_max) + std::abs(thumb_min)));
float X = inX / 255.0f;
float Y = inY / 255.0f;
if (dzRange > 0.f)
{
const float mag = std::min(sqrtf(X*X + Y*Y), 1.f);
if (mag <= 0)
{
return std::tuple<u16, u16>(ConvertAxis(X), ConvertAxis(Y));
}
if (mag > dzRange) {
float pos = lerp(0.13f, 1.f, (mag - dzRange) / (1 - dzRange));
float scale = pos / mag;
X = X * scale;
Y = Y * scale;
}
else {
float pos = lerp(0.f, 0.13f, mag / dzRange);
float scale = pos / mag;
X = X * scale;
Y = Y * scale;
}
}
return std::tuple<u16, u16>( ConvertAxis(X), ConvertAxis(Y) );
};
// get clamped value between min and max
s32 Clamp(f32 input, s32 min, s32 max)
{
if (input > max)
return max;
else if (input < min)
return min;
else return static_cast<s32>(input);
};
// get clamped value between 0 and 255
u16 Clamp0To255(f32 input)
{
return static_cast<u16>(Clamp(input, 0, 255));
};
// get clamped value between 0 and 1023
u16 Clamp0To1023(f32 input)
{
return static_cast<u16>(Clamp(input, 0, 1023));
}
// input has to be [-1,1]. result will be [0,255]
u16 ConvertAxis(float value)
{
return static_cast<u16>((value + 1.0)*(255.0 / 2.0));
};
// 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
// this might be the best for now, in practice it seems to push the corners to max of 20x20, with a squircle_factor of 8000
// This function assumes inX and inY is already in 0-255
std::tuple<u16, u16> ConvertToSquirclePoint(u16 inX, u16 inY, float squircle_factor)
{
// convert inX and Y to a (-1, 1) vector;
const f32 x = ((f32)inX - 127.5f) / 127.5f;
const f32 y = ((f32)inY - 127.5f) / 127.5f;
// compute angle and len of given point to be used for squircle radius
const f32 angle = std::atan2(y, x);
const f32 r = std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f));
// now find len/point on the given squircle from our current angle and radius in polar coords
// https://thatsmaths.com/2016/07/14/squircles/
const f32 newLen = (1 + std::pow(std::sin(2 * angle), 2.f) / (squircle_factor / 1000.f)) * r;
// we now have len and angle, convert to cartisian
const int newX = Clamp0To255(((newLen * std::cos(angle)) + 1) * 127.5f);
const int newY = Clamp0To255(((newLen * std::sin(angle)) + 1) * 127.5f);
return std::tuple<u16, u16>(newX, newY);
}
public:
s32 thumb_min = 0;
s32 thumb_max = 255;
s32 trigger_min = 0;
s32 trigger_max = 255;
s32 vibration_min = 0;
s32 vibration_max = 255;
virtual bool Init() { return true; };
virtual ~PadHandlerBase() = default;
//Does it have GUI Config?
bool has_config() { return b_has_config; };
bool has_rumble() { return b_has_rumble; };
bool has_deadzones() { return b_has_deadzones; };
pad_config* GetConfig() { return &m_pad_config; };
//Sets window to config the controller(optional)
virtual void ConfigController(std::string device) {};
virtual void GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback) {};
virtual void TestVibration(const std::string& padId, u32 largeMotor, u32 smallMotor) {};
//Return list of devices for that handler
virtual std::vector<std::string> ListDevices() = 0;
//Callback called during pad_thread::ThreadFunc
virtual void ThreadProc() = 0;
//Binds a Pad to a device
virtual bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) = 0;
private:
virtual void TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold = false) {};
};

File diff suppressed because it is too large Load diff

View file

@ -7,10 +7,77 @@
#include <limits>
#include <unordered_map>
const u32 MAX_GAMEPADS = 7;
class ds4_pad_handler final : public PadHandlerBase
{
// These are all the possible buttons on a standard DS4 controller
// The touchpad is restricted to its button for now (or forever?)
enum DS4KeyCodes
{
Triangle = 0,
Circle,
Cross,
Square,
Left,
Right,
Up,
Down,
R1,
//R2But,
R3,
L1,
//L2But,
L3,
Share,
Options,
PSButton,
TouchPad,
L2,
R2,
LSXNeg,
LSXPos,
LSYNeg,
LSYPos,
RSXNeg,
RSXPos,
RSYNeg,
RSYPos,
KeyCodeCount
};
// Unique names for the config files and our pad settings dialog
const std::unordered_map<u32, std::string> button_list =
{
{ DS4KeyCodes::Triangle, "Triangle" },
{ DS4KeyCodes::Circle, "Circle" },
{ DS4KeyCodes::Cross, "Cross" },
{ DS4KeyCodes::Square, "Square" },
{ DS4KeyCodes::Left, "Left" },
{ DS4KeyCodes::Right, "Right" },
{ DS4KeyCodes::Up, "Up" },
{ DS4KeyCodes::Down, "Down" },
{ DS4KeyCodes::R1, "R1" },
{ DS4KeyCodes::R2, "R2" },
{ DS4KeyCodes::R3, "R3" },
{ DS4KeyCodes::Options, "Options" },
{ DS4KeyCodes::Share, "Share" },
{ DS4KeyCodes::PSButton, "PS Button" },
{ DS4KeyCodes::TouchPad, "Touch Pad" },
{ DS4KeyCodes::L1, "L1" },
{ DS4KeyCodes::L2, "L2" },
{ DS4KeyCodes::L3, "L3" },
{ DS4KeyCodes::LSXNeg, "LS X-" },
{ DS4KeyCodes::LSXPos, "LS X+" },
{ DS4KeyCodes::LSYPos, "LS Y+" },
{ DS4KeyCodes::LSYNeg, "LS Y-" },
{ DS4KeyCodes::RSXNeg, "RS X-" },
{ DS4KeyCodes::RSXPos, "RS X+" },
{ DS4KeyCodes::RSYPos, "RS Y+" },
{ DS4KeyCodes::RSYNeg, "RS Y-" }
};
enum DS4CalibIndex
{
// gyro
@ -32,6 +99,13 @@ class ds4_pad_handler final : public PadHandlerBase
s32 sensDenom;
};
enum class DS4DataStatus
{
NewData,
NoNewData,
ReadError,
};
struct DS4Device
{
hid_device* hidDevice{ nullptr };
@ -43,6 +117,10 @@ class ds4_pad_handler final : public PadHandlerBase
u8 largeVibrate{ 0 };
u8 smallVibrate{ 0 };
std::array<u8, 64> padData;
u8 batteryLevel{ 0 };
u8 cableState{ 0 };
u8 led_delay_on{ 0 };
u8 led_delay_off{ 0 };
};
const u16 DS4_VID = 0x054C;
@ -63,6 +141,8 @@ public:
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& buttonCallback) override;
void TestVibration(const std::string& padId, u32 largeMotor, u32 smallMotor) override;
private:
bool is_init;
@ -73,11 +153,16 @@ private:
std::vector<std::pair<std::shared_ptr<DS4Device>, std::shared_ptr<Pad>>> bindings;
private:
void ProcessData();
void UpdateRumble();
bool GetCalibrationData(std::shared_ptr<DS4Device> ds4Device);
void TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold = false) override;
void ProcessDataToPad(const std::shared_ptr<DS4Device>& ds4Device, const std::shared_ptr<Pad>& pad);
// Copies data into padData if status is NewData, otherwise buffer is untouched
DS4DataStatus GetRawData(const std::shared_ptr<DS4Device>& ds4Device);
// This function gets us usuable buffer from the rawbuffer of padData
// Todo: this currently only handles 'buttons' and not axis or sensors for the time being
std::array<u16, DS4KeyCodes::KeyCodeCount> GetButtonValues(const std::shared_ptr<DS4Device>& ds4Device);
bool GetCalibrationData(const std::shared_ptr<DS4Device>& ds4Device);
void CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo);
void SendVibrateData(const std::shared_ptr<DS4Device> device);
int SendVibrateData(const std::shared_ptr<DS4Device>& device);
inline s16 ApplyCalibration(s32 rawValue, const DS4CalibData& calibData)
{
const s32 biased = rawValue - calibData.bias;

File diff suppressed because it is too large Load diff

View file

@ -8,91 +8,259 @@
#include <vector>
#include <thread>
enum { EVDEV_DPAD_HAT_AXIS_X = -1, EVDEV_DPAD_HAT_AXIS_Y = -2 };
struct evdev_joystick_config final : cfg::node
{
const std::string cfg_name = fs::get_config_dir() + "/config_linuxjoystick.yml";
cfg::int32 select{this, "Select", 6};
cfg::int32 start{this, "Start", 7};
cfg::int32 cross{this, "Cross", 0};
cfg::int32 circle{this, "Circle", 1};
cfg::int32 square{this, "Square", 2};
cfg::int32 triangle{this, "Triangle", 3};
cfg::int32 r1{this, "R1", 5};
cfg::int32 r2{this, "R2", 11};
cfg::int32 r3{this, "R3", 10};
cfg::int32 l1{this, "L1", 4};
cfg::int32 l2{this, "L2", 12};
cfg::int32 l3{this, "L3", 9};
cfg::int32 up{this, "Up", EVDEV_DPAD_HAT_AXIS_Y};
cfg::int32 down{this, "Down", EVDEV_DPAD_HAT_AXIS_Y};
cfg::int32 left{this, "Left", EVDEV_DPAD_HAT_AXIS_X};
cfg::int32 right{this, "Right", EVDEV_DPAD_HAT_AXIS_X};
cfg::int32 rxstick{this, "Right stick X axis", 0};
cfg::int32 rystick{this, "Right stick Y axis", 1};
cfg::int32 lxstick{this, "Left stick X axis", 2};
cfg::int32 lystick{this, "Left stick Y axis", 3};
cfg::_bool rxreverse{this, "Reverse right stick X axis", false};
cfg::_bool ryreverse{this, "Reverse right stick Y axis", false};
cfg::_bool lxreverse{this, "Reverse left stick X axis", false};
cfg::_bool lyreverse{this, "Reverse left stick Y axis", false};
cfg::_bool axistrigger{this, "Z axis triggers", true};
cfg::_bool squirclejoysticks{this, "Squircle Joysticks", true};
cfg::int32 squirclefactor{this, "Squircle Factor", 5000};
cfg::int32 deadzone{this, "Deadzone", 10};
cfg::_bool left_analog_to_dpad{this, "Left Analog to Dpad", false};
bool load()
{
if (fs::file cfg_file{ cfg_name, fs::read })
{
return from_string(cfg_file.to_string());
}
return false;
}
void save()
{
fs::file(cfg_name, fs::rewrite).write(to_string());
}
};
class evdev_joystick_handler final : public PadHandlerBase
{
public:
evdev_joystick_handler();
~evdev_joystick_handler();
// Unique button names for the config files and our pad settings dialog
const std::unordered_map<u32, std::string> button_list =
{
//{ BTN_MISC , "Misc" }, same as BTN_0
{ BTN_0 , "0" },
{ BTN_1 , "1" },
{ BTN_2 , "2" },
{ BTN_3 , "3" },
{ BTN_4 , "4" },
{ BTN_5 , "5" },
{ BTN_6 , "6" },
{ BTN_7 , "7" },
{ BTN_8 , "8" },
{ BTN_9 , "9" },
//{ BTN_MOUSE , "Mouse" }, same as BTN_LEFT
{ BTN_LEFT , "Left" },
{ BTN_RIGHT , "Right" },
{ BTN_MIDDLE , "Middle" },
{ BTN_SIDE , "Side" },
{ BTN_EXTRA , "Extra" },
{ BTN_FORWARD , "Forward" },
{ BTN_BACK , "Back" },
{ BTN_TASK , "Task" },
{ BTN_JOYSTICK , "Joystick" },
{ BTN_TRIGGER , "Trigger" },
{ BTN_THUMB , "Thumb" },
{ BTN_THUMB2 , "Thumb 2" },
{ BTN_TOP , "Top" },
{ BTN_TOP2 , "Top 2" },
{ BTN_PINKIE , "Pinkie" },
{ BTN_BASE , "Base" },
{ BTN_BASE2 , "Base 2" },
{ BTN_BASE3 , "Base 3" },
{ BTN_BASE4 , "Base 4" },
{ BTN_BASE5 , "Base 5" },
{ BTN_BASE6 , "Base 6" },
{ BTN_DEAD , "Dead" },
//{ BTN_GAMEPAD , "Gamepad" }, same as BTN_A
//{ BTN_SOUTH , "South" }, same as BTN_A
{ BTN_A , "A" },
//{ BTN_EAST , "South" }, same as BTN_B
{ BTN_B , "B" },
{ BTN_C , "C" },
//{ BTN_NORTH , "North" }, same as BTN_X
{ BTN_X , "X" },
//{ BTN_WEST , "West" }, same as BTN_Y
{ BTN_Y , "Y" },
{ BTN_Z , "Z" },
{ BTN_TL , "TL" },
{ BTN_TR , "TR" },
{ BTN_TL2 , "TL 2" },
{ BTN_TR2 , "TR 2" },
{ BTN_SELECT , "Select" },
{ BTN_START , "Start" },
{ BTN_MODE , "Mode" },
{ BTN_THUMBL , "Thumb L" },
{ BTN_THUMBR , "Thumb R" },
//{ BTN_DIGI , "Digi" }, same as BTN_TOOL_PEN
{ BTN_TOOL_PEN , "Pen" },
{ BTN_TOOL_RUBBER , "Rubber" },
{ BTN_TOOL_BRUSH , "Brush" },
{ BTN_TOOL_PENCIL , "Pencil" },
{ BTN_TOOL_AIRBRUSH , "Airbrush" },
{ BTN_TOOL_FINGER , "Finger" },
{ BTN_TOOL_MOUSE , "Mouse" },
{ BTN_TOOL_LENS , "Lense" },
{ BTN_TOOL_QUINTTAP , "Quinttap" },
{ BTN_TOUCH , "Touch" },
{ BTN_STYLUS , "Stylus" },
{ BTN_STYLUS2 , "Stylus 2" },
{ BTN_TOOL_DOUBLETAP , "Doubletap" },
{ BTN_TOOL_TRIPLETAP , "Tripletap" },
{ BTN_TOOL_QUADTAP , "Quadtap" },
//{ BTN_WHEEL , "Wheel" }, same as BTN_GEAR_DOWN
{ BTN_GEAR_DOWN , "Gear Up" },
{ BTN_GEAR_UP , "Gear Down" },
{ BTN_DPAD_UP , "D-Pad Up" },
{ BTN_DPAD_DOWN , "D-Pad Down" },
{ BTN_DPAD_LEFT , "D-Pad Left" },
{ BTN_DPAD_RIGHT , "D-Pad Right" },
{ BTN_TRIGGER_HAPPY , "Happy" },
{ BTN_TRIGGER_HAPPY1 , "Happy 1" },
{ BTN_TRIGGER_HAPPY2 , "Happy 2" },
{ BTN_TRIGGER_HAPPY3 , "Happy 3" },
{ BTN_TRIGGER_HAPPY4 , "Happy 4" },
{ BTN_TRIGGER_HAPPY5 , "Happy 5" },
{ BTN_TRIGGER_HAPPY6 , "Happy 6" },
{ BTN_TRIGGER_HAPPY7 , "Happy 7" },
{ BTN_TRIGGER_HAPPY8 , "Happy 8" },
{ BTN_TRIGGER_HAPPY9 , "Happy 9" },
{ BTN_TRIGGER_HAPPY10 , "Happy 10" },
{ BTN_TRIGGER_HAPPY11 , "Happy 11" },
{ BTN_TRIGGER_HAPPY12 , "Happy 12" },
{ BTN_TRIGGER_HAPPY13 , "Happy 13" },
{ BTN_TRIGGER_HAPPY14 , "Happy 14" },
{ BTN_TRIGGER_HAPPY15 , "Happy 15" },
{ BTN_TRIGGER_HAPPY16 , "Happy 16" },
{ BTN_TRIGGER_HAPPY17 , "Happy 17" },
{ BTN_TRIGGER_HAPPY18 , "Happy 18" },
{ BTN_TRIGGER_HAPPY19 , "Happy 19" },
{ BTN_TRIGGER_HAPPY20 , "Happy 20" },
{ BTN_TRIGGER_HAPPY21 , "Happy 21" },
{ BTN_TRIGGER_HAPPY22 , "Happy 22" },
{ BTN_TRIGGER_HAPPY23 , "Happy 23" },
{ BTN_TRIGGER_HAPPY24 , "Happy 24" },
{ BTN_TRIGGER_HAPPY25 , "Happy 25" },
{ BTN_TRIGGER_HAPPY26 , "Happy 26" },
{ BTN_TRIGGER_HAPPY27 , "Happy 27" },
{ BTN_TRIGGER_HAPPY28 , "Happy 28" },
{ BTN_TRIGGER_HAPPY29 , "Happy 29" },
{ BTN_TRIGGER_HAPPY30 , "Happy 30" },
{ BTN_TRIGGER_HAPPY31 , "Happy 31" },
{ BTN_TRIGGER_HAPPY32 , "Happy 32" },
{ BTN_TRIGGER_HAPPY33 , "Happy 33" },
{ BTN_TRIGGER_HAPPY34 , "Happy 34" },
{ BTN_TRIGGER_HAPPY35 , "Happy 35" },
{ BTN_TRIGGER_HAPPY36 , "Happy 36" },
{ BTN_TRIGGER_HAPPY37 , "Happy 37" },
{ BTN_TRIGGER_HAPPY38 , "Happy 38" },
{ BTN_TRIGGER_HAPPY39 , "Happy 39" },
{ BTN_TRIGGER_HAPPY40 , "Happy 40" },
};
bool Init() override;
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void Close();
// Unique positive axis names for the config files and our pad settings dialog
const std::unordered_map<u32, std::string> axis_list =
{
{ ABS_X , "LX+" },
{ ABS_Y , "LY+" },
{ ABS_Z , "LZ+" },
{ ABS_RX , "RX+" },
{ ABS_RY , "RY+" },
{ ABS_RZ , "RZ+" },
{ ABS_THROTTLE , "Throttle+" },
{ ABS_RUDDER , "Rudder+" },
{ ABS_WHEEL , "Wheel+" },
{ ABS_GAS , "Gas+" },
{ ABS_BRAKE , "Brake+" },
{ ABS_HAT0X , "Hat0 X+" },
{ ABS_HAT0Y , "Hat0 Y+" },
{ ABS_HAT1X , "Hat1 X+" },
{ ABS_HAT1Y , "Hat1 Y+" },
{ ABS_HAT2X , "Hat2 X+" },
{ ABS_HAT2Y , "Hat2 Y+" },
{ ABS_HAT3X , "Hat3 X+" },
{ ABS_HAT3Y , "Hat3 Y+" },
{ ABS_PRESSURE , "Pressure+" },
{ ABS_DISTANCE , "Distance+" },
{ ABS_TILT_X , "Tilt X+" },
{ ABS_TILT_Y , "Tilt Y+" },
{ ABS_TOOL_WIDTH , "Width+" },
{ ABS_VOLUME , "Volume+" },
{ ABS_MISC , "Misc+" },
{ ABS_MT_SLOT , "Slot+" },
{ ABS_MT_TOUCH_MAJOR , "MT TMaj+" },
{ ABS_MT_TOUCH_MINOR , "MT TMin+" },
{ ABS_MT_WIDTH_MAJOR , "MT WMaj+" },
{ ABS_MT_WIDTH_MINOR , "MT WMin+" },
{ ABS_MT_ORIENTATION , "MT Orient+" },
{ ABS_MT_POSITION_X , "MT PosX+" },
{ ABS_MT_POSITION_Y , "MT PosY+" },
{ ABS_MT_TOOL_TYPE , "MT TType+" },
{ ABS_MT_BLOB_ID , "MT Blob ID+" },
{ ABS_MT_TRACKING_ID , "MT Track ID+" },
{ ABS_MT_PRESSURE , "MT Pressure+" },
{ ABS_MT_DISTANCE , "MT Distance+" },
{ ABS_MT_TOOL_X , "MT Tool X+" },
{ ABS_MT_TOOL_Y , "MT Tool Y+" },
};
// Unique negative axis names for the config files and our pad settings dialog
const std::unordered_map<u32, std::string> rev_axis_list =
{
{ ABS_X , "LX-" },
{ ABS_Y , "LY-" },
{ ABS_Z , "LZ-" },
{ ABS_RX , "RX-" },
{ ABS_RY , "RY-" },
{ ABS_RZ , "RZ-" },
{ ABS_THROTTLE , "Throttle-" },
{ ABS_RUDDER , "Rudder-" },
{ ABS_WHEEL , "Wheel-" },
{ ABS_GAS , "Gas-" },
{ ABS_BRAKE , "Brake-" },
{ ABS_HAT0X , "Hat0 X-" },
{ ABS_HAT0Y , "Hat0 Y-" },
{ ABS_HAT1X , "Hat1 X-" },
{ ABS_HAT1Y , "Hat1 Y-" },
{ ABS_HAT2X , "Hat2 X-" },
{ ABS_HAT2Y , "Hat2 Y-" },
{ ABS_HAT3X , "Hat3 X-" },
{ ABS_HAT3Y , "Hat3 Y-" },
{ ABS_PRESSURE , "Pressure-" },
{ ABS_DISTANCE , "Distance-" },
{ ABS_TILT_X , "Tilt X-" },
{ ABS_TILT_Y , "Tilt Y-" },
{ ABS_TOOL_WIDTH , "Width-" },
{ ABS_VOLUME , "Volume-" },
{ ABS_MISC , "Misc-" },
{ ABS_MT_SLOT , "Slot-" },
{ ABS_MT_TOUCH_MAJOR , "MT TMaj-" },
{ ABS_MT_TOUCH_MINOR , "MT TMin-" },
{ ABS_MT_WIDTH_MAJOR , "MT WMaj-" },
{ ABS_MT_WIDTH_MINOR , "MT WMin-" },
{ ABS_MT_ORIENTATION , "MT Orient-" },
{ ABS_MT_POSITION_X , "MT PosX-" },
{ ABS_MT_POSITION_Y , "MT PosY-" },
{ ABS_MT_TOOL_TYPE , "MT TType-" },
{ ABS_MT_BLOB_ID , "MT Blob ID-" },
{ ABS_MT_TRACKING_ID , "MT Track ID-" },
{ ABS_MT_PRESSURE , "MT Pressure-" },
{ ABS_MT_DISTANCE , "MT Distance-" },
{ ABS_MT_TOOL_X , "MT Tool X-" },
{ ABS_MT_TOOL_Y , "MT Tool Y-" },
};
struct EvdevDevice
{
libevdev* device;
std::string path;
std::shared_ptr<Pad> pad;
std::unordered_map<int, bool> axis_orientations; // value is true if key was found in rev_axis_list
float stick_val[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
u16 val_min[4] = { 0, 0, 0, 0 };
u16 val_max[4] = { 0, 0, 0, 0 };
};
const int BUTTON_COUNT = 17;
public:
evdev_joystick_handler();
~evdev_joystick_handler();
bool Init() override;
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void Close();
void GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback) override;
void TestVibration(const std::string& padId, u32 largeMotor, u32 smallMotor) override;
private:
void update_devs();
std::tuple<u16, u16> ConvertToSquirclePoint(u16 inX, u16 inY);
bool try_open_dev(u32 index);
int scale_axis(int axis, int value);
void TranslateButtonPress(u64 keyCode, bool& pressed, u16& value, bool ignore_threshold = false) override;
bool update_device(EvdevDevice& device, bool use_cell = true);
void update_devs(bool use_cell = true);
int add_device(const std::string& device, bool in_settings = false, std::shared_ptr<Pad> pad = nullptr, const std::unordered_map<int, bool>& axis_map = std::unordered_map<int, bool>());
int GetButtonInfo(const input_event& evt, libevdev* dev, int& button_code, bool& is_negative);
std::unordered_map<u64, std::pair<u16, bool>> GetButtonValues(libevdev* dev);
std::vector<std::string> joy_paths;
std::vector<std::shared_ptr<Pad>> pads;
std::vector<libevdev*> joy_devs;
std::vector<std::vector<int>> joy_button_maps;
std::vector<std::vector<int>> joy_axis_maps;
// joy_axis is only used for squircling
std::vector<std::vector<int>> joy_axis;
std::vector<int> joy_hat_ids;
bool axistrigger;
std::map<int, std::pair<int, int>> axis_ranges;
std::vector<bool> revaxis;
// Search axis_orientations map for the direction by index, returns -1 if not found, 0 for positive and 1 for negative
int FindAxisDirection(const std::unordered_map<int, bool>& map, int index);
std::vector<EvdevDevice> devices;
int m_pad_index = -1;
};

View file

@ -6,7 +6,8 @@
#include "rpcs3qt/pad_settings_dialog.h"
keyboard_pad_config g_kbpad_config;
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
constexpr auto qstr = QString::fromStdString;
bool keyboard_pad_handler::Init()
{
@ -15,17 +16,48 @@ bool keyboard_pad_handler::Init()
keyboard_pad_handler::keyboard_pad_handler() : QObject()
{
b_has_config = true;
}
// Set this handler's type and save location
m_pad_config.cfg_type = "keyboard";
m_pad_config.cfg_name = fs::get_config_dir() + "/config_kbpad_qt.yml";
void keyboard_pad_handler::ConfigController(std::string device)
{
pad_settings_dialog dlg;
dlg.exec();
// Set default button mapping
m_pad_config.ls_left.def = GetKeyName(Qt::Key_A);
m_pad_config.ls_down.def = GetKeyName(Qt::Key_S);
m_pad_config.ls_right.def = GetKeyName(Qt::Key_D);
m_pad_config.ls_up.def = GetKeyName(Qt::Key_W);
m_pad_config.rs_left.def = GetKeyName(Qt::Key_Home);
m_pad_config.rs_down.def = GetKeyName(Qt::Key_PageDown);
m_pad_config.rs_right.def = GetKeyName(Qt::Key_End);
m_pad_config.rs_up.def = GetKeyName(Qt::Key_PageUp);
m_pad_config.start.def = GetKeyName(Qt::Key_Return);
m_pad_config.select.def = GetKeyName(Qt::Key_Space);
m_pad_config.ps.def = GetKeyName(Qt::Key_Backspace);
m_pad_config.square.def = GetKeyName(Qt::Key_Z);
m_pad_config.cross.def = GetKeyName(Qt::Key_X);
m_pad_config.circle.def = GetKeyName(Qt::Key_C);
m_pad_config.triangle.def = GetKeyName(Qt::Key_V);
m_pad_config.left.def = GetKeyName(Qt::Key_Left);
m_pad_config.down.def = GetKeyName(Qt::Key_Down);
m_pad_config.right.def = GetKeyName(Qt::Key_Right);
m_pad_config.up.def = GetKeyName(Qt::Key_Up);
m_pad_config.r1.def = GetKeyName(Qt::Key_E);
m_pad_config.r2.def = GetKeyName(Qt::Key_T);
m_pad_config.r3.def = GetKeyName(Qt::Key_G);
m_pad_config.l1.def = GetKeyName(Qt::Key_Q);
m_pad_config.l2.def = GetKeyName(Qt::Key_R);
m_pad_config.l3.def = GetKeyName(Qt::Key_F);
// apply defaults
m_pad_config.from_default();
// set capabilities
b_has_config = true;
}
void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value)
{
value = Clamp0To255(value);
for (auto pad : bindings)
{
for (Button& button : pad->m_buttons)
@ -33,13 +65,10 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value)
if (button.m_keyCode != code)
continue;
if (value >= 256) { value = 255; }
//Todo: Is this flush necessary once games hit decent speeds?
if (button.m_pressed && !pressed)
{
button.m_flush = true;
}
else
{
@ -76,7 +105,6 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value)
}
}
bool keyboard_pad_handler::eventFilter(QObject* target, QEvent* ev)
{
// !m_target is for future proofing when gsrender isn't automatically initialized on load.
@ -123,36 +151,29 @@ void keyboard_pad_handler::keyPressEvent(QKeyEvent* event)
{
case Qt::Key_L:
if (!(event->modifiers() == Qt::AltModifier)) { Key(event->key(), 1); }
event->ignore();
break;
case Qt::Key_Return:
if (!(event->modifiers() == Qt::AltModifier)) { Key(event->key(), 1); }
event->ignore();
break;
case Qt::Key_Escape:
event->ignore();
break;
case Qt::Key_P:
if (!(event->modifiers() == Qt::ControlModifier)) { Key(event->key(), 1); }
event->ignore();
break;
case Qt::Key_S:
if (!(event->modifiers() == Qt::ControlModifier)) { Key(event->key(), 1); }
event->ignore();
break;
case Qt::Key_R:
if (!(event->modifiers() == Qt::ControlModifier)) { Key(event->key(), 1); }
event->ignore();
break;
case Qt::Key_E:
if (!(event->modifiers() == Qt::ControlModifier)) { Key(event->key(), 1); }
event->ignore();
break;
default:
Key(event->key(), 1);
event->ignore();
break;
}
event->ignore();
}
void keyboard_pad_handler::keyReleaseEvent(QKeyEvent* event)
@ -174,42 +195,119 @@ std::vector<std::string> keyboard_pad_handler::ListDevices()
return list_devices;
}
std::string keyboard_pad_handler::GetKeyName(const QKeyEvent* keyEvent)
{
//TODO what about numpad?
// Fix some unknown button names
switch (keyEvent->key())
{
case Qt::Key_Alt:
return "Alt";
case Qt::Key_AltGr:
return "AltGr";
case Qt::Key_Shift:
return "Shift";
case Qt::Key_Control:
return "Ctrl";
case Qt::Key_NumLock:
return sstr(QKeySequence(keyEvent->key()).toString(QKeySequence::NativeText));
default:
break;
}
return sstr(QKeySequence(keyEvent->key() | keyEvent->modifiers()).toString(QKeySequence::NativeText));
}
std::string keyboard_pad_handler::GetKeyName(const u32& keyCode)
{
//TODO what about numpad?
return sstr(QKeySequence(keyCode).toString(QKeySequence::NativeText));
}
u32 keyboard_pad_handler::GetKeyCode(const std::string& keyName)
{
if (keyName == "Alt")
return Qt::Key_Alt;
else if (keyName == "AltGr")
return Qt::Key_AltGr;
else if (keyName == "Shift")
return Qt::Key_Shift;
else if (keyName == "Ctrl")
return Qt::Key_Control;
QString key = qstr(keyName);
QKeySequence seq(key);
u32 keyCode;
// We should only working with a single key here
if (seq.count() == 1)
{
keyCode = seq[0];
}
else
{
// Should be here only if a modifier key (e.g. Ctrl, Alt) is pressed.
if (seq.count() != 0)
{
return 0;
}
// Add a non-modifier key "A" to the picture because QKeySequence
// seems to need that to acknowledge the modifier. We know that A has
// a keyCode of 65 (or 0x41 in hex)
seq = QKeySequence(key + "+A");
if (seq.count() != 0 || seq[0] <= 65)
{
return 0;
}
keyCode = seq[0] - 65;
}
return keyCode;
}
bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device)
{
if (device != "Keyboard") return false;
g_kbpad_config.load();
m_pad_config.load();
//Fixed assign change, default is both sensor and press off
pad->Init(
pad->Init
(
CELL_PAD_STATUS_CONNECTED | CELL_PAD_STATUS_ASSIGN_CHANGES,
CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_HP_ANALOG_STICK | CELL_PAD_CAPABILITY_ACTUATOR | CELL_PAD_CAPABILITY_SENSOR_MODE,
CELL_PAD_DEV_TYPE_STANDARD
);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.left, CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.down, CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.right, CELL_PAD_CTRL_RIGHT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.up, CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.start, CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.r3, CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.l3, CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_kbpad_config.select, CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.left), CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.down), CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.right), CELL_PAD_CTRL_RIGHT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.up), CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.start), CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.r3), CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.l3), CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, GetKeyCode(m_pad_config.select), CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.ps), 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x0); // Reserved
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.square), CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.cross), CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.circle), CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.triangle), CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.r1), CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.l1), CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.r2), CELL_PAD_CTRL_R2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, GetKeyCode(m_pad_config.l2), CELL_PAD_CTRL_L2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.square, CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.cross, CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.circle, CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.triangle, CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.r1, CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.l1, CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.r2, CELL_PAD_CTRL_R2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_kbpad_config.l2, CELL_PAD_CTRL_L2);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, GetKeyCode(m_pad_config.ls_left), GetKeyCode(m_pad_config.ls_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, GetKeyCode(m_pad_config.ls_up), GetKeyCode(m_pad_config.ls_down));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, GetKeyCode(m_pad_config.rs_left), GetKeyCode(m_pad_config.rs_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, GetKeyCode(m_pad_config.rs_up), GetKeyCode(m_pad_config.rs_down));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, g_kbpad_config.left_stick_left, g_kbpad_config.left_stick_right);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, g_kbpad_config.left_stick_up, g_kbpad_config.left_stick_down);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, g_kbpad_config.right_stick_left, g_kbpad_config.right_stick_right);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, g_kbpad_config.right_stick_up, g_kbpad_config.right_stick_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_vibrateMotors.emplace_back(true, 0);
pad->m_vibrateMotors.emplace_back(false, 0);
bindings.push_back(pad);

View file

@ -8,51 +8,6 @@
#include <QWindow>
#include <QKeyEvent>
struct keyboard_pad_config final : cfg::node
{
const std::string cfg_name = fs::get_config_dir() + "/config_kbpad_qt.yml";
cfg::int32 left_stick_left{ this, "Left Analog Stick Left", Qt::Key_A };
cfg::int32 left_stick_down{ this, "Left Analog Stick Down", Qt::Key_S };
cfg::int32 left_stick_right{ this, "Left Analog Stick Right", Qt::Key_D };
cfg::int32 left_stick_up{ this, "Left Analog Stick Up", Qt::Key_W };
cfg::int32 right_stick_left{ this, "Right Analog Stick Left", Qt::Key_Home };
cfg::int32 right_stick_down{ this, "Right Analog Stick Down", Qt::Key_PageDown };
cfg::int32 right_stick_right{ this, "Right Analog Stick Right", Qt::Key_End };
cfg::int32 right_stick_up{ this, "Right Analog Stick Up", Qt::Key_PageUp };
cfg::int32 start{ this, "Start", Qt::Key_Return };
cfg::int32 select{ this, "Select", Qt::Key_Space };
cfg::int32 square{ this, "Square", Qt::Key_Z };
cfg::int32 cross{ this, "Cross", Qt::Key_X };
cfg::int32 circle{ this, "Circle", Qt::Key_C };
cfg::int32 triangle{ this, "Triangle", Qt::Key_V };
cfg::int32 left{ this, "Left", Qt::Key_Left };
cfg::int32 down{ this, "Down", Qt::Key_Down };
cfg::int32 right{ this, "Right", Qt::Key_Right };
cfg::int32 up{ this, "Up", Qt::Key_Up };
cfg::int32 r1{ this, "R1", Qt::Key_E };
cfg::int32 r2{ this, "R2", Qt::Key_T };
cfg::int32 r3{ this, "R3", Qt::Key_G };
cfg::int32 l1{ this, "L1", Qt::Key_Q };
cfg::int32 l2{ this, "L2", Qt::Key_R };
cfg::int32 l3{ this, "L3", Qt::Key_F };
bool load()
{
if (fs::file cfg_file{ cfg_name, fs::read })
{
return from_string(cfg_file.to_string());
}
return false;
}
void save()
{
fs::file(cfg_name, fs::rewrite).write(to_string());
}
};
class keyboard_pad_handler final : public QObject, public PadHandlerBase
{
public:
@ -69,7 +24,10 @@ public:
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void ConfigController(std::string device) override;
std::string GetKeyName(const QKeyEvent* keyEvent);
std::string GetKeyName(const u32& keyCode);
u32 GetKeyCode(const std::string& keyName);
protected:
void Key(const u32 code, bool pressed, u16 value = 255);

View file

@ -13,7 +13,7 @@
#include <windows.h>
#endif
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
template <typename... Args>
inline auto tr(Args&&... args)

View file

@ -2,9 +2,8 @@
#ifdef _WIN32
#include "mm_joystick_handler.h"
mm_joystick_config g_mmjoystick_config;
namespace {
namespace
{
const DWORD THREAD_SLEEP = 10;
const DWORD THREAD_SLEEP_INACTIVE = 100;
const DWORD THREAD_TIMEOUT = 1000;
@ -15,9 +14,56 @@ namespace {
}
}
mm_joystick_handler::mm_joystick_handler() : is_init(false)
{
// Set this handler's type and save location
m_pad_config.cfg_type = "mmjoystick";
m_pad_config.cfg_name = fs::get_config_dir() + "/config_mmjoystick.yml";
// Set default button mapping
m_pad_config.ls_left.def = axis_list.at(mmjoy_axis::joy_x_neg);
m_pad_config.ls_down.def = axis_list.at(mmjoy_axis::joy_y_neg);
m_pad_config.ls_right.def = axis_list.at(mmjoy_axis::joy_x_pos);
m_pad_config.ls_up.def = axis_list.at(mmjoy_axis::joy_y_pos);
m_pad_config.rs_left.def = axis_list.at(mmjoy_axis::joy_u_neg);
m_pad_config.rs_down.def = axis_list.at(mmjoy_axis::joy_r_neg);
m_pad_config.rs_right.def = axis_list.at(mmjoy_axis::joy_u_pos);
m_pad_config.rs_up.def = axis_list.at(mmjoy_axis::joy_r_pos);
m_pad_config.start.def = button_list.at(JOY_BUTTON8);
m_pad_config.select.def = button_list.at(JOY_BUTTON7);
m_pad_config.ps.def = button_list.at(JOY_BUTTON17);
m_pad_config.square.def = button_list.at(JOY_BUTTON3);
m_pad_config.cross.def = button_list.at(JOY_BUTTON1);
m_pad_config.circle.def = button_list.at(JOY_BUTTON2);
m_pad_config.triangle.def = button_list.at(JOY_BUTTON4);
m_pad_config.left.def = pov_list.at(JOY_POVLEFT);
m_pad_config.down.def = pov_list.at(JOY_POVBACKWARD);
m_pad_config.right.def = pov_list.at(JOY_POVRIGHT);
m_pad_config.up.def = pov_list.at(JOY_POVFORWARD);
m_pad_config.r1.def = button_list.at(JOY_BUTTON6);
m_pad_config.r2.def = axis_list.at(mmjoy_axis::joy_z_pos);
m_pad_config.r3.def = button_list.at(JOY_BUTTON10);
m_pad_config.l1.def = button_list.at(JOY_BUTTON5);
m_pad_config.l2.def = axis_list.at(mmjoy_axis::joy_z_neg);
m_pad_config.l3.def = button_list.at(JOY_BUTTON9);
// Set default misc variables
m_pad_config.lstickdeadzone.def = 0; // between 0 and 255
m_pad_config.rstickdeadzone.def = 0; // between 0 and 255
m_pad_config.ltriggerthreshold.def = 0; // between 0 and 255
m_pad_config.rtriggerthreshold.def = 0; // between 0 and 255
m_pad_config.padsquircling.def = 8000;
// apply defaults
m_pad_config.from_default();
// set capabilities
b_has_config = true;
b_has_rumble = true;
b_has_deadzones = true;
m_trigger_threshold = trigger_max / 2;
m_thumb_threshold = thumb_max / 2;
}
mm_joystick_handler::~mm_joystick_handler()
@ -50,6 +96,7 @@ bool mm_joystick_handler::Init()
return false;
}
m_pad_config.load();
is_init = true;
return true;
}
@ -69,38 +116,60 @@ bool mm_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::s
{
if (!Init()) return false;
g_mmjoystick_config.load();
auto find_key = [=](const std::string& name)
{
long key = FindKeyCode(button_list, name);
if (key < 0)
key = FindKeyCode(pov_list, name);
if (key < 0)
key = FindKeyCode(axis_list, name);
if (key < 0)
{
LOG_ERROR(HLE, "mmjoystick FindKey(%s) returned value %d", name, key);
key = -1;
}
return key;
};
pad->Init(
CELL_PAD_STATUS_DISCONNECTED,
pad->Init
(
CELL_PAD_STATUS_CONNECTED | CELL_PAD_STATUS_ASSIGN_CHANGES,
CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_HP_ANALOG_STICK | CELL_PAD_CAPABILITY_ACTUATOR | CELL_PAD_CAPABILITY_SENSOR_MODE,
CELL_PAD_DEV_TYPE_STANDARD
);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.triangle, CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.circle, CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.cross, CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.square, CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.l2, CELL_PAD_CTRL_L2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.r2, CELL_PAD_CTRL_R2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.l1, CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_mmjoystick_config.r1, CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_mmjoystick_config.start, CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_mmjoystick_config.select, CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_mmjoystick_config.l3, CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_mmjoystick_config.r3, CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.triangle), CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.circle), CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.cross), CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.square), CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.l2), CELL_PAD_CTRL_L2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.r2), CELL_PAD_CTRL_R2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.l1), CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.r1), CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.start), CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.select), CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.l3), CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.r3), CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(m_pad_config.ps), 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.up), CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.down), CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.left), CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(m_pad_config.right), CELL_PAD_CTRL_RIGHT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x0); // Reserved
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, JOY_POVFORWARD, CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, JOY_POVBACKWARD, CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, JOY_POVLEFT, CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, JOY_POVRIGHT, CELL_PAD_CTRL_RIGHT);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, find_key(m_pad_config.ls_left), find_key(m_pad_config.ls_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, find_key(m_pad_config.ls_down), find_key(m_pad_config.ls_up));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, find_key(m_pad_config.rs_left), find_key(m_pad_config.rs_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, find_key(m_pad_config.rs_down), find_key(m_pad_config.rs_up));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, 0, 0);
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_vibrateMotors.emplace_back(true, 0);
pad->m_vibrateMotors.emplace_back(false, 0);
bindings.push_back(pad);
@ -132,25 +201,205 @@ void mm_joystick_handler::ThreadProc()
pad->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
last_connection_status[i] = true;
pad->m_port_status |= CELL_PAD_STATUS_CONNECTED;
for (DWORD j = 0; j <= 12; j++)
auto button_values = GetButtonValues();
// Translate any corresponding keycodes to our normal DS3 buttons and triggers
for (auto& btn : pad->m_buttons)
{
bool pressed = js_info.dwButtons & pad->m_buttons[j].m_keyCode;
pad->m_buttons[j].m_pressed = pressed;
pad->m_buttons[j].m_value = pressed ? 255 : 0;
btn.m_value = button_values[btn.m_keyCode];
TranslateButtonPress(btn.m_keyCode, btn.m_pressed, btn.m_value);
}
for (DWORD j = 13; j <= 16; j++)//POV aka digital pad
float stick_val[4];
// Translate any corresponding keycodes to our two sticks. (ignoring thresholds for now)
for (int i = 0; i < static_cast<int>(pad->m_sticks.size()); i++)
{
bool pressed = js_info.dwPOV == pad->m_buttons[j].m_keyCode;
pad->m_buttons[j].m_pressed = pressed;
pad->m_buttons[j].m_value = pressed ? 255 : 0;
bool pressed;
// m_keyCodeMin is the mapped key for left or down
u32 key_min = pad->m_sticks[i].m_keyCodeMin;
u16 val_min = button_values[key_min];
TranslateButtonPress(key_min, pressed, val_min, true);
// m_keyCodeMax is the mapped key for right or up
u32 key_max = pad->m_sticks[i].m_keyCodeMax;
u16 val_max = button_values[key_max];
TranslateButtonPress(key_max, pressed, val_max, true);
// cancel out opposing values and get the resulting difference
stick_val[i] = val_max - val_min;
}
pad->m_sticks[0].m_value = ConvertAxis(js_info.dwXpos);
pad->m_sticks[1].m_value = ConvertAxis(js_info.dwYpos);
pad->m_sticks[2].m_value = ConvertAxis(js_info.dwZpos);
pad->m_sticks[3].m_value = ConvertAxis(js_info.dwRpos);
u16 lx, ly, rx, ry;
// Normalize our two stick's axis based on the thresholds
std::tie(lx, ly) = NormalizeStickDeadzone(stick_val[0], stick_val[1], m_pad_config.lstickdeadzone);
std::tie(rx, ry) = NormalizeStickDeadzone(stick_val[2], stick_val[3], m_pad_config.rstickdeadzone);
if (m_pad_config.padsquircling != 0)
{
std::tie(lx, ly) = ConvertToSquirclePoint(lx, ly, m_pad_config.padsquircling);
std::tie(rx, ry) = ConvertToSquirclePoint(rx, ry, m_pad_config.padsquircling);
}
pad->m_sticks[0].m_value = lx;
pad->m_sticks[1].m_value = 255 - ly;
pad->m_sticks[2].m_value = rx;
pad->m_sticks[3].m_value = 255 - ry;
break;
}
}
}
void mm_joystick_handler::GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback)
{
if (!Init())
{
return;
}
MMRESULT status = joyGetPosEx(JOYSTICKID1, &js_info);
switch (status)
{
case JOYERR_UNPLUGGED:
break;
case JOYERR_NOERROR:
auto data = GetButtonValues();
// 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, "" };
for (const auto& button : button_list)
{
u16 value = data[button.first];
if (value > 0 && value > pressed_button.first)
pressed_button = { value, button.second };
}
for (const auto& button : pov_list)
{
u16 value = data[button.first];
if (value > 0 && value > pressed_button.first)
pressed_button = { value, button.second };
}
for (const auto& button : axis_list)
{
u32 keycode = button.first;
u16 value = data[keycode];
if (((keycode == mmjoy_axis::joy_z_neg) && (value > m_trigger_threshold))
|| ((keycode == mmjoy_axis::joy_z_pos) && (value > m_trigger_threshold))
|| ((keycode <= mmjoy_axis::joy_y_neg) && (value > m_thumb_threshold))
|| ((keycode <= mmjoy_axis::joy_u_neg && keycode > mmjoy_axis::joy_z_neg) && (value > m_thumb_threshold)))
{
if (value > pressed_button.first)
{
pressed_button = { value, button.second };
}
}
}
int preview_values[6] =
{
data[mmjoy_axis::joy_z_neg],
data[mmjoy_axis::joy_z_pos],
data[mmjoy_axis::joy_x_pos] - data[mmjoy_axis::joy_x_neg],
data[mmjoy_axis::joy_y_pos] - data[mmjoy_axis::joy_y_neg],
data[mmjoy_axis::joy_u_pos] - data[mmjoy_axis::joy_u_neg],
data[mmjoy_axis::joy_r_pos] - data[mmjoy_axis::joy_r_neg]
};
if (pressed_button.first > 0)
return callback(pressed_button.first, pressed_button.second, preview_values);
else
return callback(0, "", preview_values);
break;
}
}
void mm_joystick_handler::TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold)
{
// Update the pad button values based on their type and thresholds.
// With this you can use axis or triggers as buttons or vice versa
switch (keyCode)
{
case mmjoy_axis::joy_z_neg:
pressed = val > m_pad_config.ltriggerthreshold;
val = pressed ? NormalizeTriggerInput(val, m_pad_config.ltriggerthreshold) : 0;
break;
case mmjoy_axis::joy_z_pos:
pressed = val > m_pad_config.rtriggerthreshold;
val = pressed ? NormalizeTriggerInput(val, m_pad_config.rtriggerthreshold) : 0;
break;
case mmjoy_axis::joy_x_pos:
case mmjoy_axis::joy_x_neg:
case mmjoy_axis::joy_y_pos:
case mmjoy_axis::joy_y_neg:
pressed = val > (ignore_threshold ? 0 : m_pad_config.lstickdeadzone);
val = pressed ? NormalizeStickInput(val, m_pad_config.lstickdeadzone, ignore_threshold) : 0;
break;
case mmjoy_axis::joy_r_pos:
case mmjoy_axis::joy_r_neg:
case mmjoy_axis::joy_u_pos:
case mmjoy_axis::joy_u_neg:
pressed = val > (ignore_threshold ? 0 : m_pad_config.rstickdeadzone);
val = pressed ? NormalizeStickInput(val, m_pad_config.rstickdeadzone, ignore_threshold) : 0;
break;
default: // normal button (should in theory also support sensitive buttons)
pressed = val > 0;
val = pressed ? val : 0;
break;
}
}
std::unordered_map<u64, u16> mm_joystick_handler::GetButtonValues()
{
std::unordered_map<u64, u16> button_values;
for (auto entry : button_list)
{
button_values.emplace(entry.first, js_info.dwButtons & entry.first ? 255 : 0);
}
for (auto entry : pov_list)
{
button_values.emplace(entry.first, js_info.dwPOV == entry.first ? 255 : 0);
}
auto add_axis_value = [&](DWORD axis, u64 pos, u64 neg)
{
u16 value = 0;
float fvalue = ScaleStickInput(axis, 0, 65535);
bool is_negative = fvalue <= 127.5;
if (is_negative)
{
value = Clamp0To255((127.5f - fvalue) * 2.0f);
button_values.emplace(pos, 0);
button_values.emplace(neg, value);
}
else
{
value = Clamp0To255((fvalue - 127.5f) * 2.0f);
button_values.emplace(pos, value);
button_values.emplace(neg, 0);
}
};
add_axis_value(js_info.dwXpos, mmjoy_axis::joy_x_pos, mmjoy_axis::joy_x_neg);
add_axis_value(js_info.dwYpos, mmjoy_axis::joy_y_neg, mmjoy_axis::joy_y_pos);
add_axis_value(js_info.dwZpos, mmjoy_axis::joy_z_neg, mmjoy_axis::joy_z_pos);
add_axis_value(js_info.dwRpos, mmjoy_axis::joy_r_neg, mmjoy_axis::joy_r_pos);
add_axis_value(js_info.dwUpos, mmjoy_axis::joy_u_pos, mmjoy_axis::joy_u_neg);
add_axis_value(js_info.dwVpos, mmjoy_axis::joy_v_pos, mmjoy_axis::joy_v_neg);
return button_values;
}
#endif

View file

@ -5,53 +5,87 @@
#include <mmsystem.h>
#include "Utilities/Config.h"
struct mm_joystick_config final : cfg::node
{
const std::string cfg_name = fs::get_config_dir() + "/config_mmjoystick.yml";
//cfg::int32 left_stick_left{this, "Left Analog Stick Left", static_cast<int>('A')};
//cfg::int32 left_stick_down{this, "Left Analog Stick Down", static_cast<int>('S')};
//cfg::int32 left_stick_right{this, "Left Analog Stick Right", static_cast<int>('D')};
//cfg::int32 left_stick_up{this, "Left Analog Stick Up", static_cast<int>('W')};
//cfg::int32 right_stick_left{this, "Right Analog Stick Left", 313};
//cfg::int32 right_stick_down{this, "Right Analog Stick Down", 367};
//cfg::int32 right_stick_right{this, "Right Analog Stick Right", 312};
//cfg::int32 right_stick_up{this, "Right Analog Stick Up", 366};
cfg::int32 start{this, "Start", JOY_BUTTON9};
cfg::int32 select{this, "Select", JOY_BUTTON10};
cfg::int32 square{this, "Square", JOY_BUTTON4};
cfg::int32 cross{this, "Cross", JOY_BUTTON3};
cfg::int32 circle{this, "Circle", JOY_BUTTON2};
cfg::int32 triangle{this, "Triangle", JOY_BUTTON1};
//cfg::int32 left{this, "Left", 314};
//cfg::int32 down{this, "Down", 317};
//cfg::int32 right{this, "Right", 316};
//cfg::int32 up{this, "Up", 315};
cfg::int32 r1{this, "R1", JOY_BUTTON8};
cfg::int32 r2{this, "R2", JOY_BUTTON6};
cfg::int32 r3{this, "R3", JOY_BUTTON12};
cfg::int32 l1{this, "L1", JOY_BUTTON7};
cfg::int32 l2{this, "L2", JOY_BUTTON5};
cfg::int32 l3{this, "L3", JOY_BUTTON11};
bool load()
{
if (fs::file cfg_file{ cfg_name, fs::read })
{
return from_string(cfg_file.to_string());
}
return false;
}
void save()
{
fs::file(cfg_name, fs::rewrite).write(to_string());
}
};
class mm_joystick_handler final : public PadHandlerBase
{
// Unique names for the config files and our pad settings dialog
const std::unordered_map<u64, std::string> button_list =
{
{ JOY_BUTTON1 , "Button 1" },
{ JOY_BUTTON2 , "Button 2" },
{ JOY_BUTTON3 , "Button 3" },
{ JOY_BUTTON4 , "Button 4" },
{ JOY_BUTTON5 , "Button 5" },
{ JOY_BUTTON6 , "Button 6" },
{ JOY_BUTTON7 , "Button 7" },
{ JOY_BUTTON8 , "Button 8" },
{ JOY_BUTTON9 , "Button 9" },
{ JOY_BUTTON10, "Button 10" },
{ JOY_BUTTON11, "Button 11" },
{ JOY_BUTTON12, "Button 12" },
{ JOY_BUTTON13, "Button 13" },
{ JOY_BUTTON14, "Button 14" },
{ JOY_BUTTON15, "Button 15" },
{ JOY_BUTTON16, "Button 16" },
{ JOY_BUTTON17, "Button 17" },
{ JOY_BUTTON18, "Button 18" },
{ JOY_BUTTON19, "Button 19" },
{ JOY_BUTTON20, "Button 20" },
{ JOY_BUTTON21, "Button 21" },
{ JOY_BUTTON22, "Button 22" },
{ JOY_BUTTON23, "Button 23" },
{ JOY_BUTTON24, "Button 24" },
{ JOY_BUTTON25, "Button 25" },
{ JOY_BUTTON26, "Button 26" },
{ JOY_BUTTON27, "Button 27" },
{ JOY_BUTTON28, "Button 28" },
{ JOY_BUTTON29, "Button 29" },
{ JOY_BUTTON30, "Button 30" },
{ JOY_BUTTON31, "Button 31" },
{ JOY_BUTTON32, "Button 32" },
};
// Unique names for the config files and our pad settings dialog
const std::unordered_map<u64, std::string> pov_list =
{
{ JOY_POVFORWARD, "POV Up" },
{ JOY_POVRIGHT, "POV Right" },
{ JOY_POVBACKWARD, "POV Down" },
{ JOY_POVLEFT, "POV Left" },
};
enum mmjoy_axis
{
joy_x_pos = 9700,
joy_x_neg,
joy_y_pos,
joy_y_neg,
joy_z_pos,
joy_z_neg,
joy_r_pos,
joy_r_neg,
joy_u_pos,
joy_u_neg,
joy_v_pos,
joy_v_neg,
};
// Unique names for the config files and our pad settings dialog
const std::unordered_map<u64, std::string> axis_list =
{
{ joy_x_pos, "X+" },
{ joy_x_neg, "X-" },
{ joy_y_pos, "Y+" },
{ joy_y_neg, "Y-" },
{ joy_z_pos, "Z+" },
{ joy_z_neg, "Z-" },
{ joy_r_pos, "R+" },
{ joy_r_neg, "R-" },
{ joy_u_pos, "U+" },
{ joy_u_neg, "U-" },
{ joy_v_pos, "V+" },
{ joy_v_neg, "V-" },
};
public:
mm_joystick_handler();
~mm_joystick_handler();
@ -61,14 +95,17 @@ public:
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback) override;
private:
void TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold = false) override;
std::unordered_map<u64, u16> GetButtonValues();
bool is_init;
u32 supportedJoysticks;
JOYINFOEX js_info;
JOYCAPS js_caps;
JOYINFOEX js_info;
JOYCAPS js_caps;
std::vector<std::shared_ptr<Pad>> bindings;
std::array<bool, 7> last_connection_status = {};
};

View file

@ -27,8 +27,8 @@ pad_thread::~pad_thread()
void pad_thread::Init(const u32 max_connect)
{
std::memset(&m_info, 0, sizeof(m_info));
m_info.max_connect = max_connect;
m_info.now_connect = std::min(max_connect, (u32)7); // max 7 pads
m_info.max_connect = std::min(max_connect, (u32)7); // max 7 pads
m_info.now_connect = 0;
input_cfg.load();
@ -38,17 +38,19 @@ void pad_thread::Init(const u32 max_connect)
std::shared_ptr<NullPadHandler> nullpad = std::make_shared<NullPadHandler>();
handlers.emplace(pad_handler::null, nullpad);
for (u32 i = 0; i < m_info.now_connect; i++)
for (u32 i = 0; i < m_info.max_connect; i++)
{
std::shared_ptr<PadHandlerBase> cur_pad_handler;
if (handlers.count(input_cfg.player_input[i]) != 0)
const auto &handler_type = input_cfg.player_input[i];
if (handlers.count(handler_type) != 0)
{
cur_pad_handler = handlers[input_cfg.player_input[i]];
cur_pad_handler = handlers[handler_type];
}
else
{
switch (input_cfg.player_input[i])
switch (handler_type)
{
case pad_handler::keyboard:
keyptr = std::make_shared<keyboard_pad_handler>();
@ -75,7 +77,7 @@ void pad_thread::Init(const u32 max_connect)
break;
#endif
}
handlers.emplace(input_cfg.player_input[i], cur_pad_handler);
handlers.emplace(handler_type, cur_pad_handler);
}
cur_pad_handler->Init();
@ -88,9 +90,11 @@ void pad_thread::Init(const u32 max_connect)
if (cur_pad_handler->bindPadToDevice(m_pads.back(), input_cfg.player_device[i]->to_string()) == false)
{
//Failed to bind the device to cur_pad_handler so binds to NullPadHandler
LOG_ERROR(GENERAL, "Failed to bind device %s to handler %s", input_cfg.player_device[i]->to_string(), input_cfg.player_input[i].to_string());
LOG_ERROR(GENERAL, "Failed to bind device %s to handler %s", input_cfg.player_device[i]->to_string(), handler_type.to_string());
nullpad->bindPadToDevice(m_pads.back(), input_cfg.player_device[i]->to_string());
}
else if (handler_type != pad_handler::null)
m_info.now_connect++;
}
thread = std::make_shared<std::thread>(&pad_thread::ThreadFunc, this);

View file

@ -336,6 +336,7 @@
<ClCompile Include="basic_keyboard_handler.cpp" />
<ClCompile Include="basic_mouse_handler.cpp" />
<ClCompile Include="ds4_pad_handler.cpp" />
<ClCompile Include="evdev_joystick_handler.cpp" />
<ClCompile Include="keyboard_pad_handler.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="mm_joystick_handler.cpp" />
@ -985,6 +986,7 @@
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<ClInclude Include="ds4_pad_handler.h" />
<ClInclude Include="evdev_joystick_handler.h" />
<ClInclude Include="keyboard_pad_handler.h" />
<CustomBuild Include="rpcs3qt\gs_frame.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing gs_frame.h...</Message>

View file

@ -96,6 +96,9 @@
<Filter Include="Gui\syntax highlighter">
<UniqueIdentifier>{adb985ae-88db-4274-a6fd-6bcf230451ca}</UniqueIdentifier>
</Filter>
<Filter Include="Io\evdev">
<UniqueIdentifier>{6992629a-48f1-43ec-8070-d1dba81bcbf9}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="\rpcs3qt\*.cpp">
@ -554,6 +557,9 @@
<ClCompile Include="rpcs3qt\syntax_highlighter.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="evdev_joystick_handler.cpp">
<Filter>Io\evdev</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\save_manager_dialog.cpp">
<Filter>Gui\save</Filter>
</ClCompile>
@ -637,6 +643,9 @@
<ClInclude Include="rpcs3qt\trophy_tree_widget_item.h">
<Filter>Gui\trophy</Filter>
</ClInclude>
<ClInclude Include="evdev_joystick_handler.h">
<Filter>Io\evdev</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">

View file

@ -18,7 +18,7 @@
#include "Emu/RSX/CgBinaryProgram.h"
constexpr auto qstr = QString::fromStdString;
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
cg_disasm_window::cg_disasm_window(std::shared_ptr<gui_settings> xSettings): xgui_settings(xSettings)
{

View file

@ -18,7 +18,7 @@
extern std::string g_cfg_defaults; //! Default settings grabbed from Utilities/Config.h
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
inline std::string sstr(const QVariant& _in) { return sstr(_in.toString()); }
// Emit sorted YAML

View file

@ -24,7 +24,7 @@
#include <QLabel>
#include <QScrollBar>
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
inline QSize sizeFromSlider(const int& pos) { return gui::gl_icon_size_min + (gui::gl_icon_size_max - gui::gl_icon_size_min) * (pos / (float)gui::gl_max_slider_pos); }
game_list_frame::game_list_frame(std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, QWidget *parent)

View file

@ -236,7 +236,7 @@ protected:
/** Override inherited method from Qt to allow signalling when close happened.*/
void closeEvent(QCloseEvent* event) override;
void resizeEvent(QResizeEvent *event) override;
bool eventFilter(QObject *object, QEvent *event);
bool eventFilter(QObject *object, QEvent *event) override;
private:
QPixmap PaintedPixmap(const QImage& img, bool paintConfigIcon = false);
bool Boot(const GameInfo& info);

View file

@ -1,4 +1,5 @@
#include "gamepads_settings_dialog.h"
#include "pad_settings_dialog.h"
#include "../Emu/Io/PadHandler.h"
#include "../ds4_pad_handler.h"
#ifdef _WIN32
@ -16,6 +17,9 @@
input_config input_cfg;
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
constexpr auto qstr = QString::fromStdString;
// taken from https://stackoverflow.com/a/30818424/8353754
// because size policies won't work as expected (see similar bugs in Qt bugtracker)
inline void resizeComboBoxView(QComboBox* combo)
@ -54,43 +58,6 @@ gamepads_settings_dialog::gamepads_settings_dialog(QWidget* parent)
input_cfg.from_default();
input_cfg.load();
auto fill_device_combo = [](QComboBox *combo)
{
std::vector<std::string> str_inputs = input_cfg.player_input[0].to_list();
for (int index = 0; index < str_inputs.size(); index++)
{
combo->addItem(str_inputs[index].c_str());
}
resizeComboBoxView(combo);
};
auto configure_combos = [=]
{
//Set the values from config
for (int i = 0; i < MAX_PLAYERS; i++)
{
for (int j = 0; j < co_inputtype[i]->count(); j++)
{
if (co_inputtype[i]->itemText(j).toStdString() == input_cfg.player_input[i].to_string())
{
co_inputtype[i]->setCurrentIndex(j);
ChangeInputType(i);
break;
}
}
for (int j = 0; j < co_deviceID[i]->count(); j++)
{
if (co_deviceID[i]->itemText(j).toStdString() == input_cfg.player_device[i]->to_string())
{
co_deviceID[i]->setCurrentIndex(j);
ChangeDevice(i);
break;
}
}
}
};
for (int i = 0; i < MAX_PLAYERS; i++)
{
QGroupBox *grp_player = new QGroupBox(QString(tr("Player %1").arg(i+1)));
@ -126,7 +93,12 @@ gamepads_settings_dialog::gamepads_settings_dialog(QWidget* parent)
grp_player->setFixedSize(grp_player->sizeHint());
// fill comboboxes after setting the groupbox's size to prevent stretch
fill_device_combo(co_inputtype[i]);
std::vector<std::string> str_inputs = input_cfg.player_input[0].to_list();
for (int index = 0; index < str_inputs.size(); index++)
{
co_inputtype[i]->addItem(qstr(str_inputs[index]));
}
resizeComboBoxView(co_inputtype[i]);
all_players->addWidget(grp_player);
@ -154,17 +126,38 @@ gamepads_settings_dialog::gamepads_settings_dialog(QWidget* parent)
setLayout(dialog_layout);
layout()->setSizeConstraint(QLayout::SetFixedSize);
configure_combos();
auto configure_combos = [=]
{
//Set the values from config
for (int i = 0; i < MAX_PLAYERS; i++)
{
// No extra loops are necessary because setCurrentText does it for us
co_inputtype[i]->setCurrentText(qstr(input_cfg.player_input[i].to_string()));
// Device will be empty on some rare occasions, so fill them by force
ChangeInputType(i);
}
};
for (int i = 0; i < MAX_PLAYERS; i++)
{
connect(co_inputtype[i], &QComboBox::currentTextChanged, [=] { ChangeInputType(i); });
connect(co_deviceID[i], &QComboBox::currentTextChanged, [=] { ChangeDevice(i); });
connect(co_deviceID[i], &QComboBox::currentTextChanged, [=](const QString& dev)
{
std::string device = sstr(dev);
if (!input_cfg.player_device[i]->from_string(device))
{
//Something went wrong
LOG_ERROR(GENERAL, "Failed to convert device string: %s", device);
return;
}
});
connect(bu_config[i], &QAbstractButton::clicked, [=] { ClickConfigButton(i); });
}
connect(ok_button, &QPushButton::pressed, this, &gamepads_settings_dialog::SaveExit);
connect(cancel_button, &QPushButton::pressed, this, &gamepads_settings_dialog::CancelExit);
connect(refresh_button, &QPushButton::pressed, [=] { configure_combos(); });
configure_combos();
}
void gamepads_settings_dialog::SaveExit()
@ -193,20 +186,6 @@ void gamepads_settings_dialog::CancelExit()
QDialog::accept();
}
void gamepads_settings_dialog::ChangeDevice(int player)
{
bool success;
success = input_cfg.player_device[player]->from_string(co_deviceID[player]->currentText().toStdString());
if (!success)
{
//Something went wrong
LOG_ERROR(GENERAL, "Failed to convert device string:%s", co_deviceID[player]->currentText().toStdString().c_str());
return;
}
}
std::shared_ptr<PadHandlerBase> gamepads_settings_dialog::GetHandler(pad_handler type)
{
std::shared_ptr<PadHandlerBase> ret_handler;
@ -244,24 +223,28 @@ std::shared_ptr<PadHandlerBase> gamepads_settings_dialog::GetHandler(pad_handler
void gamepads_settings_dialog::ChangeInputType(int player)
{
bool success;
std::string device = sstr(co_inputtype[player]->currentText());
success = input_cfg.player_input[player].from_string(co_inputtype[player]->currentText().toStdString());
if (!success)
// Change this player's current handler
if (!input_cfg.player_input[player].from_string(device))
{
//Something went wrong
LOG_ERROR(GENERAL, "Failed to convert input string:%s", co_inputtype[player]->currentText().toStdString().c_str());
LOG_ERROR(GENERAL, "Failed to convert input string:%s", device);
return;
}
// Get this player's current handler and it's currently available devices
std::shared_ptr<PadHandlerBase> cur_pad_handler = GetHandler(input_cfg.player_input[player]);
std::vector<std::string> list_devices = cur_pad_handler->ListDevices();
// Refill the device combobox with currently available devices
co_deviceID[player]->clear();
for (int i = 0; i < list_devices.size(); i++) co_deviceID[player]->addItem(list_devices[i].c_str(), i);
for (int i = 0; i < list_devices.size(); i++)
{
co_deviceID[player]->addItem(qstr(list_devices[i]), i);
}
// Handle empty device list
if (list_devices.size() == 0)
{
co_deviceID[player]->addItem(tr("No Device Detected"), -1);
@ -270,14 +253,22 @@ void gamepads_settings_dialog::ChangeInputType(int player)
else
{
co_deviceID[player]->setEnabled(true);
co_deviceID[player]->setCurrentText(qstr(device));
}
// Update view and enable configuration if possible
resizeComboBoxView(co_deviceID[player]);
bu_config[player]->setEnabled(cur_pad_handler->has_config());
}
void gamepads_settings_dialog::ClickConfigButton(int player)
{
// Get this player's current handler and open its pad settings dialog
std::shared_ptr<PadHandlerBase> cur_pad_handler = GetHandler(input_cfg.player_input[player]);
if (cur_pad_handler->has_config()) cur_pad_handler->ConfigController(*input_cfg.player_device[player]);
if (cur_pad_handler->has_config())
{
std::string device = sstr(co_deviceID[player]->currentText());
pad_settings_dialog dlg(device, cur_pad_handler);
dlg.exec();
}
}

View file

@ -65,7 +65,6 @@ class gamepads_settings_dialog : public QDialog
protected:
std::shared_ptr<PadHandlerBase> GetHandler(pad_handler type);
void ChangeInputType(int player);
void ChangeDevice(int player);
void ClickConfigButton(int player);
void SaveExit();
void CancelExit();

View file

@ -6,7 +6,7 @@
#include <QDir>
#include <QMessageBox>
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
gui_settings::gui_settings(QObject* parent) : QObject(parent), m_settings(ComputeSettingsDir() + tr("CurrentSettings") + ".ini", QSettings::Format::IniFormat, parent),
m_settingsDir(ComputeSettingsDir())

View file

@ -47,7 +47,7 @@
#include "ui_main_window.h"
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
main_window::main_window(std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, QWidget *parent)
: QMainWindow(parent), guiSettings(guiSettings), emuSettings(emuSettings), m_sys_menu_opened(false), ui(new Ui::main_window)

View file

@ -5,19 +5,19 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QAction>
#include <QButtonGroup>
#include <QPainter>
#include "stdafx.h"
#include "pad_settings_dialog.h"
#include "ui_pad_settings_dialog.h"
// TODO: rewrite with std::chrono or QTimer
#include <time.h>
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
constexpr auto qstr = QString::fromStdString;
extern keyboard_pad_config g_kbpad_config;
pad_settings_dialog::pad_settings_dialog(QWidget *parent) : QDialog(parent), ui(new Ui::pad_settings_dialog)
pad_settings_dialog::pad_settings_dialog(const std::string& device, std::shared_ptr<PadHandlerBase> handler, QWidget *parent)
: QDialog(parent), ui(new Ui::pad_settings_dialog), m_handler_cfg(handler->GetConfig()), m_device_name(device), m_handler(handler)
{
m_handler_cfg->load();
ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -25,46 +25,225 @@ pad_settings_dialog::pad_settings_dialog(QWidget *parent) : QDialog(parent), ui(
ui->b_cancel->setDefault(true);
connect(ui->b_cancel, &QAbstractButton::clicked, this, &QWidget::close);
// Handling
QButtonGroup *padButtons = new QButtonGroup(this);
padButtons->addButton(ui->b_left_lstick, 1);
padButtons->addButton(ui->b_down_lstick, 2);
padButtons->addButton(ui->b_right_lstick, 3);
padButtons->addButton(ui->b_up_lstick, 4);
padButtons->addButton(ui->b_left, 5);
padButtons->addButton(ui->b_down, 6);
padButtons->addButton(ui->b_right, 7);
padButtons->addButton(ui->b_up, 8);
padButtons->addButton(ui->b_shift_l1, 9);
padButtons->addButton(ui->b_shift_l2, 10);
padButtons->addButton(ui->b_shift_l3, 11);
padButtons->addButton(ui->b_start, 12);
padButtons->addButton(ui->b_select, 13);
padButtons->addButton(ui->b_shift_r1, 14);
padButtons->addButton(ui->b_shift_r2, 15);
padButtons->addButton(ui->b_shift_r3, 16);
padButtons->addButton(ui->b_square, 17);
padButtons->addButton(ui->b_cross, 18);
padButtons->addButton(ui->b_circle, 19);
padButtons->addButton(ui->b_triangle, 20);
padButtons->addButton(ui->b_left_rstick, 21);
padButtons->addButton(ui->b_down_rstick, 22);
padButtons->addButton(ui->b_right_rstick, 23);
padButtons->addButton(ui->b_up_rstick, 24);
padButtons->addButton(ui->b_reset, 25);
padButtons->addButton(ui->b_ok, 26);
padButtons->addButton(ui->b_cancel, 27);
m_padButtons = new QButtonGroup(this);
m_palette = ui->b_left->palette(); // save normal palette
connect(padButtons, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &pad_settings_dialog::OnPadButtonClicked);
ui->chb_vibration_large->setChecked((bool)m_handler_cfg->enable_vibration_motor_large);
ui->chb_vibration_small->setChecked((bool)m_handler_cfg->enable_vibration_motor_small);
ui->chb_vibration_switch->setChecked((bool)m_handler_cfg->switch_vibration_motors);
// Adjust to the different pad handlers
if (m_handler_cfg->cfg_type == "keyboard")
{
setWindowTitle(tr("Configure Keyboard"));
m_handler_type = handler_type::handler_type_keyboard;
}
else if (m_handler_cfg->cfg_type == "xinput")
{
setWindowTitle(tr("Configure XInput"));
m_handler_type = handler_type::handler_type_xinput;
}
else if (m_handler_cfg->cfg_type == "ds4")
{
setWindowTitle(tr("Configure DS4"));
m_handler_type = handler_type::handler_type_ds4;
}
else if (m_handler_cfg->cfg_type == "mmjoystick")
{
setWindowTitle(tr("Configure MMJoystick"));
m_handler_type = handler_type::handler_type_mmjoy;
}
else if (m_handler_cfg->cfg_type == "evdev")
{
setWindowTitle(tr("Configure evdev"));
m_handler_type = handler_type::handler_type_evdev;
}
// Enable Button Remapping
if (m_handler->has_config())
{
// Use timer to get button input
const auto& callback = [=](u16 val, std::string name, int preview_values[6])
{
if (m_handler->has_deadzones())
{
ui->preview_trigger_left->setValue(preview_values[0]);
ui->preview_trigger_right->setValue(preview_values[1]);
if (lx != preview_values[2] || ly != preview_values[3])
{
lx = preview_values[2], ly = preview_values[3];
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->sizeHint().width(), lx, ly);
}
if (rx != preview_values[4] || ry != preview_values[5])
{
rx = preview_values[4], ry = preview_values[5];
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->sizeHint().width(), rx, ry);
}
}
if (val <= 0) return;
LOG_NOTICE(HLE, "GetNextButtonPress: %s button %s pressed with value %d", m_handler_cfg->cfg_type, name, val);
if (m_button_id > button_ids::id_pad_begin && m_button_id < button_ids::id_pad_end)
{
m_cfg_entries[m_button_id].key = name;
m_cfg_entries[m_button_id].text = qstr(name);
ReactivateButtons();
}
};
connect(&m_timer_input, &QTimer::timeout, [=]()
{
m_handler->GetNextButtonPress(m_device_name, callback);
});
m_timer_input.start(1);
};
// Enable Vibration Checkboxes
if (m_handler->has_rumble())
{
const s32 min_force = m_handler->vibration_min;
const s32 max_force = m_handler->vibration_max;
ui->chb_vibration_large->setEnabled(true);
ui->chb_vibration_small->setEnabled(true);
ui->chb_vibration_switch->setEnabled(true);
connect(ui->chb_vibration_large, &QCheckBox::clicked, [=](bool checked)
{
if (!checked) return;
ui->chb_vibration_switch->isChecked() ? m_handler->TestVibration(m_device_name, min_force, max_force)
: m_handler->TestVibration(m_device_name, max_force, min_force);
QTimer::singleShot(300, [=]()
{
m_handler->TestVibration(m_device_name, min_force, min_force);
});
});
connect(ui->chb_vibration_small, &QCheckBox::clicked, [=](bool checked)
{
if (!checked) return;
ui->chb_vibration_switch->isChecked() ? m_handler->TestVibration(m_device_name, max_force, min_force)
: m_handler->TestVibration(m_device_name, min_force, max_force);
QTimer::singleShot(300, [=]()
{
m_handler->TestVibration(m_device_name, min_force, min_force);
});
});
connect(ui->chb_vibration_switch, &QCheckBox::clicked, [=](bool checked)
{
checked ? m_handler->TestVibration(m_device_name, min_force, max_force)
: m_handler->TestVibration(m_device_name, max_force, min_force);
QTimer::singleShot(200, [=]()
{
checked ? m_handler->TestVibration(m_device_name, max_force, min_force)
: m_handler->TestVibration(m_device_name, min_force, max_force);
QTimer::singleShot(200, [=]()
{
m_handler->TestVibration(m_device_name, min_force, min_force);
});
});
});
}
// Enable Deadzone Settings
if (m_handler->has_deadzones())
{
auto initSlider = [=](QSlider* slider, const s32& value, const s32& min, const s32& max)
{
slider->setEnabled(true);
slider->setRange(min, max);
slider->setValue(value);
};
// Enable Trigger Thresholds
initSlider(ui->slider_trigger_left, m_handler_cfg->ltriggerthreshold, 0, m_handler->trigger_max);
initSlider(ui->slider_trigger_right, m_handler_cfg->rtriggerthreshold, 0, m_handler->trigger_max);
ui->preview_trigger_left->setRange(0, m_handler->trigger_max);
ui->preview_trigger_right->setRange(0, m_handler->trigger_max);
// Enable Stick Deadzones
initSlider(ui->slider_stick_left, m_handler_cfg->lstickdeadzone, 0, m_handler->thumb_max);
initSlider(ui->slider_stick_right, m_handler_cfg->rstickdeadzone, 0, m_handler->thumb_max);
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->sizeHint().width(), lx, ly);
connect(ui->slider_stick_left, &QSlider::valueChanged, [&](int value)
{
RepaintPreviewLabel(ui->preview_stick_left, value, ui->slider_stick_left->sizeHint().width(), lx, ly);
});
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->sizeHint().width(), rx, ry);
connect(ui->slider_stick_right, &QSlider::valueChanged, [&](int value)
{
RepaintPreviewLabel(ui->preview_stick_right, value, ui->slider_stick_right->sizeHint().width(), rx, ry);
});
}
auto insertButton = [this](int id, QPushButton* button, cfg::string* cfg_name)
{
QString name = qstr(*cfg_name);
m_cfg_entries.insert(std::make_pair(id, pad_button{ cfg_name, *cfg_name, name }));
m_padButtons->addButton(button, id);
button->setText(name);
};
insertButton(button_ids::id_pad_lstick_left, ui->b_lstick_left, &m_handler_cfg->ls_left);
insertButton(button_ids::id_pad_lstick_down, ui->b_lstick_down, &m_handler_cfg->ls_down);
insertButton(button_ids::id_pad_lstick_right, ui->b_lstick_right, &m_handler_cfg->ls_right);
insertButton(button_ids::id_pad_lstick_up, ui->b_lstick_up, &m_handler_cfg->ls_up);
insertButton(button_ids::id_pad_left, ui->b_left, &m_handler_cfg->left);
insertButton(button_ids::id_pad_down, ui->b_down, &m_handler_cfg->down);
insertButton(button_ids::id_pad_right, ui->b_right, &m_handler_cfg->right);
insertButton(button_ids::id_pad_up, ui->b_up, &m_handler_cfg->up);
insertButton(button_ids::id_pad_l1, ui->b_shift_l1, &m_handler_cfg->l1);
insertButton(button_ids::id_pad_l2, ui->b_shift_l2, &m_handler_cfg->l2);
insertButton(button_ids::id_pad_l3, ui->b_shift_l3, &m_handler_cfg->l3);
insertButton(button_ids::id_pad_start, ui->b_start, &m_handler_cfg->start);
insertButton(button_ids::id_pad_select, ui->b_select, &m_handler_cfg->select);
insertButton(button_ids::id_pad_ps, ui->b_ps, &m_handler_cfg->ps);
insertButton(button_ids::id_pad_r1, ui->b_shift_r1, &m_handler_cfg->r1);
insertButton(button_ids::id_pad_r2, ui->b_shift_r2, &m_handler_cfg->r2);
insertButton(button_ids::id_pad_r3, ui->b_shift_r3, &m_handler_cfg->r3);
insertButton(button_ids::id_pad_square, ui->b_square, &m_handler_cfg->square);
insertButton(button_ids::id_pad_cross, ui->b_cross, &m_handler_cfg->cross);
insertButton(button_ids::id_pad_circle, ui->b_circle, &m_handler_cfg->circle);
insertButton(button_ids::id_pad_triangle, ui->b_triangle, &m_handler_cfg->triangle);
insertButton(button_ids::id_pad_rstick_left, ui->b_rstick_left, &m_handler_cfg->rs_left);
insertButton(button_ids::id_pad_rstick_down, ui->b_rstick_down, &m_handler_cfg->rs_down);
insertButton(button_ids::id_pad_rstick_right, ui->b_rstick_right, &m_handler_cfg->rs_right);
insertButton(button_ids::id_pad_rstick_up, ui->b_rstick_up, &m_handler_cfg->rs_up);
m_padButtons->addButton(ui->b_reset, button_ids::id_reset_parameters);
m_padButtons->addButton(ui->b_ok, button_ids::id_ok);
m_padButtons->addButton(ui->b_cancel, button_ids::id_cancel);
connect(m_padButtons, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &pad_settings_dialog::OnPadButtonClicked);
connect(&m_timer, &QTimer::timeout, [&]()
{
if (--m_seconds <= 0)
{
ReactivateButtons();
return;
}
m_padButtons->button(m_button_id)->setText(tr("[ Waiting %1 ]").arg(m_seconds));
});
g_kbpad_config.load();
UpdateLabel();
gui_settings settings(this);
@ -81,224 +260,154 @@ pad_settings_dialog::~pad_settings_dialog()
delete ui;
}
void pad_settings_dialog::ReactivateButtons()
{
m_timer.stop();
m_seconds = MAX_SECONDS;
if (m_button_id == button_ids::id_pad_begin)
{
return;
}
if (m_padButtons->button(m_button_id))
{
m_padButtons->button(m_button_id)->setPalette(m_palette);
}
m_button_id = button_ids::id_pad_begin;
UpdateLabel();
SwitchButtons(true);
}
void pad_settings_dialog::RepaintPreviewLabel(QLabel* l, int dz, int w, int x, int y)
{
int max = m_handler->thumb_max;
int origin = w * 0.1;
int width = w * 0.8;
int dz_width = width * dz / max;
int dz_origin = (w - dz_width) / 2;
x = (w + (x * width / max)) / 2;
y = (w + (y * -1 * width / max)) / 2;
QPixmap pm(w, w);
pm.fill(Qt::transparent);
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing, true);
QPen pen(Qt::black, 2);
p.setPen(pen);
QBrush brush(Qt::white);
p.setBrush(brush);
p.drawEllipse(origin, origin, width, width);
pen = QPen(Qt::red, 2);
p.setPen(pen);
p.drawEllipse(dz_origin, dz_origin, dz_width, dz_width);
pen = QPen(Qt::blue, 2);
p.setPen(pen);
p.drawEllipse(x, y, 1, 1);
l->setPixmap(pm);
}
void pad_settings_dialog::keyPressEvent(QKeyEvent *keyEvent)
{
m_key_pressed = true;
cfg::int32* entry = nullptr;
switch (m_button_id)
if (m_handler_type != handler_type::handler_type_keyboard)
{
case id_pad_lstick_left: entry = &g_kbpad_config.left_stick_left; break;
case id_pad_lstick_down: entry = &g_kbpad_config.left_stick_down; break;
case id_pad_lstick_right: entry = &g_kbpad_config.left_stick_right; break;
case id_pad_lstick_up: entry = &g_kbpad_config.left_stick_up; break;
case id_pad_left: entry = &g_kbpad_config.left; break;
case id_pad_down: entry = &g_kbpad_config.down; break;
case id_pad_right: entry = &g_kbpad_config.right; break;
case id_pad_up: entry = &g_kbpad_config.up; break;
case id_pad_l1: entry = &g_kbpad_config.l1; break;
case id_pad_l2: entry = &g_kbpad_config.l2; break;
case id_pad_l3: entry = &g_kbpad_config.l3; break;
case id_pad_start: entry = &g_kbpad_config.start; break;
case id_pad_select: entry = &g_kbpad_config.select; break;
case id_pad_r1: entry = &g_kbpad_config.r1; break;
case id_pad_r2: entry = &g_kbpad_config.r2; break;
case id_pad_r3: entry = &g_kbpad_config.r3; break;
case id_pad_square: entry = &g_kbpad_config.square; break;
case id_pad_cross: entry = &g_kbpad_config.cross; break;
case id_pad_circle: entry = &g_kbpad_config.circle; break;
case id_pad_triangle: entry = &g_kbpad_config.triangle; break;
case id_pad_rstick_left: entry = &g_kbpad_config.right_stick_left; break;
case id_pad_rstick_down: entry = &g_kbpad_config.right_stick_down; break;
case id_pad_rstick_right: entry = &g_kbpad_config.right_stick_right; break;
case id_pad_rstick_up: entry = &g_kbpad_config.right_stick_up; break;
case 0: break;
default: LOG_ERROR(HLE, "Unknown button ID: %d", m_button_id); break;
return;
}
if (entry)
if (m_button_id == button_ids::id_pad_begin)
{
// TODO: do not modify config
entry->from_string(std::to_string(keyEvent->key()));
return;
}
SwitchButtons(true); // enable all buttons
m_button_id = 0; // reset current button id
m_key_pressed = false;
UpdateLabel();
}
void pad_settings_dialog::UpdateLabel()
{
// Get button labels from .ini
ui->b_up_lstick->setText(GetKeyName(g_kbpad_config.left_stick_up));
ui->b_down_lstick->setText(GetKeyName(g_kbpad_config.left_stick_down));
ui->b_left_lstick->setText(GetKeyName(g_kbpad_config.left_stick_left));
ui->b_right_lstick->setText(GetKeyName(g_kbpad_config.left_stick_right));
ui->b_up->setText(GetKeyName(g_kbpad_config.up));
ui->b_down->setText(GetKeyName(g_kbpad_config.down));
ui->b_left->setText(GetKeyName(g_kbpad_config.left));
ui->b_right->setText(GetKeyName(g_kbpad_config.right));
ui->b_shift_l1->setText(GetKeyName(g_kbpad_config.l1));
ui->b_shift_l2->setText(GetKeyName(g_kbpad_config.l2));
ui->b_shift_l3->setText(GetKeyName(g_kbpad_config.l3));
ui->b_start->setText(GetKeyName(g_kbpad_config.start));
ui->b_select->setText(GetKeyName(g_kbpad_config.select));
ui->b_shift_r1->setText(GetKeyName(g_kbpad_config.r1));
ui->b_shift_r2->setText(GetKeyName(g_kbpad_config.r2));
ui->b_shift_r3->setText(GetKeyName(g_kbpad_config.r3));
ui->b_square->setText(GetKeyName(g_kbpad_config.square));
ui->b_cross->setText(GetKeyName(g_kbpad_config.cross));
ui->b_circle->setText(GetKeyName(g_kbpad_config.circle));
ui->b_triangle->setText(GetKeyName(g_kbpad_config.triangle));
ui->b_up_rstick->setText(GetKeyName(g_kbpad_config.right_stick_up));
ui->b_down_rstick->setText(GetKeyName(g_kbpad_config.right_stick_down));
ui->b_left_rstick->setText(GetKeyName(g_kbpad_config.right_stick_left));
ui->b_right_rstick->setText(GetKeyName(g_kbpad_config.right_stick_right));
}
void pad_settings_dialog::UpdateTimerLabel(const u32 id)
{
// Lambda used to update label. The 47 is magical.
auto UpdateLabel = [=](QPushButton* target)
if (m_button_id <= button_ids::id_pad_begin || m_button_id >= button_ids::id_pad_end)
{
target->setText(QString::number(m_seconds + 47));
};
switch (id)
{
case id_pad_lstick_left: UpdateLabel(ui->b_left_lstick); break;
case id_pad_lstick_down: UpdateLabel(ui->b_down_lstick); break;
case id_pad_lstick_right: UpdateLabel(ui->b_right_lstick); break;
case id_pad_lstick_up: UpdateLabel(ui->b_up_lstick); break;
case id_pad_left: UpdateLabel(ui->b_left); break;
case id_pad_down: UpdateLabel(ui->b_down); break;
case id_pad_right: UpdateLabel(ui->b_right); break;
case id_pad_up: UpdateLabel(ui->b_up); break;
case id_pad_l1: UpdateLabel(ui->b_shift_l1); break;
case id_pad_l2: UpdateLabel(ui->b_shift_l2); break;
case id_pad_l3: UpdateLabel(ui->b_shift_l3); break;
case id_pad_start: UpdateLabel(ui->b_start); break;
case id_pad_select: UpdateLabel(ui->b_select); break;
case id_pad_r1: UpdateLabel(ui->b_shift_r1); break;
case id_pad_r2: UpdateLabel(ui->b_shift_r2); break;
case id_pad_r3: UpdateLabel(ui->b_shift_r3); break;
case id_pad_square: UpdateLabel(ui->b_square); break;
case id_pad_cross: UpdateLabel(ui->b_cross); break;
case id_pad_circle: UpdateLabel(ui->b_circle); break;
case id_pad_triangle: UpdateLabel(ui->b_triangle); break;
case id_pad_rstick_left: UpdateLabel(ui->b_left_rstick); break;
case id_pad_rstick_down: UpdateLabel(ui->b_down_rstick); break;
case id_pad_rstick_right: UpdateLabel(ui->b_right_rstick); break;
case id_pad_rstick_up: UpdateLabel(ui->b_up_rstick); break;
default: LOG_ERROR(HLE, "Unknown button ID: %d", id); break;
LOG_NOTICE(HLE, "Pad Settings: Handler Type: %d, Unknown button ID: %d", static_cast<int>(m_handler_type), m_button_id);
}
}
void pad_settings_dialog::SwitchButtons(const bool IsEnabled)
{
ui->b_up_lstick->setEnabled(IsEnabled);
ui->b_down_lstick->setEnabled(IsEnabled);
ui->b_left_lstick->setEnabled(IsEnabled);
ui->b_right_lstick->setEnabled(IsEnabled);
ui->b_up->setEnabled(IsEnabled);
ui->b_down->setEnabled(IsEnabled);
ui->b_left->setEnabled(IsEnabled);
ui->b_right->setEnabled(IsEnabled);
ui->b_shift_l1->setEnabled(IsEnabled);
ui->b_shift_l2->setEnabled(IsEnabled);
ui->b_shift_l3->setEnabled(IsEnabled);
ui->b_start->setEnabled(IsEnabled);
ui->b_select->setEnabled(IsEnabled);
ui->b_shift_r1->setEnabled(IsEnabled);
ui->b_shift_r2->setEnabled(IsEnabled);
ui->b_shift_r3->setEnabled(IsEnabled);
ui->b_square->setEnabled(IsEnabled);
ui->b_cross->setEnabled(IsEnabled);
ui->b_circle->setEnabled(IsEnabled);
ui->b_triangle->setEnabled(IsEnabled);
ui->b_up_rstick->setEnabled(IsEnabled);
ui->b_down_rstick->setEnabled(IsEnabled);
ui->b_left_rstick->setEnabled(IsEnabled);
ui->b_right_rstick->setEnabled(IsEnabled);
ui->b_ok->setEnabled(IsEnabled);
ui->b_cancel->setEnabled(IsEnabled);
ui->b_reset->setEnabled(IsEnabled);
}
void pad_settings_dialog::RunTimer(const u32 seconds, const u32 id)
{
m_seconds = seconds;
clock_t t1, t2;
t1 = t2 = clock() / CLOCKS_PER_SEC;
while (m_seconds)
else
{
if (t1 / CLOCKS_PER_SEC + 1 <= (t2 = clock()) / CLOCKS_PER_SEC)
m_cfg_entries[m_button_id].key = ((keyboard_pad_handler*)m_handler.get())->GetKeyName(keyEvent);
m_cfg_entries[m_button_id].text = qstr(m_cfg_entries[m_button_id].key);
}
ReactivateButtons();
}
void pad_settings_dialog::UpdateLabel(bool is_reset)
{
if (is_reset)
{
ui->chb_vibration_large->setChecked((bool)m_handler_cfg->enable_vibration_motor_large);
ui->chb_vibration_small->setChecked((bool)m_handler_cfg->enable_vibration_motor_small);
ui->chb_vibration_switch->setChecked((bool)m_handler_cfg->switch_vibration_motors);
ui->slider_trigger_left->setValue(m_handler_cfg->ltriggerthreshold);
ui->slider_trigger_right->setValue(m_handler_cfg->rtriggerthreshold);
ui->slider_stick_left->setValue(m_handler_cfg->lstickdeadzone);
ui->slider_stick_right->setValue(m_handler_cfg->rstickdeadzone);
}
for (auto& entry : m_cfg_entries)
{
if (is_reset)
{
UpdateTimerLabel(id);
m_seconds--;
t1 = t2;
entry.second.key = *entry.second.cfg_name;
entry.second.text = qstr(entry.second.key);
}
if (m_key_pressed)
{
m_seconds = 0;
break;
}
m_padButtons->button(entry.first)->setText(entry.second.text);
}
}
const QString pad_settings_dialog::GetKeyName(const u32 keyCode)
void pad_settings_dialog::SwitchButtons(bool is_enabled)
{
//TODO what about numpad?
return QKeySequence(keyCode).toString();
for (int i = button_ids::id_pad_begin + 1; i < button_ids::id_pad_end; i++)
{
m_padButtons->button(i)->setEnabled(is_enabled);
}
}
void pad_settings_dialog::SaveConfig()
{
for (const auto& entry : m_cfg_entries)
{
entry.second.cfg_name->from_string(entry.second.key);
}
m_handler_cfg->enable_vibration_motor_large.set(ui->chb_vibration_large->isChecked());
m_handler_cfg->enable_vibration_motor_small.set(ui->chb_vibration_small->isChecked());
m_handler_cfg->switch_vibration_motors.set(ui->chb_vibration_switch->isChecked());
m_handler_cfg->ltriggerthreshold.set(ui->slider_trigger_left->value());
m_handler_cfg->rtriggerthreshold.set(ui->slider_trigger_right->value());
m_handler_cfg->lstickdeadzone.set(ui->slider_stick_left->value());
m_handler_cfg->rstickdeadzone.set(ui->slider_stick_right->value());
m_handler_cfg->save();
}
void pad_settings_dialog::OnPadButtonClicked(int id)
{
if (id != id_reset_parameters && id != id_ok)
switch (id)
{
m_button_id = id;
SwitchButtons(false); // disable all buttons, needed for using Space, Enter and other specific buttons
//RunTimer(3, event.GetId()); // TODO: Currently, timer disabled. Use by later, have some strange problems
//SwitchButtons(true); // needed, if timer enabled
UpdateLabel();
case button_ids::id_pad_begin:
case button_ids::id_pad_end:
case button_ids::id_cancel:
return;
case button_ids::id_reset_parameters:
ReactivateButtons();
m_handler_cfg->from_default();
UpdateLabel(true);
return;
case button_ids::id_ok:
SaveConfig();
QDialog::accept();
return;
default:
break;
}
else
{
switch (id)
{
case id_reset_parameters: g_kbpad_config.from_default(); UpdateLabel(); break;
case id_ok: g_kbpad_config.save(); QDialog::accept(); break;
case id_cancel: break;
}
}
m_button_id = id;
m_padButtons->button(m_button_id)->setText(tr("[ Waiting %1 ]").arg(MAX_SECONDS));
m_padButtons->button(m_button_id)->setPalette(QPalette(Qt::blue));
SwitchButtons(false); // disable all buttons, needed for using Space, Enter and other specific buttons
m_timer.start(1000);
}

View file

@ -3,6 +3,9 @@
#include <QDialog>
#include <QEvent>
#include <QKeyEvent>
#include <QTimer>
#include <QButtonGroup>
#include "keyboard_pad_handler.h"
#include "Utilities/types.h"
#include "Utilities/Config.h"
@ -11,43 +14,10 @@
#include "Emu/System.h"
#include "gui_settings.h"
enum button_ids
{
id_pad_lstick_left = 0x1,
id_pad_lstick_down,
id_pad_lstick_right,
id_pad_lstick_up,
id_pad_left,
id_pad_down,
id_pad_right,
id_pad_up,
id_pad_l1,
id_pad_l2,
id_pad_l3,
id_pad_start,
id_pad_select,
id_pad_r1,
id_pad_r2,
id_pad_r3,
id_pad_square,
id_pad_cross,
id_pad_circle,
id_pad_triangle,
id_pad_rstick_left,
id_pad_rstick_down,
id_pad_rstick_right,
id_pad_rstick_up,
id_reset_parameters,
id_ok,
id_cancel
};
#ifdef _WIN32
#include "xinput_pad_handler.h"
#endif
#include "ds4_pad_handler.h"
namespace Ui
{
@ -58,22 +28,118 @@ class pad_settings_dialog : public QDialog
{
Q_OBJECT
enum button_ids
{
id_pad_begin, // begin
id_pad_lstick_left,
id_pad_lstick_down,
id_pad_lstick_right,
id_pad_lstick_up,
id_pad_left,
id_pad_down,
id_pad_right,
id_pad_up,
id_pad_l1,
id_pad_l2,
id_pad_l3,
id_pad_start,
id_pad_select,
id_pad_ps,
id_pad_r1,
id_pad_r2,
id_pad_r3,
id_pad_square,
id_pad_cross,
id_pad_circle,
id_pad_triangle,
id_pad_rstick_left,
id_pad_rstick_down,
id_pad_rstick_right,
id_pad_rstick_up,
id_pad_end, // end
id_reset_parameters,
id_ok,
id_cancel
};
enum handler_type
{
handler_type_keyboard,
handler_type_xinput,
handler_type_ds4,
handler_type_mmjoy,
handler_type_evdev
};
struct pad_button
{
cfg::string* cfg_name;
std::string key;
QString text;
};
private Q_SLOTS:
void OnPadButtonClicked(int id);
private:
u32 m_seconds;
u32 m_button_id;
bool m_key_pressed;
Ui::pad_settings_dialog *ui;
// Button Mapping
QButtonGroup* m_padButtons;
u32 m_button_id = id_pad_begin;
std::map<int /*id*/, pad_button /*info*/> m_cfg_entries;
// Real time stick values
int lx = 0;
int ly = 0;
int rx = 0;
int ry = 0;
// Backup for standard button palette
QPalette m_palette;
// Pad Handlers
std::shared_ptr<PadHandlerBase> m_handler;
handler_type m_handler_type;
pad_config* m_handler_cfg;
std::string m_device_name;
// Remap Timer
const int MAX_SECONDS = 5;
int m_seconds = MAX_SECONDS;
QTimer m_timer;
// Input timer. Its Callback handles the input
QTimer m_timer_input;
/** Resets the view to default. Resets the Remap Timer */
void ReactivateButtons();
/** Repaints a stick deadzone preview label */
void RepaintPreviewLabel(QLabel* l, int dz, int w, int x, int y);
public:
explicit pad_settings_dialog(QWidget *parent = nullptr);
explicit pad_settings_dialog(const std::string& device, std::shared_ptr<PadHandlerBase> handler, QWidget *parent = nullptr);
~pad_settings_dialog();
/** Handle keyboard handler input */
void keyPressEvent(QKeyEvent *keyEvent) override;
void UpdateLabel();
void UpdateTimerLabel(const u32 id);
void SwitchButtons(const bool IsEnabled);
void RunTimer(const u32 seconds, const u32 id);
const QString GetKeyName(const u32 keyCode);
/** Update all the Button Labels with current button mapping */
void UpdateLabel(bool is_reset = false);
/** Enable/Disable Buttons while trying to remap an other */
void SwitchButtons(bool is_enabled);
/** Save the Pad Configuration to the current Pad Handler Config File */
void SaveConfig();
};

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>892</width>
<height>554</height>
<height>568</height>
</rect>
</property>
<property name="windowTitle">
@ -23,64 +23,6 @@
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Controller to Configure</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QComboBox" name="cb_controller"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Profiles</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<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_profile">
<property name="text">
<string>Manage Profiles</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
@ -389,7 +331,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_up_lstick">
<widget class="QPushButton" name="b_lstick_up">
<property name="text">
<string>Up</string>
</property>
@ -438,7 +380,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_left_lstick">
<widget class="QPushButton" name="b_lstick_left">
<property name="text">
<string>Left</string>
</property>
@ -469,7 +411,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_right_lstick">
<widget class="QPushButton" name="b_lstick_right">
<property name="text">
<string>Right</string>
</property>
@ -530,7 +472,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_down_lstick">
<widget class="QPushButton" name="b_lstick_down">
<property name="text">
<string>Down</string>
</property>
@ -561,6 +503,138 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_triggers">
<property name="minimumSize">
<size>
<width>0</width>
<height>80</height>
</size>
</property>
<property name="title">
<string>Trigger Thresholds</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_21">
<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>
<layout class="QVBoxLayout" name="gb_trigger_left">
<item>
<widget class="QSlider" name="slider_trigger_left">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="preview_trigger_left">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="gb_trigger_right">
<item>
<widget class="QSlider" name="slider_trigger_right">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="preview_trigger_right">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_vibration">
<property name="title">
<string>Enable Vibration</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QCheckBox" name="chb_vibration_large">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Large</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chb_vibration_small">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Small</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chb_vibration_switch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Switch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
@ -818,11 +892,8 @@
</property>
<item>
<widget class="QPushButton" name="b_ps">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Null</string>
<string>Backspace</string>
</property>
<property name="autoDefault">
<bool>false</bool>
@ -1107,86 +1178,6 @@
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Controller Status</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<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="QComboBox" name="cb_status"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Features</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QCheckBox" name="chb_sixaxis">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Sixaxis</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chb_vibration">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Vibration</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_20">
<property name="title">
@ -1489,7 +1480,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_up_rstick">
<widget class="QPushButton" name="b_rstick_up">
<property name="text">
<string>PgUp</string>
</property>
@ -1538,7 +1529,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_left_rstick">
<widget class="QPushButton" name="b_rstick_left">
<property name="text">
<string>Home</string>
</property>
@ -1569,7 +1560,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_right_rstick">
<widget class="QPushButton" name="b_rstick_right">
<property name="text">
<string>End</string>
</property>
@ -1630,7 +1621,7 @@
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="b_down_rstick">
<widget class="QPushButton" name="b_rstick_down">
<property name="text">
<string>PgDown</string>
</property>
@ -1661,6 +1652,69 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_sticks">
<property name="title">
<string>Analog Deadzones</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_22">
<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>
<layout class="QVBoxLayout" name="gb_stick_left">
<item>
<widget class="QSlider" name="slider_stick_left">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="preview_stick_left">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="gb_stick_right">
<item>
<widget class="QSlider" name="slider_stick_right">
<property name="enabled">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="preview_stick_right">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">

View file

@ -2,7 +2,7 @@
#include "register_editor_dialog.h"
constexpr auto qstr = QString::fromStdString;
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
inline std::string sstr(const QVariant& _in) { return sstr(_in.toString()); }
register_editor_dialog::register_editor_dialog(QWidget *parent, u32 _pc, const std::shared_ptr<cpu_thread>& _cpu, CPUDisAsm* _disasm)

View file

@ -21,7 +21,7 @@ namespace
{
// Helper converters
constexpr auto qstr = QString::fromStdString;
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
/**
* This certainly isn't ideal for this code, as it essentially copies cellSaveData. But, I have no other choice without adding public methods to cellSaveData.

View file

@ -23,7 +23,7 @@
#include <unordered_set>
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
inline std::string sstr(const QVariant& _in) { return sstr(_in.toString()); }
settings_dialog::settings_dialog(std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, const int& tabIndex, QWidget *parent, const GameInfo* game)

View file

@ -22,7 +22,7 @@ class settings_dialog : public QDialog
public:
explicit settings_dialog(std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, const int& tabIndex = 0, QWidget *parent = 0, const GameInfo *game = nullptr);
~settings_dialog();
int exec();
int exec() override;
Q_SIGNALS:
void GuiSettingsSyncRequest(bool configure_all);
void GuiStylesheetRequest(const QString& path);
@ -51,5 +51,5 @@ private:
QHash<QObject*, QString> m_descriptions;
void SubscribeDescription(QLabel* description);
void SubscribeTooltip(QObject* object, const QString& tooltip);
bool eventFilter(QObject* object, QEvent* event);
bool eventFilter(QObject* object, QEvent* event) override;
};

View file

@ -7,7 +7,7 @@
#include <QVBoxLayout>
#include <QPushButton>
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
vfs_dialog::vfs_dialog(std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, QWidget* parent)
: QDialog(parent), m_gui_settings(guiSettings), m_emu_settings(emuSettings)

View file

@ -5,7 +5,7 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); }
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
vfs_dialog_tab::vfs_dialog_tab(const vfs_settings_info& settingsInfo, std::shared_ptr<gui_settings> guiSettings, std::shared_ptr<emu_settings> emuSettings, QWidget* parent)
: QWidget(parent), m_info(settingsInfo), m_gui_settings(guiSettings), m_emu_settings(emuSettings)

View file

@ -1,41 +1,68 @@
#include "stdafx.h"
#ifdef _MSC_VER
#include "xinput_pad_handler.h"
#include "rpcs3qt/pad_settings_dialog.h"
xinput_config xinput_cfg;
namespace {
const DWORD THREAD_TIMEOUT = 1000;
const DWORD THREAD_SLEEP = 10;
const DWORD THREAD_SLEEP_INACTIVE = 100;
const DWORD MAX_GAMEPADS = 4;
const DWORD XINPUT_GAMEPAD_GUIDE = 0x0400;
const DWORD XINPUT_GAMEPAD_BUTTONS = 16;
const LPCWSTR LIBRARY_FILENAMES[] = {
L"xinput1_4.dll",
L"xinput1_3.dll",
L"xinput1_2.dll",
L"xinput9_1_0.dll"
};
inline u16 Clamp0To255(f32 input)
{
if (input > 255.f)
return 255;
else if (input < 0.f)
return 0;
else return static_cast<u16>(input);
}
inline u16 ConvertAxis(float value)
{
return static_cast<u16>((value + 1.0)*(255.0 / 2.0));
}
}
xinput_pad_handler::xinput_pad_handler() : library(nullptr), xinputGetState(nullptr), xinputEnable(nullptr), xinputSetState(nullptr), is_init(false)
xinput_pad_handler::xinput_pad_handler() :
library(nullptr), xinputGetState(nullptr), xinputEnable(nullptr),
xinputSetState(nullptr), xinputGetBatteryInformation(nullptr), is_init(false)
{
// Define border values
thumb_min = -32768;
thumb_max = 32767;
trigger_min = 0;
trigger_max = 255;
vibration_min = 0;
vibration_max = 65535;
// Set this handler's type and save location
m_pad_config.cfg_type = "xinput";
m_pad_config.cfg_name = fs::get_config_dir() + "/config_xinput.yml";
// Set default button mapping
m_pad_config.ls_left.def = button_list.at(XInputKeyCodes::LSXNeg);
m_pad_config.ls_down.def = button_list.at(XInputKeyCodes::LSYNeg);
m_pad_config.ls_right.def = button_list.at(XInputKeyCodes::LSXPos);
m_pad_config.ls_up.def = button_list.at(XInputKeyCodes::LSYPos);
m_pad_config.rs_left.def = button_list.at(XInputKeyCodes::RSXNeg);
m_pad_config.rs_down.def = button_list.at(XInputKeyCodes::RSYNeg);
m_pad_config.rs_right.def = button_list.at(XInputKeyCodes::RSXPos);
m_pad_config.rs_up.def = button_list.at(XInputKeyCodes::RSYPos);
m_pad_config.start.def = button_list.at(XInputKeyCodes::Start);
m_pad_config.select.def = button_list.at(XInputKeyCodes::Back);
m_pad_config.ps.def = button_list.at(XInputKeyCodes::Guide);
m_pad_config.square.def = button_list.at(XInputKeyCodes::X);
m_pad_config.cross.def = button_list.at(XInputKeyCodes::A);
m_pad_config.circle.def = button_list.at(XInputKeyCodes::B);
m_pad_config.triangle.def = button_list.at(XInputKeyCodes::Y);
m_pad_config.left.def = button_list.at(XInputKeyCodes::Left);
m_pad_config.down.def = button_list.at(XInputKeyCodes::Down);
m_pad_config.right.def = button_list.at(XInputKeyCodes::Right);
m_pad_config.up.def = button_list.at(XInputKeyCodes::Up);
m_pad_config.r1.def = button_list.at(XInputKeyCodes::RB);
m_pad_config.r2.def = button_list.at(XInputKeyCodes::RT);
m_pad_config.r3.def = button_list.at(XInputKeyCodes::RS);
m_pad_config.l1.def = button_list.at(XInputKeyCodes::RB);
m_pad_config.l2.def = button_list.at(XInputKeyCodes::LT);
m_pad_config.l3.def = button_list.at(XInputKeyCodes::LS);
// Set default misc variables
m_pad_config.lstickdeadzone.def = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE; // between 0 and 32767
m_pad_config.rstickdeadzone.def = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE; // between 0 and 32767
m_pad_config.ltriggerthreshold.def = XINPUT_GAMEPAD_TRIGGER_THRESHOLD; // between 0 and 255
m_pad_config.rtriggerthreshold.def = XINPUT_GAMEPAD_TRIGGER_THRESHOLD; // between 0 and 255
m_pad_config.padsquircling.def = 8000;
// apply defaults
m_pad_config.from_default();
// set capabilities
b_has_config = true;
b_has_rumble = true;
b_has_deadzones = true;
m_trigger_threshold = trigger_max / 2;
m_thumb_threshold = thumb_max / 2;
}
xinput_pad_handler::~xinput_pad_handler()
@ -43,11 +70,198 @@ xinput_pad_handler::~xinput_pad_handler()
Close();
}
void xinput_pad_handler::GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback)
{
if (!Init())
{
return;
}
size_t pos = padId.find("Xinput Pad #");
int device_number;
if (pos != std::string::npos)
{
device_number = std::stoul(padId.substr(pos + 12));
}
if (pos == std::string::npos || device_number >= XUSER_MAX_COUNT)
{
return;
}
DWORD dwResult;
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
// Simply get the state of the controller from XInput.
dwResult = (*xinputGetState)(device_number, &state);
if (dwResult != ERROR_SUCCESS)
{
return;
}
// 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, "" };
auto data = GetButtonValues(state);
for (const auto& button : button_list)
{
u32 keycode = button.first;
u16 value = data[keycode];
if (((keycode < XInputKeyCodes::LT) && (value > 0))
|| ((keycode == XInputKeyCodes::LT) && (value > m_trigger_threshold))
|| ((keycode == XInputKeyCodes::RT) && (value > m_trigger_threshold))
|| ((keycode >= XInputKeyCodes::LSXNeg && keycode <= XInputKeyCodes::LSYPos) && (value > m_thumb_threshold))
|| ((keycode >= XInputKeyCodes::RSXNeg && keycode <= XInputKeyCodes::RSYPos) && (value > m_thumb_threshold)))
{
if (value > pressed_button.first)
{
pressed_button = { value, button.second };
}
}
}
int preview_values[6] = { data[LT], data[RT], data[LSXPos] - data[LSXNeg], data[LSYPos] - data[LSYNeg], data[RSXPos] - data[RSXNeg], data[RSYPos] - data[RSYNeg] };
if (pressed_button.first > 0)
return callback(pressed_button.first, pressed_button.second, preview_values);
else
return callback(0, "", preview_values);
}
void xinput_pad_handler::TestVibration(const std::string& padId, u32 largeMotor, u32 smallMotor)
{
if (!Init())
{
return;
}
u32 device_number = 0;
size_t pos = padId.find("Xinput Pad #");
if (pos != std::string::npos)
{
device_number = std::stoul(padId.substr(pos + 12));
}
if (pos == std::string::npos || device_number >= XUSER_MAX_COUNT)
{
return;
}
// The left motor is the low-frequency rumble motor. The right motor is the high-frequency rumble motor.
// The two motors are not the same, and they create different vibration effects.
XINPUT_VIBRATION vibrate;
vibrate.wLeftMotorSpeed = largeMotor; // between 0 to 65535
vibrate.wRightMotorSpeed = smallMotor; // between 0 to 65535
(*xinputSetState)(device_number, &vibrate);
}
void xinput_pad_handler::TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold)
{
// Update the pad button values based on their type and thresholds.
// With this you can use axis or triggers as buttons or vice versa
switch (keyCode)
{
case XInputKeyCodes::LT:
pressed = val > m_pad_config.ltriggerthreshold;
val = pressed ? NormalizeTriggerInput(val, m_pad_config.ltriggerthreshold) : 0;
break;
case XInputKeyCodes::RT:
pressed = val > m_pad_config.rtriggerthreshold;
val = pressed ? NormalizeTriggerInput(val, m_pad_config.rtriggerthreshold) : 0;
break;
case XInputKeyCodes::LSXNeg:
case XInputKeyCodes::LSXPos:
case XInputKeyCodes::LSYPos:
case XInputKeyCodes::LSYNeg:
pressed = val > (ignore_threshold ? 0 : m_pad_config.lstickdeadzone);
val = pressed ? NormalizeStickInput(val, m_pad_config.lstickdeadzone, ignore_threshold) : 0;
break;
case XInputKeyCodes::RSXNeg:
case XInputKeyCodes::RSXPos:
case XInputKeyCodes::RSYPos:
case XInputKeyCodes::RSYNeg:
pressed = val > (ignore_threshold ? 0 : m_pad_config.rstickdeadzone);
val = pressed ? NormalizeStickInput(val, m_pad_config.rstickdeadzone, ignore_threshold) : 0;
break;
default: // normal button (should in theory also support sensitive buttons)
pressed = val > 0;
val = pressed ? val : 0;
break;
}
}
std::array<u16, xinput_pad_handler::XInputKeyCodes::KeyCodeCount> xinput_pad_handler::GetButtonValues(const XINPUT_STATE& state)
{
std::array<u16, xinput_pad_handler::XInputKeyCodes::KeyCodeCount> values;
// Triggers
values[XInputKeyCodes::LT] = state.Gamepad.bLeftTrigger;
values[XInputKeyCodes::RT] = state.Gamepad.bRightTrigger;
// Sticks
int lx = state.Gamepad.sThumbLX;
int ly = state.Gamepad.sThumbLY;
int rx = state.Gamepad.sThumbRX;
int ry = state.Gamepad.sThumbRY;
// Left Stick X Axis
values[XInputKeyCodes::LSXNeg] = lx < 0 ? abs(lx) - 1 : 0;
values[XInputKeyCodes::LSXPos] = lx > 0 ? lx : 0;
// Left Stick Y Axis
values[XInputKeyCodes::LSYNeg] = ly < 0 ? abs(ly) - 1 : 0;
values[XInputKeyCodes::LSYPos] = ly > 0 ? ly : 0;
// Right Stick X Axis
values[XInputKeyCodes::RSXNeg] = rx < 0 ? abs(rx) - 1 : 0;
values[XInputKeyCodes::RSXPos] = rx > 0 ? rx : 0;
// Right Stick Y Axis
values[XInputKeyCodes::RSYNeg] = ry < 0 ? abs(ry) - 1 : 0;
values[XInputKeyCodes::RSYPos] = ry > 0 ? ry : 0;
// Buttons
WORD buttons = state.Gamepad.wButtons;
// A, B, X, Y
values[XInputKeyCodes::A] = buttons & XINPUT_GAMEPAD_A ? 255 : 0;
values[XInputKeyCodes::B] = buttons & XINPUT_GAMEPAD_B ? 255 : 0;
values[XInputKeyCodes::X] = buttons & XINPUT_GAMEPAD_X ? 255 : 0;
values[XInputKeyCodes::Y] = buttons & XINPUT_GAMEPAD_Y ? 255 : 0;
// D-Pad
values[XInputKeyCodes::Left] = buttons & XINPUT_GAMEPAD_DPAD_LEFT ? 255 : 0;
values[XInputKeyCodes::Right] = buttons & XINPUT_GAMEPAD_DPAD_RIGHT ? 255 : 0;
values[XInputKeyCodes::Up] = buttons & XINPUT_GAMEPAD_DPAD_UP ? 255 : 0;
values[XInputKeyCodes::Down] = buttons & XINPUT_GAMEPAD_DPAD_DOWN ? 255 : 0;
// LB, RB, LS, RS
values[XInputKeyCodes::LB] = buttons & XINPUT_GAMEPAD_LEFT_SHOULDER ? 255 : 0;
values[XInputKeyCodes::RB] = buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER ? 255 : 0;
values[XInputKeyCodes::LS] = buttons & XINPUT_GAMEPAD_LEFT_THUMB ? 255 : 0;
values[XInputKeyCodes::RS] = buttons & XINPUT_GAMEPAD_RIGHT_THUMB ? 255 : 0;
// Start, Back, Guide
values[XInputKeyCodes::Start] = buttons & XINPUT_GAMEPAD_START ? 255 : 0;
values[XInputKeyCodes::Back] = buttons & XINPUT_GAMEPAD_BACK ? 255 : 0;
values[XInputKeyCodes::Guide] = buttons & XINPUT_INFO::GUIDE_BUTTON ? 255 : 0;
return values;
}
bool xinput_pad_handler::Init()
{
if (is_init) return true;
for (auto it : LIBRARY_FILENAMES)
for (auto it : XINPUT_INFO::LIBRARY_FILENAMES)
{
library = LoadLibrary(it);
if (library)
@ -60,8 +274,9 @@ bool xinput_pad_handler::Init()
}
xinputSetState = reinterpret_cast<PFN_XINPUTSETSTATE>(GetProcAddress(library, "XInputSetState"));
xinputGetBatteryInformation = reinterpret_cast<PFN_XINPUTGETBATTERYINFORMATION>(GetProcAddress(library, "XInputGetBatteryInformation"));
if (xinputEnable && xinputGetState && xinputSetState)
if (xinputEnable && xinputGetState && xinputSetState && xinputGetBatteryInformation)
{
is_init = true;
break;
@ -71,17 +286,14 @@ bool xinput_pad_handler::Init()
library = nullptr;
xinputEnable = nullptr;
xinputGetState = nullptr;
xinputGetBatteryInformation = nullptr;
}
}
if (!is_init) return false;
xinput_cfg.load();
if (!xinput_cfg.exist()) xinput_cfg.save();
squircle_factor = xinput_cfg.padsquircling / 1000.f;
left_stick_deadzone = xinput_cfg.lstickdeadzone;
right_stick_deadzone = xinput_cfg.rstickdeadzone;
m_pad_config.load();
if (!m_pad_config.exist()) m_pad_config.save();
return true;
}
@ -94,36 +306,17 @@ void xinput_pad_handler::Close()
library = nullptr;
xinputGetState = nullptr;
xinputEnable = nullptr;
xinputGetBatteryInformation = nullptr;
}
}
std::tuple<u16, u16> xinput_pad_handler::ConvertToSquirclePoint(u16 inX, u16 inY)
{
// convert inX and Y to a (-1, 1) vector;
const f32 x = (inX - 127) / 127.f;
const f32 y = ((inY - 127) / 127.f);
// compute angle and len of given point to be used for squircle radius
const f32 angle = std::atan2(y, x);
const f32 r = std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f));
// now find len/point on the given squircle from our current angle and radius in polar coords
// https://thatsmaths.com/2016/07/14/squircles/
const f32 newLen = (1 + std::pow(std::sin(2 * angle), 2.f) / squircle_factor) * r;
// we now have len and angle, convert to cartisian
const int newX = Clamp0To255(((newLen * std::cos(angle)) + 1) * 127);
const int newY = Clamp0To255(((newLen * std::sin(angle)) + 1) * 127);
return std::tuple<u16, u16>(newX, newY);
}
void xinput_pad_handler::ThreadProc()
{
for (u32 index = 0; index != bindings.size(); index++)
for (auto &bind : bindings)
{
auto padnum = bindings[index].first;
auto pad = bindings[index].second;
auto device = bind.first;
auto padnum = device->deviceNumber;
auto pad = bind.second;
result = (*xinputGetState)(padnum, &state);
switch (result)
@ -142,77 +335,95 @@ void xinput_pad_handler::ThreadProc()
last_connection_status[padnum] = true;
pad->m_port_status |= CELL_PAD_STATUS_CONNECTED;
for (DWORD j = 0; j != XINPUT_GAMEPAD_BUTTONS; ++j)
std::array<u16, XInputKeyCodes::KeyCodeCount> button_values = GetButtonValues(state);
// Translate any corresponding keycodes to our normal DS3 buttons and triggers
for (auto& btn : pad->m_buttons)
{
bool pressed = state.Gamepad.wButtons & (1 << j);
pad->m_buttons[j].m_pressed = pressed;
pad->m_buttons[j].m_value = pressed ? 255 : 0;
btn.m_value = button_values[btn.m_keyCode];
TranslateButtonPress(btn.m_keyCode, btn.m_pressed, btn.m_value);
}
for (int i = 6; i < 16; i++)
for (const auto& btn : pad->m_buttons)
{
if (pad->m_buttons[i].m_pressed)
if (btn.m_pressed)
{
SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
break;
}
}
pad->m_buttons[XINPUT_GAMEPAD_BUTTONS].m_pressed = state.Gamepad.bLeftTrigger > 0;
pad->m_buttons[XINPUT_GAMEPAD_BUTTONS].m_value = state.Gamepad.bLeftTrigger;
pad->m_buttons[XINPUT_GAMEPAD_BUTTONS + 1].m_pressed = state.Gamepad.bRightTrigger > 0;
pad->m_buttons[XINPUT_GAMEPAD_BUTTONS + 1].m_value = state.Gamepad.bRightTrigger;
// used to get the absolute value of an axis
float stick_val[4];
float LX, LY, RX, RY;
LX = state.Gamepad.sThumbLX;
LY = state.Gamepad.sThumbLY;
RX = state.Gamepad.sThumbRX;
RY = state.Gamepad.sThumbRY;
auto normalize_input = [](float& X, float& Y, float deadzone)
// Translate any corresponding keycodes to our two sticks. (ignoring thresholds for now)
for (int i = 0; i < static_cast<int>(pad->m_sticks.size()); i++)
{
X /= 32767.0f;
Y /= 32767.0f;
deadzone /= 32767.0f;
bool pressed;
float mag = sqrtf(X*X + Y*Y);
// m_keyCodeMin is the mapped key for left or down
u32 key_min = pad->m_sticks[i].m_keyCodeMin;
u16 val_min = button_values[key_min];
TranslateButtonPress(key_min, pressed, val_min, true);
if (mag > deadzone)
{
float legalRange = 1.0f - deadzone;
float normalizedMag = std::min(1.0f, (mag - deadzone) / legalRange);
float scale = normalizedMag / mag;
X = X * scale;
Y = Y * scale;
}
else
{
X = 0;
Y = 0;
}
};
// m_keyCodeMax is the mapped key for right or up
u32 key_max = pad->m_sticks[i].m_keyCodeMax;
u16 val_max = button_values[key_max];
TranslateButtonPress(key_max, pressed, val_max, true);
normalize_input(LX, LY, left_stick_deadzone);
normalize_input(RX, RY, right_stick_deadzone);
pad->m_sticks[0].m_value = ConvertAxis(LX);
pad->m_sticks[1].m_value = 255 - ConvertAxis(LY);
pad->m_sticks[2].m_value = ConvertAxis(RX);
pad->m_sticks[3].m_value = 255 - ConvertAxis(RY);
if (squircle_factor != 0.f)
{
std::tie(pad->m_sticks[0].m_value, pad->m_sticks[1].m_value) = ConvertToSquirclePoint(pad->m_sticks[0].m_value, pad->m_sticks[1].m_value);
std::tie(pad->m_sticks[2].m_value, pad->m_sticks[3].m_value) = ConvertToSquirclePoint(pad->m_sticks[2].m_value, pad->m_sticks[3].m_value);
// cancel out opposing values and get the resulting difference
stick_val[i] = val_max - val_min;
}
XINPUT_VIBRATION vibrate;
u16 lx, ly, rx, ry;
vibrate.wLeftMotorSpeed = pad->m_vibrateMotors[0].m_value * 257;
vibrate.wRightMotorSpeed = pad->m_vibrateMotors[1].m_value * 257;
// Normalize our two stick's axis based on the thresholds
std::tie(lx, ly) = NormalizeStickDeadzone(stick_val[0], stick_val[1], m_pad_config.lstickdeadzone);
std::tie(rx, ry) = NormalizeStickDeadzone(stick_val[2], stick_val[3], m_pad_config.rstickdeadzone);
(*xinputSetState)(padnum, &vibrate);
if (m_pad_config.padsquircling != 0)
{
std::tie(lx, ly) = ConvertToSquirclePoint(lx, ly, m_pad_config.padsquircling);
std::tie(rx, ry) = ConvertToSquirclePoint(rx, ry, m_pad_config.padsquircling);
}
pad->m_sticks[0].m_value = lx;
pad->m_sticks[1].m_value = 255 - ly;
pad->m_sticks[2].m_value = rx;
pad->m_sticks[3].m_value = 255 - ry;
// Receive Battery Info. If device is not on cable, get battery level, else assume full
XINPUT_BATTERY_INFORMATION battery_info;
(*xinputGetBatteryInformation)(padnum, BATTERY_DEVTYPE_GAMEPAD, &battery_info);
pad->m_cable_state = battery_info.BatteryType == BATTERY_TYPE_WIRED ? 1 : 0;
pad->m_battery_level = pad->m_cable_state ? BATTERY_LEVEL_FULL : battery_info.BatteryLevel;
// The left motor is the low-frequency rumble motor. The right motor is the high-frequency rumble motor.
// The two motors are not the same, and they create different vibration effects. Values range between 0 to 65535.
int idx_l = m_pad_config.switch_vibration_motors ? 1 : 0;
int idx_s = m_pad_config.switch_vibration_motors ? 0 : 1;
int speed_large = m_pad_config.enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value * 257 : vibration_min;
int speed_small = m_pad_config.enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value * 257 : vibration_min;
device->newVibrateData = device->newVibrateData || device->largeVibrate != speed_large || device->smallVibrate != speed_small;
device->largeVibrate = speed_large;
device->smallVibrate = speed_small;
// XBox One Controller can't handle faster vibration updates than ~10ms. Elite is even worse. So I'll use 20ms to be on the safe side. No lag was noticable.
if (device->newVibrateData && (clock() - device->last_vibration > 20))
{
XINPUT_VIBRATION vibrate;
vibrate.wLeftMotorSpeed = speed_large;
vibrate.wRightMotorSpeed = speed_small;
if ((*xinputSetState)(padnum, &vibrate) == ERROR_SUCCESS)
{
device->newVibrateData = false;
device->last_vibration = clock();
}
}
break;
}
@ -225,7 +436,7 @@ std::vector<std::string> xinput_pad_handler::ListDevices()
if (!Init()) return xinput_pads_list;
for (DWORD i = 0; i < MAX_GAMEPADS; i++)
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++)
{
XINPUT_STATE state;
DWORD result = (*xinputGetState)(i, &state);
@ -245,43 +456,54 @@ bool xinput_pad_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::st
if (pos != std::string::npos) device_number = std::stoul(device.substr(pos + 12));
if (pos == std::string::npos || device_number >= MAX_GAMEPADS) return false;
if (pos == std::string::npos || device_number >= XUSER_MAX_COUNT) return false;
pad->Init(
CELL_PAD_STATUS_DISCONNECTED,
std::shared_ptr<XInputDevice> device_id = std::make_shared<XInputDevice>();
device_id->deviceNumber = device_number;
m_pad_config.load();
pad->Init
(
CELL_PAD_STATUS_CONNECTED | CELL_PAD_STATUS_ASSIGN_CHANGES,
CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_ACTUATOR,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_HP_ANALOG_STICK | CELL_PAD_CAPABILITY_ACTUATOR | CELL_PAD_CAPABILITY_SENSOR_MODE,
CELL_PAD_DEV_TYPE_STANDARD
);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_DPAD_UP, CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_DPAD_DOWN, CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_DPAD_LEFT, CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_DPAD_RIGHT, CELL_PAD_CTRL_RIGHT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_START, CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_BACK, CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_LEFT_THUMB, CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, XINPUT_GAMEPAD_RIGHT_THUMB, CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_LEFT_SHOULDER, CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_RIGHT_SHOULDER, CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_GUIDE, 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.up), CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.down), CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.left), CELL_PAD_CTRL_LEFT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.right), CELL_PAD_CTRL_RIGHT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.start), CELL_PAD_CTRL_START);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.select), CELL_PAD_CTRL_SELECT);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.l3), CELL_PAD_CTRL_L3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, FindKeyCode(button_list, m_pad_config.r3), CELL_PAD_CTRL_R3);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.l1), CELL_PAD_CTRL_L1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.r1), CELL_PAD_CTRL_R1);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.ps), 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.cross), CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.circle), CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.square), CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.triangle), CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.l2), CELL_PAD_CTRL_L2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, FindKeyCode(button_list, m_pad_config.r2), CELL_PAD_CTRL_R2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x0); // Reserved
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_A, CELL_PAD_CTRL_CROSS);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_B, CELL_PAD_CTRL_CIRCLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_X, CELL_PAD_CTRL_SQUARE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, XINPUT_GAMEPAD_Y, CELL_PAD_CTRL_TRIANGLE);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, CELL_PAD_CTRL_L2);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, CELL_PAD_CTRL_R2);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, 0, 0);
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, FindKeyCode(button_list, m_pad_config.ls_left), FindKeyCode(button_list, m_pad_config.ls_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, FindKeyCode(button_list, m_pad_config.ls_down), FindKeyCode(button_list, m_pad_config.ls_up));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, FindKeyCode(button_list, m_pad_config.rs_left), FindKeyCode(button_list, m_pad_config.rs_right));
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, FindKeyCode(button_list, m_pad_config.rs_down), FindKeyCode(button_list, m_pad_config.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_vibrateMotors.emplace_back(true, 0);
pad->m_vibrateMotors.emplace_back(false, 0);
bindings.emplace_back(device_number, pad);
bindings.emplace_back(device_id, pad);
return true;
}

View file

@ -1,43 +1,102 @@
#ifndef X_INPUT_PAD_HANDLER
#define X_INPUT_PAD_HANDLER
#pragma once
#include "stdafx.h"
#include "Utilities/Config.h"
#include "Emu/Io/PadHandler.h"
#define NOMINMAX
#include <Windows.h>
#include <Xinput.h>
#include <ctime>
struct xinput_config final : cfg::node
namespace XINPUT_INFO
{
const std::string cfg_name = fs::get_config_dir() + "/config_xinput.yml";
cfg::_int<0, 1000000> lstickdeadzone{ this, "Left Stick Deadzone", 7849 };
cfg::_int<0, 1000000> rstickdeadzone{ this, "Right Stick Deadzone", 8689 };
cfg::_int<0, 1000000> padsquircling{ this, "Pad Squircling Factor", 8000 };
bool load()
{
if (fs::file cfg_file{ cfg_name, fs::read })
{
return from_string(cfg_file.to_string());
}
return false;
}
void save()
{
fs::file(cfg_name, fs::rewrite).write(to_string());
}
bool exist()
{
return fs::is_file(cfg_name);
}
};
const DWORD THREAD_TIMEOUT = 1000;
const DWORD THREAD_SLEEP = 10;
const DWORD THREAD_SLEEP_INACTIVE = 100;
const DWORD GUIDE_BUTTON = 0x0400;
const LPCWSTR LIBRARY_FILENAMES[] = {
L"xinput1_4.dll",
L"xinput1_3.dll",
L"xinput1_2.dll",
L"xinput9_1_0.dll"
};
}
class xinput_pad_handler final : public PadHandlerBase
{
// These are all the possible buttons on a standard xbox 360 or xbox one controller
enum XInputKeyCodes
{
A,
B,
X,
Y,
Left,
Right,
Up,
Down,
LB,
RB,
LS,
RS,
Start,
Back,
Guide,
LT,
RT,
LSXNeg,
LSXPos,
LSYNeg,
LSYPos,
RSXNeg,
RSXPos,
RSYNeg,
RSYPos,
KeyCodeCount
};
// Unique names for the config files and our pad settings dialog
const std::unordered_map<u32, std::string> button_list =
{
{ XInputKeyCodes::A, "A" },
{ XInputKeyCodes::B, "B" },
{ XInputKeyCodes::X, "X" },
{ XInputKeyCodes::Y, "Y" },
{ XInputKeyCodes::Left, "Left" },
{ XInputKeyCodes::Right, "Right" },
{ XInputKeyCodes::Up, "Up" },
{ XInputKeyCodes::Down, "Down" },
{ XInputKeyCodes::LB, "LB" },
{ XInputKeyCodes::RB, "RB" },
{ XInputKeyCodes::Back, "Back" },
{ XInputKeyCodes::Start, "Start" },
{ XInputKeyCodes::LS, "LS" },
{ XInputKeyCodes::RS, "RS" },
{ XInputKeyCodes::Guide, "Guide" },
{ XInputKeyCodes::LT, "LT" },
{ XInputKeyCodes::RT, "RT" },
{ XInputKeyCodes::LSXNeg, "LS X-" },
{ XInputKeyCodes::LSXPos, "LS X+" },
{ XInputKeyCodes::LSYPos, "LS Y+" },
{ XInputKeyCodes::LSYNeg, "LS Y-" },
{ XInputKeyCodes::RSXNeg, "RS X-" },
{ XInputKeyCodes::RSXPos, "RS X+" },
{ XInputKeyCodes::RSYPos, "RS Y+" },
{ XInputKeyCodes::RSYNeg, "RS Y-" }
};
struct XInputDevice
{
u32 deviceNumber{ 0 };
bool newVibrateData{ true };
u8 largeVibrate{ 0 };
u8 smallVibrate{ 0 };
clock_t last_vibration{ 0 };
};
public:
xinput_pad_handler();
~xinput_pad_handler();
@ -48,25 +107,27 @@ public:
std::vector<std::string> ListDevices() override;
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
void ThreadProc() override;
void GetNextButtonPress(const std::string& padId, const std::function<void(u16, std::string, int[])>& callback) override;
void TestVibration(const std::string& padId, u32 largeMotor, u32 smallMotor) override;
private:
typedef void (WINAPI * PFN_XINPUTENABLE)(BOOL);
typedef DWORD (WINAPI * PFN_XINPUTGETSTATE)(DWORD, XINPUT_STATE *);
typedef DWORD (WINAPI * PFN_XINPUTSETSTATE)(DWORD, XINPUT_VIBRATION *);
typedef DWORD (WINAPI * PFN_XINPUTGETBATTERYINFORMATION)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
private:
std::tuple<u16, u16> ConvertToSquirclePoint(u16 inX, u16 inY);
std::array<u16, XInputKeyCodes::KeyCodeCount> GetButtonValues(const XINPUT_STATE& state);
void TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold = false) override;
private:
bool is_init;
float squircle_factor;
u32 left_stick_deadzone, right_stick_deadzone;
HMODULE library;
PFN_XINPUTGETSTATE xinputGetState;
PFN_XINPUTSETSTATE xinputSetState;
PFN_XINPUTENABLE xinputEnable;
PFN_XINPUTGETBATTERYINFORMATION xinputGetBatteryInformation;
std::vector<std::pair<u32, std::shared_ptr<Pad>>> bindings;
std::vector<std::pair<std::shared_ptr<XInputDevice>, std::shared_ptr<Pad>>> bindings;
std::array<bool, 7> last_connection_status = {};
// holds internal controller state change
@ -74,7 +135,3 @@ private:
DWORD result;
DWORD online = 0;
};
extern xinput_config xinput_cfg;
#endif