mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-04-04 14:08:37 +00:00
Move input to its own directory (#7126)
This commit is contained in:
parent
f03cb5c9c0
commit
e9c5c6e6bf
28 changed files with 67 additions and 66 deletions
296
rpcs3/Input/basic_keyboard_handler.cpp
Normal file
296
rpcs3/Input/basic_keyboard_handler.cpp
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "basic_keyboard_handler.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "Emu/System.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "windows.h"
|
||||
#endif
|
||||
|
||||
void basic_keyboard_handler::Init(const u32 max_connect)
|
||||
{
|
||||
for (u32 i = 0; i < max_connect; i++)
|
||||
{
|
||||
Keyboard kb = Keyboard();
|
||||
|
||||
kb.m_config.arrange = g_cfg.sys.keyboard_type;
|
||||
|
||||
m_keyboards.emplace_back();
|
||||
}
|
||||
|
||||
LoadSettings();
|
||||
memset(&m_info, 0, sizeof(KbInfo));
|
||||
m_info.max_connect = max_connect;
|
||||
m_info.now_connect = std::min(::size32(m_keyboards), max_connect);
|
||||
m_info.info = 0; // Ownership of keyboard data: 0=Application, 1=System
|
||||
m_info.status[0] = CELL_KB_STATUS_CONNECTED; // (TODO: Support for more keyboards)
|
||||
}
|
||||
|
||||
basic_keyboard_handler::basic_keyboard_handler() : QObject()
|
||||
{
|
||||
}
|
||||
|
||||
/* Sets the target window for the event handler, and also installs an event filter on the target. */
|
||||
void basic_keyboard_handler::SetTargetWindow(QWindow* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
{
|
||||
m_target = target;
|
||||
target->installEventFilter(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this is hit, it probably means that some refactoring occurs because currently a gsframe is created in Load.
|
||||
// We still want events so filter from application instead since target is null.
|
||||
QApplication::instance()->installEventFilter(this);
|
||||
LOG_ERROR(GENERAL, "Trying to set keyboard handler to a null target window.");
|
||||
}
|
||||
}
|
||||
|
||||
bool basic_keyboard_handler::eventFilter(QObject* target, QEvent* ev)
|
||||
{
|
||||
if (!ev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// !m_target is for future proofing when gsrender isn't automatically initialized on load.
|
||||
// !m_target->isVisible() is a hack since currently a guiless application will STILL inititialize a gsrender (providing a valid target)
|
||||
if (!m_target || !m_target->isVisible() || target == m_target)
|
||||
{
|
||||
if (ev->type() == QEvent::KeyPress)
|
||||
{
|
||||
keyPressEvent(static_cast<QKeyEvent*>(ev));
|
||||
}
|
||||
else if (ev->type() == QEvent::KeyRelease)
|
||||
{
|
||||
keyReleaseEvent(static_cast<QKeyEvent*>(ev));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void basic_keyboard_handler::keyPressEvent(QKeyEvent* keyEvent)
|
||||
{
|
||||
if (!keyEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_keyboards.empty() || (keyEvent->isAutoRepeat() && !m_keyboards[0].m_key_repeat))
|
||||
{
|
||||
keyEvent->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
const int key = getUnmodifiedKey(keyEvent);
|
||||
|
||||
if (key >= 0)
|
||||
{
|
||||
Key(static_cast<u32>(key), true);
|
||||
}
|
||||
}
|
||||
|
||||
void basic_keyboard_handler::keyReleaseEvent(QKeyEvent* keyEvent)
|
||||
{
|
||||
if (!keyEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_keyboards.empty() || (keyEvent->isAutoRepeat() && !m_keyboards[0].m_key_repeat))
|
||||
{
|
||||
keyEvent->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
const int key = getUnmodifiedKey(keyEvent);
|
||||
|
||||
if (key >= 0)
|
||||
{
|
||||
Key(static_cast<u32>(key), false);
|
||||
}
|
||||
}
|
||||
|
||||
// This should get the actual unmodified key without getting too crazy.
|
||||
// key() only shows the modifiers and the modified key (e.g. no easy way of knowing that - was pressed in 'SHIFT+-' in order to get _)
|
||||
s32 basic_keyboard_handler::getUnmodifiedKey(QKeyEvent* keyEvent)
|
||||
{
|
||||
if (!keyEvent)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int key = keyEvent->key();
|
||||
|
||||
if (key < 0)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
u32 raw_key = static_cast<u32>(key);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (keyEvent->modifiers() != Qt::NoModifier && !keyEvent->text().isEmpty())
|
||||
{
|
||||
u32 mapped_key = (u32)MapVirtualKeyA((UINT)keyEvent->nativeVirtualKey(), MAPVK_VK_TO_CHAR);
|
||||
|
||||
if (raw_key != mapped_key)
|
||||
{
|
||||
if (mapped_key > 0x80000000) // diacritics
|
||||
{
|
||||
mapped_key -= 0x80000000;
|
||||
}
|
||||
raw_key = mapped_key;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return static_cast<s32>(raw_key);
|
||||
}
|
||||
|
||||
void basic_keyboard_handler::LoadSettings()
|
||||
{
|
||||
if (m_keyboards.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Meta Keys
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_Control, CELL_KB_MKEY_L_CTRL);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Shift, CELL_KB_MKEY_L_SHIFT);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Alt, CELL_KB_MKEY_L_ALT);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Super_L, CELL_KB_MKEY_L_WIN);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KB_MKEY_R_CTRL);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KB_MKEY_R_SHIFT);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KB_MKEY_R_ALT);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Super_R, CELL_KB_MKEY_R_WIN);
|
||||
|
||||
// CELL_KB_RAWDAT
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KEYC_NO_EVENT);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KEYC_E_ROLLOVER);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KEYC_E_POSTFAIL);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KEYC_E_UNDEF);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Escape, CELL_KEYC_ESCAPE);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Kanji, CELL_KEYC_106_KANJI);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_CapsLock, CELL_KEYC_CAPS_LOCK);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F1, CELL_KEYC_F1);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F2, CELL_KEYC_F2);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F3, CELL_KEYC_F3);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F4, CELL_KEYC_F4);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F5, CELL_KEYC_F5);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F6, CELL_KEYC_F6);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F7, CELL_KEYC_F7);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F8, CELL_KEYC_F8);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F9, CELL_KEYC_F9);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F10, CELL_KEYC_F10);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F11, CELL_KEYC_F11);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F12, CELL_KEYC_F12);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Print, CELL_KEYC_PRINTSCREEN);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_ScrollLock, CELL_KEYC_SCROLL_LOCK);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Pause, CELL_KEYC_PAUSE);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Insert, CELL_KEYC_INSERT);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Home, CELL_KEYC_HOME);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_PageUp, CELL_KEYC_PAGE_UP);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Delete, CELL_KEYC_DELETE);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_End, CELL_KEYC_END);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_PageDown, CELL_KEYC_PAGE_DOWN);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Right, CELL_KEYC_RIGHT_ARROW);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Left, CELL_KEYC_LEFT_ARROW);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Down, CELL_KEYC_DOWN_ARROW);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Up, CELL_KEYC_UP_ARROW);
|
||||
//m_keyboards[0].m_buttons.emplace_back(WXK_NUMLOCK, CELL_KEYC_NUM_LOCK);
|
||||
//m_keyboards[0].m_buttons.emplace_back(, CELL_KEYC_APPLICATION);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Kana_Shift, CELL_KEYC_KANA); // maybe Key_Kana_Lock
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Henkan, CELL_KEYC_HENKAN);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Muhenkan, CELL_KEYC_MUHENKAN);
|
||||
|
||||
// CELL_KB_KEYPAD
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_NumLock, CELL_KEYC_KPAD_NUMLOCK);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_division, CELL_KEYC_KPAD_SLASH); // should ideally be slash but that's occupied obviously
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_multiply, CELL_KEYC_KPAD_ASTERISK); // should ideally be asterisk but that's occupied obviously
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_Minus, CELL_KEYC_KPAD_MINUS); // should ideally be minus but that's occupied obviously
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Plus, CELL_KEYC_KPAD_PLUS);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Enter, CELL_KEYC_KPAD_ENTER);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_1, CELL_KEYC_KPAD_1);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_2, CELL_KEYC_KPAD_2);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_3, CELL_KEYC_KPAD_3);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_4, CELL_KEYC_KPAD_4);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_5, CELL_KEYC_KPAD_5);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_6, CELL_KEYC_KPAD_6);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_7, CELL_KEYC_KPAD_7);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_8, CELL_KEYC_KPAD_8);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_9, CELL_KEYC_KPAD_9);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_0, CELL_KEYC_KPAD_0);
|
||||
//m_keyboards[0].m_buttons.emplace_back(Qt::Key_Delete, CELL_KEYC_KPAD_PERIOD);
|
||||
|
||||
// ASCII Printable characters
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_A, CELL_KEYC_A);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_B, CELL_KEYC_B);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_C, CELL_KEYC_C);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_D, CELL_KEYC_D);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_E, CELL_KEYC_E);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_F, CELL_KEYC_F);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_G, CELL_KEYC_G);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_H, CELL_KEYC_H);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_I, CELL_KEYC_I);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_J, CELL_KEYC_J);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_K, CELL_KEYC_K);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_L, CELL_KEYC_L);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_M, CELL_KEYC_M);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_N, CELL_KEYC_N);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_O, CELL_KEYC_O);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_P, CELL_KEYC_P);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Q, CELL_KEYC_Q);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_R, CELL_KEYC_R);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_S, CELL_KEYC_S);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_T, CELL_KEYC_T);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_U, CELL_KEYC_U);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_V, CELL_KEYC_V);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_W, CELL_KEYC_W);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_X, CELL_KEYC_X);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Y, CELL_KEYC_Y);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Z, CELL_KEYC_Z);
|
||||
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_1, CELL_KEYC_1);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_2, CELL_KEYC_2);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_3, CELL_KEYC_3);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_4, CELL_KEYC_4);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_5, CELL_KEYC_5);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_6, CELL_KEYC_6);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_7, CELL_KEYC_7);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_8, CELL_KEYC_8);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_9, CELL_KEYC_9);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_0, CELL_KEYC_0);
|
||||
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Return, CELL_KEYC_ENTER);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Backspace, CELL_KEYC_BS);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Tab, CELL_KEYC_TAB);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Space, CELL_KEYC_SPACE);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Minus, CELL_KEYC_MINUS);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Equal, CELL_KEYC_EQUAL_101);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_AsciiCircum, CELL_KEYC_ACCENT_CIRCONFLEX_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_BracketLeft, CELL_KEYC_LEFT_BRACKET_101);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_At, CELL_KEYC_ATMARK_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_BracketRight, CELL_KEYC_RIGHT_BRACKET_101);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_BracketLeft, CELL_KEYC_LEFT_BRACKET_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Backslash, CELL_KEYC_BACKSLASH_101);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_BracketRight, CELL_KEYC_RIGHT_BRACKET_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Semicolon, CELL_KEYC_SEMICOLON);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Apostrophe, CELL_KEYC_QUOTATION_101);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Colon, CELL_KEYC_COLON_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Comma, CELL_KEYC_COMMA);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Period, CELL_KEYC_PERIOD);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Slash, CELL_KEYC_SLASH);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Backslash, CELL_KEYC_BACKSLASH_106);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_yen, CELL_KEYC_YEN_106);
|
||||
|
||||
// Made up helper buttons
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_Less, CELL_KEYC_LESS);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_NumberSign, CELL_KEYC_HASHTAG);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_ssharp, CELL_KEYC_SSHARP);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_QuoteLeft, CELL_KEYC_BACK_QUOTE);
|
||||
m_keyboards[0].m_buttons.emplace_back(Qt::Key_acute, CELL_KEYC_BACK_QUOTE);
|
||||
}
|
||||
25
rpcs3/Input/basic_keyboard_handler.h
Normal file
25
rpcs3/Input/basic_keyboard_handler.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Emu/Io/KeyboardHandler.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QWindow>
|
||||
|
||||
class basic_keyboard_handler final : public QObject, public KeyboardHandlerBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual void Init(const u32 max_connect) override;
|
||||
|
||||
explicit basic_keyboard_handler();
|
||||
|
||||
void SetTargetWindow(QWindow* target);
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override;
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void keyReleaseEvent(QKeyEvent* event);
|
||||
s32 getUnmodifiedKey(QKeyEvent* event);
|
||||
void LoadSettings();
|
||||
private:
|
||||
QWindow* m_target = nullptr;
|
||||
};
|
||||
126
rpcs3/Input/basic_mouse_handler.cpp
Normal file
126
rpcs3/Input/basic_mouse_handler.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include "basic_mouse_handler.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCursor>
|
||||
|
||||
void basic_mouse_handler::Init(const u32 max_connect)
|
||||
{
|
||||
m_mice.emplace_back(Mouse());
|
||||
memset(&m_info, 0, sizeof(MouseInfo));
|
||||
m_info.max_connect = max_connect;
|
||||
m_info.now_connect = std::min(::size32(m_mice), max_connect);
|
||||
m_info.info = 0; // Ownership of mouse data: 0=Application, 1=System
|
||||
for (u32 i = 1; i < max_connect; i++)
|
||||
{
|
||||
m_info.status[i] = CELL_MOUSE_STATUS_DISCONNECTED;
|
||||
m_info.mode[i] = CELL_MOUSE_INFO_TABLET_MOUSE_MODE;
|
||||
m_info.tablet_is_supported[i] = CELL_MOUSE_INFO_TABLET_NOT_SUPPORTED;
|
||||
}
|
||||
m_info.status[0] = CELL_MOUSE_STATUS_CONNECTED; // (TODO: Support for more mice)
|
||||
m_info.vendor_id[0] = 0x1234;
|
||||
m_info.product_id[0] = 0x1234;
|
||||
}
|
||||
|
||||
basic_mouse_handler::basic_mouse_handler() : QObject()
|
||||
{}
|
||||
|
||||
/* Sets the target window for the event handler, and also installs an event filter on the target. */
|
||||
void basic_mouse_handler::SetTargetWindow(QWindow* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
{
|
||||
m_target = target;
|
||||
target->installEventFilter(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this is hit, it probably means that some refactoring occurs because currently a gsframe is created in Load.
|
||||
// We still want events so filter from application instead since target is null.
|
||||
QApplication::instance()->installEventFilter(this);
|
||||
LOG_ERROR(GENERAL, "Trying to set mouse handler to a null target window.");
|
||||
}
|
||||
}
|
||||
|
||||
bool basic_mouse_handler::eventFilter(QObject* target, QEvent* ev)
|
||||
{
|
||||
// !m_target is for future proofing when gsrender isn't automatically initialized on load to ensure events still occur
|
||||
// !m_target->isVisible() is a hack since currently a guiless application will STILL inititialize a gsrender (providing a valid target)
|
||||
if (!m_target || !m_target->isVisible() || target == m_target)
|
||||
{
|
||||
switch (ev->type())
|
||||
{
|
||||
case QEvent::MouseButtonPress:
|
||||
MouseButtonDown(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
MouseButtonUp(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
case QEvent::MouseMove:
|
||||
MouseMove(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
case QEvent::Wheel:
|
||||
MouseScroll(static_cast<QWheelEvent*>(ev));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void basic_mouse_handler::MouseButtonDown(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_1, 1);
|
||||
else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_2, 1);
|
||||
else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_3, 1);
|
||||
}
|
||||
|
||||
void basic_mouse_handler::MouseButtonUp(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_1, 0);
|
||||
else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_2, 0);
|
||||
else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_3, 0);
|
||||
}
|
||||
|
||||
void basic_mouse_handler::MouseScroll(QWheelEvent* event)
|
||||
{
|
||||
MouseHandlerBase::Scroll(event->angleDelta().y());
|
||||
}
|
||||
|
||||
void basic_mouse_handler::MouseMove(QMouseEvent* event)
|
||||
{
|
||||
if (is_time_for_update())
|
||||
{
|
||||
if (m_target && m_target->visibility() == QWindow::Visibility::FullScreen && m_target->isActive())
|
||||
{
|
||||
// get the screen dimensions
|
||||
const QSize screen = m_target->size();
|
||||
|
||||
// get the center of the screen in global coordinates
|
||||
QPoint p_center = m_target->geometry().topLeft() + QPoint(screen.width() / 2, screen.height() / 2);
|
||||
|
||||
// reset the mouse to the center for consistent results since edge movement won't be registered
|
||||
QCursor::setPos(m_target->screen(), p_center);
|
||||
|
||||
// convert the center into screen coordinates
|
||||
p_center = m_target->mapFromGlobal(p_center);
|
||||
|
||||
// current mouse position, starting at the center
|
||||
static QPoint p_real(p_center);
|
||||
|
||||
// get the delta of the mouse position to the screen center
|
||||
const QPoint p_delta = event->pos() - p_center;
|
||||
|
||||
// update the current position without leaving the screen borders
|
||||
p_real.setX(std::clamp(p_real.x() + p_delta.x(), 0, screen.width()));
|
||||
p_real.setY(std::clamp(p_real.y() + p_delta.y(), 0, screen.height()));
|
||||
|
||||
// pass the 'real' position and the current delta to the screen center
|
||||
MouseHandlerBase::Move(p_real.x(), p_real.y(), true, p_delta.x(), p_delta.y());
|
||||
}
|
||||
else
|
||||
{
|
||||
MouseHandlerBase::Move(event->x(), event->y());
|
||||
}
|
||||
}
|
||||
}
|
||||
27
rpcs3/Input/basic_mouse_handler.h
Normal file
27
rpcs3/Input/basic_mouse_handler.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Emu/Io/MouseHandler.h"
|
||||
|
||||
#include <QWindow>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
class basic_mouse_handler final : public QObject, public MouseHandlerBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual void Init(const u32 max_connect) override;
|
||||
|
||||
basic_mouse_handler();
|
||||
|
||||
void SetTargetWindow(QWindow* target);
|
||||
void MouseButtonDown(QMouseEvent* event);
|
||||
void MouseButtonUp(QMouseEvent* event);
|
||||
void MouseScroll(QWheelEvent* event);
|
||||
void MouseMove(QMouseEvent* event);
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override;
|
||||
private:
|
||||
QWindow* m_target = nullptr;
|
||||
};
|
||||
523
rpcs3/Input/ds3_pad_handler.cpp
Normal file
523
rpcs3/Input/ds3_pad_handler.cpp
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
#include "ds3_pad_handler.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
ds3_pad_handler::ds3_pad_handler() : PadHandlerBase(pad_handler::ds3)
|
||||
{
|
||||
button_list =
|
||||
{
|
||||
{ DS3KeyCodes::Triangle, "Triangle" },
|
||||
{ DS3KeyCodes::Circle, "Circle" },
|
||||
{ DS3KeyCodes::Cross, "Cross" },
|
||||
{ DS3KeyCodes::Square, "Square" },
|
||||
{ DS3KeyCodes::Left, "Left" },
|
||||
{ DS3KeyCodes::Right, "Right" },
|
||||
{ DS3KeyCodes::Up, "Up" },
|
||||
{ DS3KeyCodes::Down, "Down" },
|
||||
{ DS3KeyCodes::R1, "R1" },
|
||||
{ DS3KeyCodes::R2, "R2" },
|
||||
{ DS3KeyCodes::R3, "R3" },
|
||||
{ DS3KeyCodes::Start, "Start" },
|
||||
{ DS3KeyCodes::Select, "Select" },
|
||||
{ DS3KeyCodes::PSButton, "PS Button" },
|
||||
{ DS3KeyCodes::L1, "L1" },
|
||||
{ DS3KeyCodes::L2, "L2" },
|
||||
{ DS3KeyCodes::L3, "L3" },
|
||||
{ DS3KeyCodes::LSXNeg, "LS X-" },
|
||||
{ DS3KeyCodes::LSXPos, "LS X+" },
|
||||
{ DS3KeyCodes::LSYPos, "LS Y+" },
|
||||
{ DS3KeyCodes::LSYNeg, "LS Y-" },
|
||||
{ DS3KeyCodes::RSXNeg, "RS X-" },
|
||||
{ DS3KeyCodes::RSXPos, "RS X+" },
|
||||
{ DS3KeyCodes::RSYPos, "RS Y+" },
|
||||
{ DS3KeyCodes::RSYNeg, "RS Y-" }
|
||||
};
|
||||
|
||||
init_configs();
|
||||
|
||||
// set capabilities
|
||||
b_has_config = true;
|
||||
b_has_rumble = true;
|
||||
b_has_deadzones = true;
|
||||
|
||||
m_name_string = "DS3 Pad #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
}
|
||||
|
||||
ds3_pad_handler::~ds3_pad_handler()
|
||||
{
|
||||
for (auto& controller : controllers)
|
||||
{
|
||||
if (controller->handle)
|
||||
{
|
||||
// Disable blinking and vibration
|
||||
controller->large_motor = 0;
|
||||
controller->small_motor = 0;
|
||||
send_output_report(controller);
|
||||
hid_close(controller->handle);
|
||||
}
|
||||
}
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::init_usb()
|
||||
{
|
||||
if (hid_init() != 0)
|
||||
{
|
||||
LOG_FATAL(HLE, "[DS3] Failed to init hidapi for the DS3 pad handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::Init()
|
||||
{
|
||||
if (is_init)
|
||||
return true;
|
||||
|
||||
if (!init_usb())
|
||||
return false;
|
||||
|
||||
bool warn_about_drivers = false;
|
||||
|
||||
// Uses libusb for windows as hidapi will never work with UsbHid driver for the ds3 and it won't work with WinUsb either(windows hid api needs the UsbHid in the driver stack as far as I can tell)
|
||||
// For other os use hidapi and hope for the best!
|
||||
hid_device_info* hid_info = hid_enumerate(DS3_VID, DS3_PID);
|
||||
hid_device_info* head = hid_info;
|
||||
while (hid_info)
|
||||
{
|
||||
hid_device *handle = hid_open_path(hid_info->path);
|
||||
|
||||
#ifdef _WIN32
|
||||
u8 buf[0xFF];
|
||||
buf[0] = 0xF2;
|
||||
if (handle && (hid_get_feature_report(handle, buf, 0xFF) >= 0))
|
||||
#else
|
||||
if(handle)
|
||||
#endif
|
||||
{
|
||||
std::shared_ptr<ds3_device> ds3dev = std::make_shared<ds3_device>();
|
||||
ds3dev->device = hid_info->path;
|
||||
ds3dev->handle = handle;
|
||||
controllers.emplace_back(ds3dev);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handle)
|
||||
hid_close(handle);
|
||||
|
||||
warn_about_drivers = true;
|
||||
}
|
||||
hid_info = hid_info->next;
|
||||
}
|
||||
|
||||
hid_free_enumeration(head);
|
||||
|
||||
if (warn_about_drivers)
|
||||
{
|
||||
LOG_ERROR(HLE, "[DS3] One or more DS3 pads were detected but couldn't be interacted with directly");
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
LOG_ERROR(HLE, "[DS3] Check https://wiki.rpcs3.net/index.php?title=Help:Controller_Configuration for intructions on how to solve this issue");
|
||||
#endif
|
||||
}
|
||||
else if (controllers.empty())
|
||||
{
|
||||
LOG_WARNING(HLE, "[DS3] No controllers found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_SUCCESS(HLE, "[DS3] Controllers found: %d", controllers.size());
|
||||
}
|
||||
|
||||
is_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> ds3_pad_handler::ListDevices()
|
||||
{
|
||||
std::vector<std::string> ds3_pads_list;
|
||||
|
||||
if (!Init())
|
||||
return ds3_pads_list;
|
||||
|
||||
for (size_t i = 1; i <= controllers.size(); ++i) // Controllers 1-n in GUI
|
||||
{
|
||||
ds3_pads_list.emplace_back(m_name_string + std::to_string(i));
|
||||
}
|
||||
|
||||
return ds3_pads_list;
|
||||
}
|
||||
|
||||
void ds3_pad_handler::SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32/* r*/, s32/* g*/, s32 /* b*/)
|
||||
{
|
||||
std::shared_ptr<ds3_device> device = get_ds3_device(padId);
|
||||
if (device == nullptr || device->handle == nullptr)
|
||||
return;
|
||||
|
||||
// Set the device's motor speeds to our requested values 0-255
|
||||
device->large_motor = largeMotor;
|
||||
device->small_motor = smallMotor;
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < MAX_GAMEPADS; i++)
|
||||
{
|
||||
if (g_cfg_input.player[i]->handler == pad_handler::ds3)
|
||||
{
|
||||
if (g_cfg_input.player[i]->device.to_string() == padId)
|
||||
{
|
||||
m_pad_configs[index].load();
|
||||
device->config = &m_pad_configs[index];
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Start/Stop the engines :)
|
||||
send_output_report(device);
|
||||
}
|
||||
|
||||
void ds3_pad_handler::send_output_report(const std::shared_ptr<ds3_device>& ds3dev)
|
||||
{
|
||||
if (!ds3dev)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
u8 report_buf[] = {
|
||||
0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00
|
||||
};
|
||||
|
||||
report_buf[6] = ds3dev->small_motor;
|
||||
report_buf[8] = ds3dev->large_motor;
|
||||
#else
|
||||
u8 report_buf[] = {
|
||||
0x01,
|
||||
0x00, 0xff, 0x00, 0xff, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0x27, 0x10, 0x00, 0x32,
|
||||
0xff, 0x27, 0x10, 0x00, 0x32,
|
||||
0xff, 0x27, 0x10, 0x00, 0x32,
|
||||
0xff, 0x27, 0x10, 0x00, 0x32,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
report_buf[3] = ds3dev->large_motor;
|
||||
report_buf[5] = ds3dev->small_motor;
|
||||
#endif
|
||||
|
||||
hid_write(ds3dev->handle, report_buf, sizeof(report_buf));
|
||||
}
|
||||
|
||||
std::shared_ptr<ds3_pad_handler::ds3_device> ds3_pad_handler::get_ds3_device(const std::string& padId)
|
||||
{
|
||||
if (!Init())
|
||||
return nullptr;
|
||||
|
||||
size_t pos = padId.find(m_name_string);
|
||||
if (pos == std::string::npos)
|
||||
return nullptr;
|
||||
|
||||
int pad_number = std::stoi(padId.substr(pos + 9));
|
||||
if (pad_number > 0 && pad_number <= controllers.size())
|
||||
return controllers[static_cast<size_t>(pad_number) - 1];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ds3_pad_handler::init_config(pad_config* cfg, const std::string& name)
|
||||
{
|
||||
// Set this profile's save location
|
||||
cfg->cfg_name = name;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = button_list.at(DS3KeyCodes::LSXNeg);
|
||||
cfg->ls_down.def = button_list.at(DS3KeyCodes::LSYNeg);
|
||||
cfg->ls_right.def = button_list.at(DS3KeyCodes::LSXPos);
|
||||
cfg->ls_up.def = button_list.at(DS3KeyCodes::LSYPos);
|
||||
cfg->rs_left.def = button_list.at(DS3KeyCodes::RSXNeg);
|
||||
cfg->rs_down.def = button_list.at(DS3KeyCodes::RSYNeg);
|
||||
cfg->rs_right.def = button_list.at(DS3KeyCodes::RSXPos);
|
||||
cfg->rs_up.def = button_list.at(DS3KeyCodes::RSYPos);
|
||||
cfg->start.def = button_list.at(DS3KeyCodes::Start);
|
||||
cfg->select.def = button_list.at(DS3KeyCodes::Select);
|
||||
cfg->ps.def = button_list.at(DS3KeyCodes::PSButton);
|
||||
cfg->square.def = button_list.at(DS3KeyCodes::Square);
|
||||
cfg->cross.def = button_list.at(DS3KeyCodes::Cross);
|
||||
cfg->circle.def = button_list.at(DS3KeyCodes::Circle);
|
||||
cfg->triangle.def = button_list.at(DS3KeyCodes::Triangle);
|
||||
cfg->left.def = button_list.at(DS3KeyCodes::Left);
|
||||
cfg->down.def = button_list.at(DS3KeyCodes::Down);
|
||||
cfg->right.def = button_list.at(DS3KeyCodes::Right);
|
||||
cfg->up.def = button_list.at(DS3KeyCodes::Up);
|
||||
cfg->r1.def = button_list.at(DS3KeyCodes::R1);
|
||||
cfg->r2.def = button_list.at(DS3KeyCodes::R2);
|
||||
cfg->r3.def = button_list.at(DS3KeyCodes::R3);
|
||||
cfg->l1.def = button_list.at(DS3KeyCodes::L1);
|
||||
cfg->l2.def = button_list.at(DS3KeyCodes::L2);
|
||||
cfg->l3.def = button_list.at(DS3KeyCodes::L3);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->rstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->padsquircling.def = 0;
|
||||
|
||||
// Set color value
|
||||
cfg->colorR.def = 0;
|
||||
cfg->colorG.def = 0;
|
||||
cfg->colorB.def = 0;
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
ds3_pad_handler::DS3Status ds3_pad_handler::get_data(const std::shared_ptr<ds3_device>& ds3dev)
|
||||
{
|
||||
if (!ds3dev)
|
||||
return DS3Status::Disconnected;
|
||||
|
||||
auto& dbuf = ds3dev->buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
dbuf[0] = 0xF2;
|
||||
int result = hid_get_feature_report(ds3dev->handle, dbuf, sizeof(dbuf));
|
||||
#else
|
||||
int result = hid_read(ds3dev->handle, dbuf, sizeof(dbuf));
|
||||
#endif
|
||||
|
||||
if (result > 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if(dbuf[0] == 0xF2)
|
||||
#else
|
||||
if (dbuf[0] == 0x01 && dbuf[1] != 0xFF)
|
||||
#endif
|
||||
{
|
||||
return DS3Status::NewData;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING(HLE, "[DS3] Unknown packet received:0x%02x", dbuf[0]);
|
||||
return DS3Status::Connected;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(result == 0)
|
||||
return DS3Status::Connected;
|
||||
}
|
||||
|
||||
return DS3Status::Disconnected;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> ds3_pad_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
std::unordered_map<u64, u16> key_buf;
|
||||
auto dev = std::static_pointer_cast<ds3_device>(device);
|
||||
if (!dev)
|
||||
return key_buf;
|
||||
|
||||
auto& dbuf = dev->buf;
|
||||
|
||||
const u8 lsx = dbuf[6 + DS3_HID_OFFSET];
|
||||
const u8 lsy = dbuf[7 + DS3_HID_OFFSET];
|
||||
const u8 rsx = dbuf[8 + DS3_HID_OFFSET];
|
||||
const u8 rsy = dbuf[9 + DS3_HID_OFFSET];
|
||||
|
||||
// Left Stick X Axis
|
||||
key_buf[DS3KeyCodes::LSXNeg] = Clamp0To255((127.5f - lsx) * 2.0f);
|
||||
key_buf[DS3KeyCodes::LSXPos] = Clamp0To255((lsx - 127.5f) * 2.0f);
|
||||
|
||||
// Left Stick Y Axis (Up is the negative for some reason)
|
||||
key_buf[DS3KeyCodes::LSYNeg] = Clamp0To255((lsy - 127.5f) * 2.0f);
|
||||
key_buf[DS3KeyCodes::LSYPos] = Clamp0To255((127.5f - lsy) * 2.0f);
|
||||
|
||||
// Right Stick X Axis
|
||||
key_buf[DS3KeyCodes::RSXNeg] = Clamp0To255((127.5f - rsx) * 2.0f);
|
||||
key_buf[DS3KeyCodes::RSXPos] = Clamp0To255((rsx - 127.5f) * 2.0f);
|
||||
|
||||
// Right Stick Y Axis (Up is the negative for some reason)
|
||||
key_buf[DS3KeyCodes::RSYNeg] = Clamp0To255((rsy - 127.5f) * 2.0f);
|
||||
key_buf[DS3KeyCodes::RSYPos] = Clamp0To255((127.5f - rsy) * 2.0f);
|
||||
|
||||
// Buttons or triggers with pressure sensitivity
|
||||
key_buf[DS3KeyCodes::Up] = (dbuf[2 + DS3_HID_OFFSET] & 0x10) ? dbuf[14 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Right] = (dbuf[2 + DS3_HID_OFFSET] & 0x20) ? dbuf[15 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Down] = (dbuf[2 + DS3_HID_OFFSET] & 0x40) ? dbuf[16 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Left] = (dbuf[2 + DS3_HID_OFFSET] & 0x80) ? dbuf[17 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::L2] = (dbuf[3 + DS3_HID_OFFSET] & 0x01) ? dbuf[18 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::R2] = (dbuf[3 + DS3_HID_OFFSET] & 0x02) ? dbuf[19 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::L1] = (dbuf[3 + DS3_HID_OFFSET] & 0x04) ? dbuf[20 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::R1] = (dbuf[3 + DS3_HID_OFFSET] & 0x08) ? dbuf[21 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Triangle] = (dbuf[3 + DS3_HID_OFFSET] & 0x10) ? dbuf[22 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Circle] = (dbuf[3 + DS3_HID_OFFSET] & 0x20) ? dbuf[23 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Cross] = (dbuf[3 + DS3_HID_OFFSET] & 0x40) ? dbuf[24 + DS3_HID_OFFSET] : 0;
|
||||
key_buf[DS3KeyCodes::Square] = (dbuf[3 + DS3_HID_OFFSET] & 0x80) ? dbuf[25 + DS3_HID_OFFSET] : 0;
|
||||
|
||||
// Buttons without pressure sensitivity
|
||||
key_buf[DS3KeyCodes::Select] = (dbuf[2 + DS3_HID_OFFSET] & 0x01) ? 255 : 0;
|
||||
key_buf[DS3KeyCodes::L3] = (dbuf[2 + DS3_HID_OFFSET] & 0x02) ? 255 : 0;
|
||||
key_buf[DS3KeyCodes::R3] = (dbuf[2 + DS3_HID_OFFSET] & 0x04) ? 255 : 0;
|
||||
key_buf[DS3KeyCodes::Start] = (dbuf[2 + DS3_HID_OFFSET] & 0x08) ? 255 : 0;
|
||||
key_buf[DS3KeyCodes::PSButton] = (dbuf[4 + DS3_HID_OFFSET] & 0x01) ? 255 : 0;
|
||||
|
||||
return key_buf;
|
||||
}
|
||||
|
||||
std::array<int, 6> ds3_pad_handler::get_preview_values(std::unordered_map<u64, u16> data)
|
||||
{
|
||||
return { data[L2], data[R2], data[LSXPos] - data[LSXNeg], data[LSYPos] - data[LSYNeg], data[RSXPos] - data[RSXNeg], data[RSYPos] - data[RSYNeg] };
|
||||
}
|
||||
|
||||
void ds3_pad_handler::get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto ds3dev = std::static_pointer_cast<ds3_device>(device);
|
||||
if (!ds3dev || !pad)
|
||||
return;
|
||||
|
||||
// For unknown reasons the sixaxis values seem to be in little endian on linux
|
||||
|
||||
#ifdef _WIN32
|
||||
// Official Sony Windows DS3 driver seems to do the same modification of this value as the ps3
|
||||
pad->m_sensors[0].m_value = *reinterpret_cast<le_t<u16, 1>*>(&ds3dev->buf[41 + DS3_HID_OFFSET]);
|
||||
#else
|
||||
// When getting raw values from the device this adjustement is needed
|
||||
pad->m_sensors[0].m_value = 512 - (*reinterpret_cast<le_t<u16, 1>*>(&ds3dev->buf[41]) - 512);
|
||||
#endif
|
||||
pad->m_sensors[1].m_value = *reinterpret_cast<le_t<u16, 1>*>(&ds3dev->buf[45 + DS3_HID_OFFSET]);
|
||||
pad->m_sensors[2].m_value = *reinterpret_cast<le_t<u16, 1>*>(&ds3dev->buf[43 + DS3_HID_OFFSET]);
|
||||
pad->m_sensors[3].m_value = *reinterpret_cast<le_t<u16, 1>*>(&ds3dev->buf[47 + DS3_HID_OFFSET]);
|
||||
|
||||
// Those are formulas used to adjust sensor values in sys_hid code but I couldn't find all the vars.
|
||||
//auto polish_value = [](s32 value, s32 dword_0x0, s32 dword_0x4, s32 dword_0x8, s32 dword_0xC, s32 dword_0x18, s32 dword_0x1C) -> u16
|
||||
//{
|
||||
// value -= dword_0xC;
|
||||
// value *= dword_0x4;
|
||||
// value <<= 10;
|
||||
// value /= dword_0x0;
|
||||
// value >>= 10;
|
||||
// value += dword_0x8;
|
||||
// if (value < dword_0x18) return dword_0x18;
|
||||
// if (value > dword_0x1C) return dword_0x1C;
|
||||
// return static_cast<u16>(value);
|
||||
//};
|
||||
|
||||
// dword_0x0 and dword_0xC are unknown
|
||||
//pad->m_sensors[0].m_value = polish_value(pad->m_sensors[0].m_value, 226, -226, 512, 512, 0, 1023);
|
||||
//pad->m_sensors[1].m_value = polish_value(pad->m_sensors[1].m_value, 226, 226, 512, 512, 0, 1023);
|
||||
//pad->m_sensors[2].m_value = polish_value(pad->m_sensors[2].m_value, 113, 113, 512, 512, 0, 1023);
|
||||
//pad->m_sensors[3].m_value = polish_value(pad->m_sensors[3].m_value, 1, 1, 512, 512, 0, 1023);
|
||||
}
|
||||
|
||||
std::shared_ptr<PadDevice> ds3_pad_handler::get_device(const std::string& device)
|
||||
{
|
||||
std::shared_ptr<ds3_device> ds3device = get_ds3_device(device);
|
||||
if (ds3device == nullptr || ds3device->handle == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return ds3device;
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::get_is_left_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == DS3KeyCodes::L2;
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::get_is_right_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == DS3KeyCodes::R2;
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::get_is_left_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case DS3KeyCodes::LSXNeg:
|
||||
case DS3KeyCodes::LSXPos:
|
||||
case DS3KeyCodes::LSYPos:
|
||||
case DS3KeyCodes::LSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::get_is_right_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case DS3KeyCodes::RSXNeg:
|
||||
case DS3KeyCodes::RSXPos:
|
||||
case DS3KeyCodes::RSYPos:
|
||||
case DS3KeyCodes::RSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PadHandlerBase::connection ds3_pad_handler::update_connection(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<ds3_device>(device);
|
||||
if (!dev)
|
||||
return connection::disconnected;
|
||||
|
||||
if (dev->handle == nullptr)
|
||||
{
|
||||
hid_device* devhandle = hid_open_path(dev->device.c_str());
|
||||
if (!devhandle)
|
||||
{
|
||||
return connection::disconnected;
|
||||
}
|
||||
|
||||
dev->handle = devhandle;
|
||||
}
|
||||
|
||||
switch (get_data(dev))
|
||||
{
|
||||
case DS3Status::Disconnected:
|
||||
{
|
||||
if (dev->status == DS3Status::Connected)
|
||||
{
|
||||
dev->status = DS3Status::Disconnected;
|
||||
hid_close(dev->handle);
|
||||
dev->handle = nullptr;
|
||||
}
|
||||
return connection::disconnected;
|
||||
}
|
||||
case DS3Status::Connected:
|
||||
{
|
||||
if (dev->status == DS3Status::Disconnected)
|
||||
{
|
||||
dev->status = DS3Status::Connected;
|
||||
}
|
||||
return connection::no_data;
|
||||
}
|
||||
case DS3Status::NewData:
|
||||
{
|
||||
return connection::connected;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return connection::disconnected;
|
||||
}
|
||||
|
||||
void ds3_pad_handler::apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<ds3_device>(device);
|
||||
if (!dev || !pad)
|
||||
return;
|
||||
|
||||
if (dev->large_motor != pad->m_vibrateMotors[0].m_value || dev->small_motor != pad->m_vibrateMotors[1].m_value)
|
||||
{
|
||||
dev->large_motor = static_cast<u8>(pad->m_vibrateMotors[0].m_value);
|
||||
dev->small_motor = static_cast<u8>(pad->m_vibrateMotors[1].m_value);
|
||||
send_output_report(dev);
|
||||
}
|
||||
}
|
||||
128
rpcs3/Input/ds3_pad_handler.h
Normal file
128
rpcs3/Input/ds3_pad_handler.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
class ds3_pad_handler final : public PadHandlerBase
|
||||
{
|
||||
enum DS3KeyCodes
|
||||
{
|
||||
Triangle = 0,
|
||||
Circle,
|
||||
Cross,
|
||||
Square,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
R1,
|
||||
R3,
|
||||
L1,
|
||||
L3,
|
||||
Select,
|
||||
Start,
|
||||
PSButton,
|
||||
|
||||
L2,
|
||||
R2,
|
||||
|
||||
LSXNeg,
|
||||
LSXPos,
|
||||
LSYNeg,
|
||||
LSYPos,
|
||||
RSXNeg,
|
||||
RSXPos,
|
||||
RSYNeg,
|
||||
RSYPos,
|
||||
|
||||
KeyCodeCount
|
||||
};
|
||||
|
||||
enum HidRequest
|
||||
{
|
||||
HID_GETREPORT = 0x01,
|
||||
HID_GETIDLE,
|
||||
HID_GETPROTOCOL,
|
||||
HID_SETREPORT = 0x09,
|
||||
HID_SETIDLE,
|
||||
HID_SETPROTOCOL
|
||||
};
|
||||
|
||||
enum ReportType
|
||||
{
|
||||
HIDREPORT_INPUT = 0x0100,
|
||||
HIDREPORT_OUTPUT = 0x0200,
|
||||
HIDREPORT_FEATURE = 0x0300
|
||||
};
|
||||
|
||||
enum DS3Endpoints
|
||||
{
|
||||
DS3_ENDPOINT_OUT = 0x02,
|
||||
DS3_ENDPOINT_IN = 0x81
|
||||
};
|
||||
|
||||
enum DS3Status : u8
|
||||
{
|
||||
Disconnected,
|
||||
Connected,
|
||||
NewData
|
||||
};
|
||||
|
||||
struct ds3_device : public PadDevice
|
||||
{
|
||||
std::string device = {};
|
||||
hid_device *handle = nullptr;
|
||||
u8 buf[64]{ 0 };
|
||||
u8 large_motor = 0;
|
||||
u8 small_motor = 0;
|
||||
u8 status = DS3Status::Disconnected;
|
||||
};
|
||||
|
||||
const u16 DS3_VID = 0x054C;
|
||||
const u16 DS3_PID = 0x0268;
|
||||
|
||||
#ifdef _WIN32
|
||||
const u8 DS3_HID_OFFSET = 0x01;
|
||||
#else
|
||||
const u8 DS3_HID_OFFSET = 0x00;
|
||||
#endif
|
||||
|
||||
// pseudo 'controller id' to keep track of unique controllers
|
||||
std::vector<std::shared_ptr<ds3_device>> controllers;
|
||||
|
||||
public:
|
||||
ds3_pad_handler();
|
||||
~ds3_pad_handler();
|
||||
|
||||
bool Init() override;
|
||||
|
||||
std::vector<std::string> ListDevices() override;
|
||||
void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b) override;
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<ds3_device> get_ds3_device(const std::string& padId);
|
||||
ds3_pad_handler::DS3Status get_data(const std::shared_ptr<ds3_device>& ds3dev);
|
||||
void send_output_report(const std::shared_ptr<ds3_device>& ds3dev);
|
||||
|
||||
private:
|
||||
bool init_usb();
|
||||
|
||||
private:
|
||||
bool is_init = false;
|
||||
|
||||
std::shared_ptr<PadDevice> get_device(const std::string& device) override;
|
||||
bool get_is_left_trigger(u64 keyCode) override;
|
||||
bool get_is_right_trigger(u64 keyCode) override;
|
||||
bool get_is_left_stick(u64 keyCode) override;
|
||||
bool get_is_right_stick(u64 keyCode) override;
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
|
||||
std::array<int, 6> get_preview_values(std::unordered_map<u64, u16> data) override;
|
||||
};
|
||||
918
rpcs3/Input/ds4_pad_handler.cpp
Normal file
918
rpcs3/Input/ds4_pad_handler.cpp
Normal file
|
|
@ -0,0 +1,918 @@
|
|||
#include "ds4_pad_handler.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
const auto THREAD_SLEEP = 1ms; //ds4 has new data every ~4ms,
|
||||
const auto THREAD_SLEEP_INACTIVE = 100ms;
|
||||
|
||||
const u32 DS4_ACC_RES_PER_G = 8192;
|
||||
const u32 DS4_GYRO_RES_PER_DEG_S = 16; // technically this could be 1024, but keeping it at 16 keeps us within 16 bits of precision
|
||||
const u32 DS4_FEATURE_REPORT_0x02_SIZE = 37;
|
||||
const u32 DS4_FEATURE_REPORT_0x05_SIZE = 41;
|
||||
const u32 DS4_FEATURE_REPORT_0x12_SIZE = 16;
|
||||
const u32 DS4_FEATURE_REPORT_0x81_SIZE = 7;
|
||||
const u32 DS4_INPUT_REPORT_0x11_SIZE = 78;
|
||||
const u32 DS4_OUTPUT_REPORT_0x05_SIZE = 32;
|
||||
const u32 DS4_OUTPUT_REPORT_0x11_SIZE = 78;
|
||||
const u32 DS4_INPUT_REPORT_GYRO_X_OFFSET = 13;
|
||||
const u32 DS4_INPUT_REPORT_BATTERY_OFFSET = 30;
|
||||
|
||||
// This tries to convert axis to give us the max even in the corners,
|
||||
// this actually might work 'too' well, we end up actually getting diagonals of actual max/min, we need the corners still a bit rounded to match ds3
|
||||
// im leaving it here for now, and future reference as it probably can be used later
|
||||
//taken from http://theinstructionlimit.com/squaring-the-thumbsticks
|
||||
/*std::tuple<u16, u16> ConvertToSquarePoint(u16 inX, u16 inY, u32 innerRoundness = 0) {
|
||||
// convert inX and Y to a (-1, 1) vector;
|
||||
const f32 x = (inX - 127) / 127.f;
|
||||
const f32 y = ((inY - 127) / 127.f) * -1;
|
||||
|
||||
f32 outX, outY;
|
||||
const f32 piOver4 = M_PI / 4;
|
||||
const f32 angle = std::atan2(y, x) + M_PI;
|
||||
// x+ wall
|
||||
if (angle <= piOver4 || angle > 7 * piOver4) {
|
||||
outX = x * (f32)(1 / std::cos(angle));
|
||||
outY = y * (f32)(1 / std::cos(angle));
|
||||
}
|
||||
// y+ wall
|
||||
else if (angle > piOver4 && angle <= 3 * piOver4) {
|
||||
outX = x * (f32)(1 / std::sin(angle));
|
||||
outY = y * (f32)(1 / std::sin(angle));
|
||||
}
|
||||
// x- wall
|
||||
else if (angle > 3 * piOver4 && angle <= 5 * piOver4) {
|
||||
outX = x * (f32)(-1 / std::cos(angle));
|
||||
outY = y * (f32)(-1 / std::cos(angle));
|
||||
}
|
||||
// y- wall
|
||||
else if (angle > 5 * piOver4 && angle <= 7 * piOver4) {
|
||||
outX = x * (f32)(-1 / std::sin(angle));
|
||||
outY = y * (f32)(-1 / std::sin(angle));
|
||||
}
|
||||
else fmt::throw_exception("invalid angle in convertToSquarePoint");
|
||||
|
||||
if (innerRoundness == 0)
|
||||
return std::tuple<u16, u16>(Clamp0To255((outX + 1) * 127.f), Clamp0To255(((outY * -1) + 1) * 127.f));
|
||||
|
||||
const f32 len = std::sqrt(std::pow(x, 2) + std::pow(y, 2));
|
||||
const f32 factor = std::pow(len, innerRoundness);
|
||||
|
||||
outX = (1 - factor) * x + factor * outX;
|
||||
outY = (1 - factor) * y + factor * outY;
|
||||
|
||||
return std::tuple<u16, u16>(Clamp0To255((outX + 1) * 127.f), Clamp0To255(((outY * -1) + 1) * 127.f));
|
||||
}*/
|
||||
|
||||
inline s16 read_s16(const void* buf)
|
||||
{
|
||||
return *reinterpret_cast<const s16*>(buf);
|
||||
}
|
||||
|
||||
inline u32 read_u32(const void* buf)
|
||||
{
|
||||
return *reinterpret_cast<const u32*>(buf);
|
||||
}
|
||||
}
|
||||
|
||||
ds4_pad_handler::ds4_pad_handler() : PadHandlerBase(pad_handler::ds4)
|
||||
{
|
||||
// Unique names for the config files and our pad settings dialog
|
||||
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-" }
|
||||
};
|
||||
|
||||
init_configs();
|
||||
|
||||
// Define border values
|
||||
thumb_min = 0;
|
||||
thumb_max = 255;
|
||||
trigger_min = 0;
|
||||
trigger_max = 255;
|
||||
vibration_min = 0;
|
||||
vibration_max = 255;
|
||||
|
||||
// set capabilities
|
||||
b_has_config = true;
|
||||
b_has_rumble = true;
|
||||
b_has_deadzones = true;
|
||||
b_has_led = true;
|
||||
|
||||
m_name_string = "DS4 Pad #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
}
|
||||
|
||||
void ds4_pad_handler::init_config(pad_config* cfg, const std::string& name)
|
||||
{
|
||||
// Set this profile's save location
|
||||
cfg->cfg_name = name;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = button_list.at(DS4KeyCodes::LSXNeg);
|
||||
cfg->ls_down.def = button_list.at(DS4KeyCodes::LSYNeg);
|
||||
cfg->ls_right.def = button_list.at(DS4KeyCodes::LSXPos);
|
||||
cfg->ls_up.def = button_list.at(DS4KeyCodes::LSYPos);
|
||||
cfg->rs_left.def = button_list.at(DS4KeyCodes::RSXNeg);
|
||||
cfg->rs_down.def = button_list.at(DS4KeyCodes::RSYNeg);
|
||||
cfg->rs_right.def = button_list.at(DS4KeyCodes::RSXPos);
|
||||
cfg->rs_up.def = button_list.at(DS4KeyCodes::RSYPos);
|
||||
cfg->start.def = button_list.at(DS4KeyCodes::Options);
|
||||
cfg->select.def = button_list.at(DS4KeyCodes::Share);
|
||||
cfg->ps.def = button_list.at(DS4KeyCodes::PSButton);
|
||||
cfg->square.def = button_list.at(DS4KeyCodes::Square);
|
||||
cfg->cross.def = button_list.at(DS4KeyCodes::Cross);
|
||||
cfg->circle.def = button_list.at(DS4KeyCodes::Circle);
|
||||
cfg->triangle.def = button_list.at(DS4KeyCodes::Triangle);
|
||||
cfg->left.def = button_list.at(DS4KeyCodes::Left);
|
||||
cfg->down.def = button_list.at(DS4KeyCodes::Down);
|
||||
cfg->right.def = button_list.at(DS4KeyCodes::Right);
|
||||
cfg->up.def = button_list.at(DS4KeyCodes::Up);
|
||||
cfg->r1.def = button_list.at(DS4KeyCodes::R1);
|
||||
cfg->r2.def = button_list.at(DS4KeyCodes::R2);
|
||||
cfg->r3.def = button_list.at(DS4KeyCodes::R3);
|
||||
cfg->l1.def = button_list.at(DS4KeyCodes::L1);
|
||||
cfg->l2.def = button_list.at(DS4KeyCodes::L2);
|
||||
cfg->l3.def = button_list.at(DS4KeyCodes::L3);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->rstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->padsquircling.def = 8000;
|
||||
|
||||
// Set color value
|
||||
cfg->colorR.def = 0;
|
||||
cfg->colorG.def = 0;
|
||||
cfg->colorB.def = 20;
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
void ds4_pad_handler::SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b)
|
||||
{
|
||||
std::shared_ptr<DS4Device> device = GetDS4Device(padId);
|
||||
if (device == nullptr || device->hidDevice == nullptr)
|
||||
return;
|
||||
|
||||
// Set the device's motor speeds to our requested values 0-255
|
||||
device->largeVibrate = largeMotor;
|
||||
device->smallVibrate = smallMotor;
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < MAX_GAMEPADS; i++)
|
||||
{
|
||||
if (g_cfg_input.player[i]->handler == pad_handler::ds4)
|
||||
{
|
||||
if (g_cfg_input.player[i]->device.to_string() == padId)
|
||||
{
|
||||
m_pad_configs[index].load();
|
||||
device->config = &m_pad_configs[index];
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Set new LED color
|
||||
if (r >= 0 && g >= 0 && b >= 0 && r <= 255 && g <= 255 && b <= 255)
|
||||
{
|
||||
device->config->colorR.set(r);
|
||||
device->config->colorG.set(g);
|
||||
device->config->colorB.set(b);
|
||||
}
|
||||
|
||||
// Start/Stop the engines :)
|
||||
SendVibrateData(device);
|
||||
}
|
||||
|
||||
std::shared_ptr<ds4_pad_handler::DS4Device> ds4_pad_handler::GetDS4Device(const std::string& padId, bool try_reconnect)
|
||||
{
|
||||
if (!Init())
|
||||
return nullptr;
|
||||
|
||||
size_t pos = padId.find(m_name_string);
|
||||
if (pos == std::string::npos)
|
||||
return nullptr;
|
||||
|
||||
std::string pad_serial = padId.substr(pos + 9);
|
||||
std::shared_ptr<DS4Device> device = nullptr;
|
||||
|
||||
int i = 0; // Controllers 1-n in GUI
|
||||
for (auto& cur_control : controllers)
|
||||
{
|
||||
if (pad_serial == std::to_string(++i) || pad_serial == cur_control.first)
|
||||
{
|
||||
device = cur_control.second;
|
||||
|
||||
if (try_reconnect && device && !device->hidDevice)
|
||||
{
|
||||
device->hidDevice = hid_open_path(device->path.c_str());
|
||||
if (device->hidDevice)
|
||||
{
|
||||
hid_set_nonblocking(device->hidDevice, 1);
|
||||
LOG_NOTICE(HLE, "DS4 device %d reconnected", i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> ds4_pad_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
std::unordered_map<u64, u16> keyBuffer;
|
||||
auto ds4_dev = std::static_pointer_cast<DS4Device>(device);
|
||||
if (!ds4_dev)
|
||||
return keyBuffer;
|
||||
|
||||
auto buf = ds4_dev->padData;
|
||||
|
||||
// Left Stick X Axis
|
||||
keyBuffer[DS4KeyCodes::LSXNeg] = Clamp0To255((127.5f - buf[1]) * 2.0f);
|
||||
keyBuffer[DS4KeyCodes::LSXPos] = Clamp0To255((buf[1] - 127.5f) * 2.0f);
|
||||
|
||||
// Left Stick Y Axis (Up is the negative for some reason)
|
||||
keyBuffer[DS4KeyCodes::LSYNeg] = Clamp0To255((buf[2] - 127.5f) * 2.0f);
|
||||
keyBuffer[DS4KeyCodes::LSYPos] = Clamp0To255((127.5f - buf[2]) * 2.0f);
|
||||
|
||||
// Right Stick X Axis
|
||||
keyBuffer[DS4KeyCodes::RSXNeg] = Clamp0To255((127.5f - buf[3]) * 2.0f);
|
||||
keyBuffer[DS4KeyCodes::RSXPos] = Clamp0To255((buf[3] - 127.5f) * 2.0f);
|
||||
|
||||
// Right Stick Y Axis (Up is the negative for some reason)
|
||||
keyBuffer[DS4KeyCodes::RSYNeg] = Clamp0To255((buf[4] - 127.5f) * 2.0f);
|
||||
keyBuffer[DS4KeyCodes::RSYPos] = Clamp0To255((127.5f - buf[4]) * 2.0f);
|
||||
|
||||
// bleh, dpad in buffer is stored in a different state
|
||||
u8 dpadState = buf[5] & 0xf;
|
||||
switch (dpadState)
|
||||
{
|
||||
case 0x08: // none pressed
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
case 0x07: // NW...left and up
|
||||
keyBuffer[DS4KeyCodes::Up] = 255;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 255;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
case 0x06: // W..left
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 255;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
case 0x05: // SW..left down
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 255;
|
||||
keyBuffer[DS4KeyCodes::Left] = 255;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
case 0x04: // S..down
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 255;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
case 0x03: // SE..down and right
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 255;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 255;
|
||||
break;
|
||||
case 0x02: // E... right
|
||||
keyBuffer[DS4KeyCodes::Up] = 0;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 255;
|
||||
break;
|
||||
case 0x01: // NE.. up right
|
||||
keyBuffer[DS4KeyCodes::Up] = 255;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 255;
|
||||
break;
|
||||
case 0x00: // n.. up
|
||||
keyBuffer[DS4KeyCodes::Up] = 255;
|
||||
keyBuffer[DS4KeyCodes::Down] = 0;
|
||||
keyBuffer[DS4KeyCodes::Left] = 0;
|
||||
keyBuffer[DS4KeyCodes::Right] = 0;
|
||||
break;
|
||||
default:
|
||||
fmt::throw_exception("ds4 dpad state encountered unexpected input");
|
||||
}
|
||||
|
||||
// square, cross, circle, triangle
|
||||
keyBuffer[DS4KeyCodes::Square] = ((buf[5] & (1 << 4)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::Cross] = ((buf[5] & (1 << 5)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::Circle] = ((buf[5] & (1 << 6)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::Triangle] = ((buf[5] & (1 << 7)) != 0) ? 255 : 0;
|
||||
|
||||
// L1, R1, L2, L3, select, start, L3, L3
|
||||
keyBuffer[DS4KeyCodes::L1] = ((buf[6] & (1 << 0)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::R1] = ((buf[6] & (1 << 1)) != 0) ? 255 : 0;
|
||||
//keyBuffer[DS4KeyCodes::L2But] = ((buf[6] & (1 << 2)) != 0) ? 255 : 0;
|
||||
//keyBuffer[DS4KeyCodes::R2But] = ((buf[6] & (1 << 3)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::Share] = ((buf[6] & (1 << 4)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::Options] = ((buf[6] & (1 << 5)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::L3] = ((buf[6] & (1 << 6)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::R3] = ((buf[6] & (1 << 7)) != 0) ? 255 : 0;
|
||||
|
||||
// PS Button, Touch Button
|
||||
keyBuffer[DS4KeyCodes::PSButton] = ((buf[7] & (1 << 0)) != 0) ? 255 : 0;
|
||||
keyBuffer[DS4KeyCodes::TouchPad] = ((buf[7] & (1 << 1)) != 0) ? 255 : 0;
|
||||
|
||||
// L2, R2
|
||||
keyBuffer[DS4KeyCodes::L2] = buf[8];
|
||||
keyBuffer[DS4KeyCodes::R2] = buf[9];
|
||||
|
||||
return keyBuffer;
|
||||
}
|
||||
|
||||
std::array<int, 6> ds4_pad_handler::get_preview_values(std::unordered_map<u64, u16> data)
|
||||
{
|
||||
return { data[L2], data[R2], data[LSXPos] - data[LSXNeg], data[LSYPos] - data[LSYNeg], data[RSXPos] - data[RSXNeg], data[RSYPos] - data[RSYNeg] };
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::GetCalibrationData(const std::shared_ptr<DS4Device>& ds4Dev)
|
||||
{
|
||||
std::array<u8, 64> buf;
|
||||
if (ds4Dev->btCon)
|
||||
{
|
||||
for (int tries = 0; tries < 3; ++tries)
|
||||
{
|
||||
buf[0] = 0x05;
|
||||
if (hid_get_feature_report(ds4Dev->hidDevice, buf.data(), DS4_FEATURE_REPORT_0x05_SIZE) <= 0)
|
||||
return false;
|
||||
|
||||
const u8 btHdr = 0xA3;
|
||||
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
|
||||
const u32 crcCalc = CRCPP::CRC::Calculate(buf.data(), (DS4_FEATURE_REPORT_0x05_SIZE - 4), crcTable, crcHdr);
|
||||
const u32 crcReported = read_u32(&buf[DS4_FEATURE_REPORT_0x05_SIZE - 4]);
|
||||
if (crcCalc != crcReported)
|
||||
LOG_WARNING(HLE, "[DS4] Calibration CRC check failed! Will retry up to 3 times. Received 0x%x, Expected 0x%x", crcReported, crcCalc);
|
||||
else break;
|
||||
if (tries == 2)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[0] = 0x02;
|
||||
if (hid_get_feature_report(ds4Dev->hidDevice, buf.data(), DS4_FEATURE_REPORT_0x02_SIZE) <= 0)
|
||||
{
|
||||
LOG_ERROR(HLE, "[DS4] Failed getting calibration data report!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ds4Dev->calibData[DS4CalibIndex::PITCH].bias = read_s16(&buf[1]);
|
||||
ds4Dev->calibData[DS4CalibIndex::YAW].bias = read_s16(&buf[3]);
|
||||
ds4Dev->calibData[DS4CalibIndex::ROLL].bias = read_s16(&buf[5]);
|
||||
|
||||
s16 pitchPlus, pitchNeg, rollPlus, rollNeg, yawPlus, yawNeg;
|
||||
|
||||
// Check for calibration data format
|
||||
// It's going to be either alternating +/- or +++---
|
||||
if (read_s16(&buf[9]) < 0 && read_s16(&buf[7]) > 0)
|
||||
{
|
||||
// Wired mode for OEM controllers
|
||||
pitchPlus = read_s16(&buf[7]);
|
||||
pitchNeg = read_s16(&buf[9]);
|
||||
yawPlus = read_s16(&buf[11]);
|
||||
yawNeg = read_s16(&buf[13]);
|
||||
rollPlus = read_s16(&buf[15]);
|
||||
rollNeg = read_s16(&buf[17]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bluetooth mode and wired mode for some 3rd party controllers
|
||||
pitchPlus = read_s16(&buf[7]);
|
||||
yawPlus = read_s16(&buf[9]);
|
||||
rollPlus = read_s16(&buf[11]);
|
||||
pitchNeg = read_s16(&buf[13]);
|
||||
yawNeg = read_s16(&buf[15]);
|
||||
rollNeg = read_s16(&buf[17]);
|
||||
}
|
||||
|
||||
// Confirm correctness. Need confirmation with dongle with no active controller
|
||||
if (pitchPlus <= 0 || yawPlus <= 0 || rollPlus <= 0 ||
|
||||
pitchNeg >= 0 || yawNeg >= 0 || rollNeg >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const s32 gyroSpeedScale = read_s16(&buf[19]) + read_s16(&buf[21]);
|
||||
|
||||
ds4Dev->calibData[DS4CalibIndex::PITCH].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
|
||||
ds4Dev->calibData[DS4CalibIndex::PITCH].sensDenom = pitchPlus - pitchNeg;
|
||||
|
||||
ds4Dev->calibData[DS4CalibIndex::YAW].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
|
||||
ds4Dev->calibData[DS4CalibIndex::YAW].sensDenom = yawPlus - yawNeg;
|
||||
|
||||
ds4Dev->calibData[DS4CalibIndex::ROLL].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
|
||||
ds4Dev->calibData[DS4CalibIndex::ROLL].sensDenom = rollPlus - rollNeg;
|
||||
|
||||
const s16 accelXPlus = read_s16(&buf[23]);
|
||||
const s16 accelXNeg = read_s16(&buf[25]);
|
||||
const s16 accelYPlus = read_s16(&buf[27]);
|
||||
const s16 accelYNeg = read_s16(&buf[29]);
|
||||
const s16 accelZPlus = read_s16(&buf[31]);
|
||||
const s16 accelZNeg = read_s16(&buf[33]);
|
||||
|
||||
const s32 accelXRange = accelXPlus - accelXNeg;
|
||||
ds4Dev->calibData[DS4CalibIndex::X].bias = accelXPlus - accelXRange / 2;
|
||||
ds4Dev->calibData[DS4CalibIndex::X].sensNumer = 2 * DS4_ACC_RES_PER_G;
|
||||
ds4Dev->calibData[DS4CalibIndex::X].sensDenom = accelXRange;
|
||||
|
||||
const s32 accelYRange = accelYPlus - accelYNeg;
|
||||
ds4Dev->calibData[DS4CalibIndex::Y].bias = accelYPlus - accelYRange / 2;
|
||||
ds4Dev->calibData[DS4CalibIndex::Y].sensNumer = 2 * DS4_ACC_RES_PER_G;
|
||||
ds4Dev->calibData[DS4CalibIndex::Y].sensDenom = accelYRange;
|
||||
|
||||
const s32 accelZRange = accelZPlus - accelZNeg;
|
||||
ds4Dev->calibData[DS4CalibIndex::Z].bias = accelZPlus - accelZRange / 2;
|
||||
ds4Dev->calibData[DS4CalibIndex::Z].sensNumer = 2 * DS4_ACC_RES_PER_G;
|
||||
ds4Dev->calibData[DS4CalibIndex::Z].sensDenom = accelZRange;
|
||||
|
||||
// Make sure data 'looks' valid, dongle will report invalid calibration data with no controller connected
|
||||
|
||||
for (const auto& data : ds4Dev->calibData)
|
||||
{
|
||||
if (data.sensDenom == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo)
|
||||
{
|
||||
std::string serial = "";
|
||||
std::shared_ptr<DS4Device> ds4Dev = std::make_shared<DS4Device>();
|
||||
ds4Dev->hidDevice = hidDevice;
|
||||
// There isnt a nice 'portable' way with hidapi to detect bt vs wired as the pid/vid's are the same
|
||||
// Let's try getting 0x81 feature report, which should will return mac address on wired, and should error on bluetooth
|
||||
std::array<u8, 64> buf{};
|
||||
buf[0] = 0x81;
|
||||
if (const auto length = hid_get_feature_report(hidDevice, buf.data(), DS4_FEATURE_REPORT_0x81_SIZE); length > 0)
|
||||
{
|
||||
if (length != DS4_FEATURE_REPORT_0x81_SIZE)
|
||||
{
|
||||
// Controller may not be genuine. These controllers do not have feature 0x81 implemented and calibration data is in bluetooth format even in USB mode!
|
||||
LOG_WARNING(HLE, "DS4 controller may not be genuine. Workaround enabled.");
|
||||
|
||||
// Read feature report 0x12 instead which is what the console uses.
|
||||
buf[0] = 0x12;
|
||||
buf[1] = 0;
|
||||
hid_get_feature_report(hidDevice, buf.data(), DS4_FEATURE_REPORT_0x12_SIZE);
|
||||
}
|
||||
|
||||
serial = fmt::format("%x%x%x%x%x%x", buf[6], buf[5], buf[4], buf[3], buf[2], buf[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ds4Dev->btCon = true;
|
||||
std::wstring wSerial(hidDevInfo->serial_number);
|
||||
serial = std::string(wSerial.begin(), wSerial.end());
|
||||
}
|
||||
|
||||
if (!GetCalibrationData(ds4Dev))
|
||||
{
|
||||
hid_close(hidDevice);
|
||||
return;
|
||||
}
|
||||
|
||||
ds4Dev->hasCalibData = true;
|
||||
ds4Dev->path = hidDevInfo->path;
|
||||
|
||||
hid_set_nonblocking(hidDevice, 1);
|
||||
controllers.emplace(serial, ds4Dev);
|
||||
}
|
||||
|
||||
ds4_pad_handler::~ds4_pad_handler()
|
||||
{
|
||||
for (auto& controller : controllers)
|
||||
{
|
||||
if (controller.second->hidDevice)
|
||||
{
|
||||
// Disable blinking and vibration
|
||||
controller.second->smallVibrate = 0;
|
||||
controller.second->largeVibrate = 0;
|
||||
controller.second->led_delay_on = 0;
|
||||
controller.second->led_delay_off = 0;
|
||||
SendVibrateData(controller.second);
|
||||
|
||||
hid_close(controller.second->hidDevice);
|
||||
}
|
||||
}
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
int ds4_pad_handler::SendVibrateData(const std::shared_ptr<DS4Device>& device)
|
||||
{
|
||||
if (!device)
|
||||
return -2;
|
||||
|
||||
auto p_profile = device->config;
|
||||
if (p_profile == nullptr)
|
||||
return -2; // hid_write and hid_write_control return -1 on error
|
||||
|
||||
std::array<u8, 78> outputBuf{0};
|
||||
// write rumble state
|
||||
if (device->btCon)
|
||||
{
|
||||
outputBuf[0] = 0x11;
|
||||
outputBuf[1] = 0xC4;
|
||||
outputBuf[3] = 0x07;
|
||||
outputBuf[6] = device->smallVibrate;
|
||||
outputBuf[7] = device->largeVibrate;
|
||||
outputBuf[8] = p_profile->colorR; // red
|
||||
outputBuf[9] = p_profile->colorG; // green
|
||||
outputBuf[10] = p_profile->colorB; // blue
|
||||
|
||||
// alternating blink states with values 0-255: only setting both to zero disables blinking
|
||||
// 255 is roughly 2 seconds, so setting both values to 255 results in a 4 second interval
|
||||
// using something like (0,10) will heavily blink, while using (0, 255) will be slow. you catch the drift
|
||||
outputBuf[11] = device->led_delay_on;
|
||||
outputBuf[12] = device->led_delay_off;
|
||||
|
||||
const u8 btHdr = 0xA2;
|
||||
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
|
||||
const u32 crcCalc = CRCPP::CRC::Calculate(outputBuf.data(), (DS4_OUTPUT_REPORT_0x11_SIZE - 4), crcTable, crcHdr);
|
||||
|
||||
outputBuf[74] = (crcCalc >> 0) & 0xFF;
|
||||
outputBuf[75] = (crcCalc >> 8) & 0xFF;
|
||||
outputBuf[76] = (crcCalc >> 16) & 0xFF;
|
||||
outputBuf[77] = (crcCalc >> 24) & 0xFF;
|
||||
|
||||
return hid_write_control(device->hidDevice, outputBuf.data(), DS4_OUTPUT_REPORT_0x11_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBuf[0] = 0x05;
|
||||
outputBuf[1] = 0x07;
|
||||
outputBuf[4] = device->smallVibrate;
|
||||
outputBuf[5] = device->largeVibrate;
|
||||
outputBuf[6] = p_profile->colorR; // red
|
||||
outputBuf[7] = p_profile->colorG; // green
|
||||
outputBuf[8] = p_profile->colorB; // blue
|
||||
outputBuf[9] = device->led_delay_on;
|
||||
outputBuf[10] = device->led_delay_off;
|
||||
|
||||
return hid_write(device->hidDevice, outputBuf.data(), DS4_OUTPUT_REPORT_0x05_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::Init()
|
||||
{
|
||||
if (is_init)
|
||||
return true;
|
||||
|
||||
const int res = hid_init();
|
||||
if (res != 0)
|
||||
fmt::throw_exception("hidapi-init error.threadproc");
|
||||
|
||||
// get all the possible controllers at start
|
||||
bool warn_about_drivers = false;
|
||||
for (auto pid : ds4Pids)
|
||||
{
|
||||
hid_device_info* devInfo = hid_enumerate(DS4_VID, pid);
|
||||
hid_device_info* head = devInfo;
|
||||
while (devInfo)
|
||||
{
|
||||
if (controllers.size() >= MAX_GAMEPADS)
|
||||
break;
|
||||
|
||||
hid_device* dev = hid_open_path(devInfo->path);
|
||||
if (dev)
|
||||
{
|
||||
CheckAddDevice(dev, devInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(HLE, "[DS4] hid_open_path failed! Reason: %s", hid_error(dev));
|
||||
warn_about_drivers = true;
|
||||
}
|
||||
devInfo = devInfo->next;
|
||||
}
|
||||
hid_free_enumeration(head);
|
||||
}
|
||||
|
||||
if (warn_about_drivers)
|
||||
{
|
||||
LOG_ERROR(HLE, "[DS4] One or more DS4 pads were detected but couldn't be interacted with directly");
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
LOG_ERROR(HLE, "[DS4] Check https://wiki.rpcs3.net/index.php?title=Help:Controller_Configuration for intructions on how to solve this issue");
|
||||
#endif
|
||||
}
|
||||
else if (controllers.empty())
|
||||
{
|
||||
LOG_WARNING(HLE, "[DS4] No controllers found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_SUCCESS(HLE, "[DS4] Controllers found: %d", controllers.size());
|
||||
}
|
||||
|
||||
is_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> ds4_pad_handler::ListDevices()
|
||||
{
|
||||
std::vector<std::string> ds4_pads_list;
|
||||
|
||||
if (!Init())
|
||||
return ds4_pads_list;
|
||||
|
||||
for (size_t i = 1; i <= controllers.size(); ++i) // Controllers 1-n in GUI
|
||||
{
|
||||
ds4_pads_list.emplace_back(m_name_string + std::to_string(i));
|
||||
}
|
||||
|
||||
return ds4_pads_list;
|
||||
}
|
||||
|
||||
ds4_pad_handler::DS4DataStatus ds4_pad_handler::GetRawData(const std::shared_ptr<DS4Device>& device)
|
||||
{
|
||||
if (!device)
|
||||
return DS4DataStatus::ReadError;
|
||||
|
||||
std::array<u8, 78> buf{};
|
||||
|
||||
const int res = hid_read(device->hidDevice, buf.data(), device->btCon ? 78 : 64);
|
||||
if (res == -1)
|
||||
{
|
||||
// looks like controller disconnected or read error
|
||||
return DS4DataStatus::ReadError;
|
||||
}
|
||||
|
||||
// no data? keep going
|
||||
if (res == 0)
|
||||
return DS4DataStatus::NoNewData;
|
||||
|
||||
// bt controller sends this until 0x02 feature report is sent back (happens on controller init/restart)
|
||||
if (device->btCon && buf[0] == 0x1)
|
||||
{
|
||||
// tells controller to send 0x11 reports
|
||||
std::array<u8, 64> buf_error{};
|
||||
buf_error[0] = 0x2;
|
||||
hid_get_feature_report(device->hidDevice, buf_error.data(), buf_error.size());
|
||||
return DS4DataStatus::NoNewData;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
// check report and set offset
|
||||
if (device->btCon && buf[0] == 0x11 && res == 78)
|
||||
{
|
||||
offset = 2;
|
||||
|
||||
const u8 btHdr = 0xA1;
|
||||
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
|
||||
const u32 crcCalc = CRCPP::CRC::Calculate(buf.data(), (DS4_INPUT_REPORT_0x11_SIZE - 4), crcTable, crcHdr);
|
||||
const u32 crcReported = read_u32(&buf[DS4_INPUT_REPORT_0x11_SIZE - 4]);
|
||||
if (crcCalc != crcReported)
|
||||
{
|
||||
LOG_WARNING(HLE, "[DS4] Data packet CRC check failed, ignoring! Received 0x%x, Expected 0x%x", crcReported, crcCalc);
|
||||
return DS4DataStatus::NoNewData;
|
||||
}
|
||||
}
|
||||
else if (!device->btCon && buf[0] == 0x01 && res == 64)
|
||||
{
|
||||
// Ds4 Dongle uses this bit to actually report whether a controller is connected
|
||||
bool connected = (buf[31] & 0x04) ? false : true;
|
||||
if (connected && !device->hasCalibData)
|
||||
device->hasCalibData = GetCalibrationData(device);
|
||||
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
return DS4DataStatus::NoNewData;
|
||||
|
||||
int battery_offset = offset + DS4_INPUT_REPORT_BATTERY_OFFSET;
|
||||
device->cableState = (buf[battery_offset] >> 4) & 0x01;
|
||||
device->batteryLevel = buf[battery_offset] & 0x0F;
|
||||
|
||||
if (device->hasCalibData)
|
||||
{
|
||||
int calibOffset = offset + DS4_INPUT_REPORT_GYRO_X_OFFSET;
|
||||
for (int i = 0; i < DS4CalibIndex::COUNT; ++i)
|
||||
{
|
||||
const s16 rawValue = read_s16(&buf[calibOffset]);
|
||||
const s16 calValue = ApplyCalibration(rawValue, device->calibData[i]);
|
||||
buf[calibOffset++] = (static_cast<u16>(calValue) >> 0) & 0xFF;
|
||||
buf[calibOffset++] = (static_cast<u16>(calValue) >> 8) & 0xFF;
|
||||
}
|
||||
}
|
||||
memcpy(device->padData.data(), &buf[offset], 64);
|
||||
|
||||
return DS4DataStatus::NewData;
|
||||
}
|
||||
|
||||
std::shared_ptr<PadDevice> ds4_pad_handler::get_device(const std::string& device)
|
||||
{
|
||||
std::shared_ptr<DS4Device> ds4device = GetDS4Device(device);
|
||||
if (ds4device == nullptr || ds4device->hidDevice == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return ds4device;
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::get_is_left_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == DS4KeyCodes::L2;
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::get_is_right_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == DS4KeyCodes::R2;
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::get_is_left_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case DS4KeyCodes::LSXNeg:
|
||||
case DS4KeyCodes::LSXPos:
|
||||
case DS4KeyCodes::LSYPos:
|
||||
case DS4KeyCodes::LSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ds4_pad_handler::get_is_right_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case DS4KeyCodes::RSXNeg:
|
||||
case DS4KeyCodes::RSXPos:
|
||||
case DS4KeyCodes::RSYPos:
|
||||
case DS4KeyCodes::RSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PadHandlerBase::connection ds4_pad_handler::update_connection(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
auto ds4_dev = std::static_pointer_cast<DS4Device>(device);
|
||||
if (!ds4_dev)
|
||||
return connection::disconnected;
|
||||
|
||||
if (ds4_dev->hidDevice == nullptr)
|
||||
{
|
||||
// try to reconnect
|
||||
hid_device* dev = hid_open_path(ds4_dev->path.c_str());
|
||||
if (dev)
|
||||
{
|
||||
hid_set_nonblocking(dev, 1);
|
||||
ds4_dev->hidDevice = dev;
|
||||
if (!ds4_dev->hasCalibData)
|
||||
ds4_dev->hasCalibData = GetCalibrationData(ds4_dev);
|
||||
}
|
||||
else
|
||||
{
|
||||
// nope, not there
|
||||
return connection::disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
status = GetRawData(ds4_dev);
|
||||
|
||||
if (status == DS4DataStatus::ReadError)
|
||||
{
|
||||
// this also can mean disconnected, either way deal with it on next loop and reconnect
|
||||
hid_close(ds4_dev->hidDevice);
|
||||
ds4_dev->hidDevice = nullptr;
|
||||
|
||||
return connection::no_data;
|
||||
}
|
||||
|
||||
return connection::connected;
|
||||
}
|
||||
|
||||
void ds4_pad_handler::get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto ds4_device = std::static_pointer_cast<DS4Device>(device);
|
||||
if (!ds4_device || !pad)
|
||||
return;
|
||||
|
||||
auto buf = ds4_device->padData;
|
||||
|
||||
pad->m_battery_level = ds4_device->batteryLevel;
|
||||
pad->m_cable_state = ds4_device->cableState;
|
||||
|
||||
// these values come already calibrated from our ds4Thread,
|
||||
// all we need to do is convert to ds3 range
|
||||
|
||||
// accel
|
||||
f32 accelX = static_cast<s16>((buf[20] << 8) | buf[19]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
f32 accelY = static_cast<s16>((buf[22] << 8) | buf[21]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
f32 accelZ = static_cast<s16>((buf[24] << 8) | buf[23]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
|
||||
// now just use formula from ds3
|
||||
accelX = accelX * 113 + 512;
|
||||
accelY = accelY * 113 + 512;
|
||||
accelZ = accelZ * 113 + 512;
|
||||
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(accelX);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(accelY);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(accelZ);
|
||||
|
||||
// gyroX is yaw, which is all that we need
|
||||
f32 gyroX = static_cast<s16>((buf[16] << 8) | buf[15]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
//const int gyroY = ((u16)(buf[14] << 8) | buf[13]) / 256;
|
||||
//const int gyroZ = ((u16)(buf[18] << 8) | buf[17]) / 256;
|
||||
|
||||
// convert to ds3
|
||||
gyroX = gyroX * (123.f / 90.f) + 512;
|
||||
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(gyroX);
|
||||
}
|
||||
|
||||
void ds4_pad_handler::apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto ds4_dev = std::static_pointer_cast<DS4Device>(device);
|
||||
if (!ds4_dev || !pad)
|
||||
return;
|
||||
|
||||
auto profile = ds4_dev->config;
|
||||
|
||||
// Attempt to send rumble no matter what
|
||||
int idx_l = profile->switch_vibration_motors ? 1 : 0;
|
||||
int idx_s = profile->switch_vibration_motors ? 0 : 1;
|
||||
|
||||
int speed_large = profile->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : vibration_min;
|
||||
int speed_small = profile->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : vibration_min;
|
||||
|
||||
bool wireless = ds4_dev->cableState < 1;
|
||||
bool lowBattery = ds4_dev->batteryLevel < 2;
|
||||
bool isBlinking = ds4_dev->led_delay_on > 0 || ds4_dev->led_delay_off > 0;
|
||||
bool newBlinkData = false;
|
||||
|
||||
// we are now wired or have okay battery level -> stop blinking
|
||||
if (isBlinking && !(wireless && lowBattery))
|
||||
{
|
||||
ds4_dev->led_delay_on = 0;
|
||||
ds4_dev->led_delay_off = 0;
|
||||
newBlinkData = true;
|
||||
}
|
||||
// we are now wireless and low on battery -> blink
|
||||
if (!isBlinking && wireless && lowBattery)
|
||||
{
|
||||
ds4_dev->led_delay_on = 100;
|
||||
ds4_dev->led_delay_off = 100;
|
||||
newBlinkData = true;
|
||||
}
|
||||
|
||||
ds4_dev->newVibrateData |= ds4_dev->largeVibrate != speed_large || ds4_dev->smallVibrate != speed_small || newBlinkData;
|
||||
|
||||
ds4_dev->largeVibrate = speed_large;
|
||||
ds4_dev->smallVibrate = speed_small;
|
||||
|
||||
if (ds4_dev->newVibrateData && SendVibrateData(ds4_dev) >= 0)
|
||||
{
|
||||
ds4_dev->newVibrateData = false;
|
||||
}
|
||||
}
|
||||
151
rpcs3/Input/ds4_pad_handler.h
Normal file
151
rpcs3/Input/ds4_pad_handler.h
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Utilities/CRC.h"
|
||||
#include "hidapi.h"
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
enum DS4CalibIndex
|
||||
{
|
||||
// gyro
|
||||
PITCH = 0,
|
||||
YAW,
|
||||
ROLL,
|
||||
|
||||
// accel
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct DS4CalibData
|
||||
{
|
||||
s16 bias;
|
||||
s32 sensNumer;
|
||||
s32 sensDenom;
|
||||
};
|
||||
|
||||
enum class DS4DataStatus
|
||||
{
|
||||
NewData,
|
||||
NoNewData,
|
||||
ReadError,
|
||||
};
|
||||
|
||||
struct DS4Device : public PadDevice
|
||||
{
|
||||
hid_device* hidDevice{ nullptr };
|
||||
std::string path{ "" };
|
||||
bool btCon{ false };
|
||||
bool hasCalibData{ false };
|
||||
std::array<DS4CalibData, DS4CalibIndex::COUNT> calibData{};
|
||||
bool newVibrateData{ true };
|
||||
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;
|
||||
|
||||
// pid's of connected ds4
|
||||
const std::array<u16, 3> ds4Pids = { { 0xBA0, 0x5C4, 0x09CC } };
|
||||
|
||||
// pseudo 'controller id' to keep track of unique controllers
|
||||
std::unordered_map<std::string, std::shared_ptr<DS4Device>> controllers;
|
||||
CRCPP::CRC::Table<u32, 32> crcTable{ CRCPP::CRC::CRC_32() };
|
||||
|
||||
public:
|
||||
ds4_pad_handler();
|
||||
~ds4_pad_handler();
|
||||
|
||||
bool Init() override;
|
||||
|
||||
std::vector<std::string> ListDevices() override;
|
||||
void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b) override;
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
|
||||
private:
|
||||
bool is_init = false;
|
||||
DS4DataStatus status;
|
||||
|
||||
private:
|
||||
std::shared_ptr<DS4Device> GetDS4Device(const std::string& padId, bool try_reconnect = false);
|
||||
// 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
|
||||
bool GetCalibrationData(const std::shared_ptr<DS4Device>& ds4Device);
|
||||
void CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo);
|
||||
int SendVibrateData(const std::shared_ptr<DS4Device>& device);
|
||||
inline s16 ApplyCalibration(s32 rawValue, const DS4CalibData& calibData)
|
||||
{
|
||||
const s32 biased = rawValue - calibData.bias;
|
||||
const s32 quot = calibData.sensNumer / calibData.sensDenom;
|
||||
const s32 rem = calibData.sensNumer % calibData.sensDenom;
|
||||
const s32 output = (quot * biased) + ((rem * biased) / calibData.sensDenom);
|
||||
|
||||
if (output > std::numeric_limits<s16>::max())
|
||||
return std::numeric_limits<s16>::max();
|
||||
else if (output < std::numeric_limits<s16>::min())
|
||||
return std::numeric_limits<s16>::min();
|
||||
else return static_cast<s16>(output);
|
||||
}
|
||||
|
||||
std::shared_ptr<PadDevice> get_device(const std::string& device) override;
|
||||
bool get_is_left_trigger(u64 keyCode) override;
|
||||
bool get_is_right_trigger(u64 keyCode) override;
|
||||
bool get_is_left_stick(u64 keyCode) override;
|
||||
bool get_is_right_stick(u64 keyCode) override;
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
|
||||
std::array<int, 6> get_preview_values(std::unordered_map<u64, u16> data) override;
|
||||
};
|
||||
1027
rpcs3/Input/evdev_joystick_handler.cpp
Normal file
1027
rpcs3/Input/evdev_joystick_handler.cpp
Normal file
File diff suppressed because it is too large
Load diff
377
rpcs3/Input/evdev_joystick_handler.h
Normal file
377
rpcs3/Input/evdev_joystick_handler.h
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
#pragma once
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
|
||||
#include "Utilities/types.h"
|
||||
#include "Utilities/Config.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <ctime>
|
||||
|
||||
struct positive_axis : cfg::node
|
||||
{
|
||||
const std::string cfg_name = fs::get_config_dir() + "/evdev_positive_axis.yml";
|
||||
|
||||
cfg::_bool abs_x{ this, "ABS_X", false };
|
||||
cfg::_bool abs_y{ this, "ABS_Y", false };
|
||||
cfg::_bool abs_z{ this, "ABS_Z", false };
|
||||
cfg::_bool abs_rx{ this, "ABS_RX", false };
|
||||
cfg::_bool abs_ry{ this, "ABS_RY", false };
|
||||
cfg::_bool abs_rz{ this, "ABS_RZ", false };
|
||||
cfg::_bool abs_throttle{ this, "ABS_THROTTLE", false };
|
||||
cfg::_bool abs_rudder{ this, "ABS_RUDDER", false };
|
||||
cfg::_bool abs_wheel{ this, "ABS_WHEEL", false };
|
||||
cfg::_bool abs_gas{ this, "ABS_GAS", false };
|
||||
cfg::_bool abs_brake{ this, "ABS_BRAKE", false };
|
||||
cfg::_bool abs_hat0x{ this, "ABS_HAT0X", false };
|
||||
cfg::_bool abs_hat0y{ this, "ABS_HAT0Y", false };
|
||||
cfg::_bool abs_hat1x{ this, "ABS_HAT1X", false };
|
||||
cfg::_bool abs_hat1y{ this, "ABS_HAT1Y", false };
|
||||
cfg::_bool abs_hat2x{ this, "ABS_HAT2X", false };
|
||||
cfg::_bool abs_hat2y{ this, "ABS_HAT2Y", false };
|
||||
cfg::_bool abs_hat3x{ this, "ABS_HAT3X", false };
|
||||
cfg::_bool abs_hat3y{ this, "ABS_HAT3Y", false };
|
||||
cfg::_bool abs_pressure{ this, "ABS_PRESSURE", false };
|
||||
cfg::_bool abs_distance{ this, "ABS_DISTANCE", false };
|
||||
cfg::_bool abs_tilt_x{ this, "ABS_TILT_X", false };
|
||||
cfg::_bool abs_tilt_y{ this, "ABS_TILT_Y", false };
|
||||
cfg::_bool abs_tool_width{ this, "ABS_TOOL_WIDTH", false };
|
||||
cfg::_bool abs_volume{ this, "ABS_VOLUME", false };
|
||||
cfg::_bool abs_misc{ this, "ABS_MISC", false };
|
||||
cfg::_bool abs_mt_slot{ this, "ABS_MT_SLOT", false };
|
||||
cfg::_bool abs_mt_touch_major{ this, "ABS_MT_TOUCH_MAJOR", false };
|
||||
cfg::_bool abs_mt_touch_minor{ this, "ABS_MT_TOUCH_MINOR", false };
|
||||
cfg::_bool abs_mt_width_major{ this, "ABS_MT_WIDTH_MAJOR", false };
|
||||
cfg::_bool abs_mt_width_minor{ this, "ABS_MT_WIDTH_MINOR", false };
|
||||
cfg::_bool abs_mt_orientation{ this, "ABS_MT_ORIENTATION", false };
|
||||
cfg::_bool abs_mt_position_x{ this, "ABS_MT_POSITION_X", false };
|
||||
cfg::_bool abs_mt_position_y{ this, "ABS_MT_POSITION_Y", false };
|
||||
cfg::_bool abs_mt_tool_type{ this, "ABS_MT_TOOL_TYPE", false };
|
||||
cfg::_bool abs_mt_blob_id{ this, "ABS_MT_BLOB_ID", false };
|
||||
cfg::_bool abs_mt_tracking_id{ this, "ABS_MT_TRACKING_ID", false };
|
||||
cfg::_bool abs_mt_pressure{ this, "ABS_MT_PRESSURE", false };
|
||||
cfg::_bool abs_mt_distance{ this, "ABS_MT_DISTANCE", false };
|
||||
cfg::_bool abs_mt_tool_x{ this, "ABS_MT_TOOL_X", false };
|
||||
cfg::_bool abs_mt_tool_y{ this, "ABS_MT_TOOL_Y", 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 evdev_joystick_handler final : public PadHandlerBase
|
||||
{
|
||||
// Unique button names for the config files and our pad settings dialog
|
||||
const std::unordered_map<u32, std::string> button_list =
|
||||
{
|
||||
// Xbox One S Controller returns some buttons as key when connected through bluetooth
|
||||
{ KEY_BACK , "Back Key" },
|
||||
{ KEY_HOMEPAGE , "Homepage Key"},
|
||||
//{ 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" }
|
||||
};
|
||||
|
||||
// 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 EvdevButton
|
||||
{
|
||||
u32 code;
|
||||
int dir;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct EvdevDevice : public PadDevice
|
||||
{
|
||||
libevdev* device{ nullptr };
|
||||
std::string path;
|
||||
std::unordered_map<int, bool> axis_orientations; // value is true if key was found in rev_axis_list
|
||||
s32 stick_val[4] = { 0, 0, 0, 0 };
|
||||
u16 val_min[4] = { 0, 0, 0, 0 };
|
||||
u16 val_max[4] = { 0, 0, 0, 0 };
|
||||
EvdevButton trigger_left = { 0, 0, 0 };
|
||||
EvdevButton trigger_right = { 0, 0, 0 };
|
||||
std::vector<EvdevButton> axis_left = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
|
||||
std::vector<EvdevButton> axis_right = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
|
||||
int cur_dir = 0;
|
||||
int cur_type = 0;
|
||||
int effect_id = -1;
|
||||
bool has_rumble = false;
|
||||
u16 force_large = 0;
|
||||
u16 force_small = 0;
|
||||
clock_t last_vibration = 0;
|
||||
};
|
||||
|
||||
const int BUTTON_COUNT = 17;
|
||||
|
||||
public:
|
||||
evdev_joystick_handler();
|
||||
~evdev_joystick_handler();
|
||||
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
bool Init() override;
|
||||
std::vector<std::string> ListDevices() override;
|
||||
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
|
||||
void Close();
|
||||
void get_next_button_press(const std::string& padId, const std::function<void(u16, std::string, std::string, std::array<int, 6>)>& callback, const std::function<void(std::string)>& fail_callback, bool get_blacklist = false, const std::vector<std::string>& buttons = {}) override;
|
||||
void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<EvdevDevice> get_evdev_device(const std::string& device);
|
||||
std::string get_device_name(const libevdev* dev);
|
||||
bool update_device(const std::shared_ptr<PadDevice>& device);
|
||||
void update_devs();
|
||||
int add_device(const std::string& device, const std::shared_ptr<Pad>& pad, bool in_settings = false);
|
||||
int GetButtonInfo(const input_event& evt, const std::shared_ptr<EvdevDevice>& device, int& button_code);
|
||||
std::unordered_map<u64, std::pair<u16, bool>> GetButtonValues(const std::shared_ptr<EvdevDevice>& device);
|
||||
void SetRumble(std::shared_ptr<EvdevDevice> device, u16 large, u16 small);
|
||||
|
||||
// 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);
|
||||
|
||||
positive_axis m_pos_axis_config;
|
||||
std::vector<u32> m_positive_axis;
|
||||
std::vector<std::string> blacklist;
|
||||
std::unordered_map<std::string, int> settings_added;
|
||||
std::shared_ptr<EvdevDevice> m_dev;
|
||||
bool m_is_button_or_trigger;
|
||||
bool m_is_negative;
|
||||
bool m_is_init = false;
|
||||
|
||||
bool check_button(const EvdevButton& b, const u32 code);
|
||||
bool check_buttons(const std::vector<EvdevButton>& b, const u32 code);
|
||||
|
||||
protected:
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
void get_mapping(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
bool get_is_left_trigger(u64 keyCode) override;
|
||||
bool get_is_right_trigger(u64 keyCode) override;
|
||||
bool get_is_left_stick(u64 keyCode) override;
|
||||
bool get_is_right_stick(u64 keyCode) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
662
rpcs3/Input/keyboard_pad_handler.cpp
Normal file
662
rpcs3/Input/keyboard_pad_handler.cpp
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
#include "keyboard_pad_handler.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
|
||||
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
|
||||
constexpr auto qstr = QString::fromStdString;
|
||||
|
||||
bool keyboard_pad_handler::Init()
|
||||
{
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
m_last_mouse_move_left = now;
|
||||
m_last_mouse_move_right = now;
|
||||
m_last_mouse_move_up = now;
|
||||
m_last_mouse_move_down = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
keyboard_pad_handler::keyboard_pad_handler() : PadHandlerBase(pad_handler::keyboard), QObject()
|
||||
{
|
||||
init_configs();
|
||||
|
||||
// set capabilities
|
||||
b_has_config = true;
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::init_config(pad_config* cfg, const std::string& name)
|
||||
{
|
||||
// Set this profile's save location
|
||||
cfg->cfg_name = name;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = GetKeyName(Qt::Key_A);
|
||||
cfg->ls_down.def = GetKeyName(Qt::Key_S);
|
||||
cfg->ls_right.def = GetKeyName(Qt::Key_D);
|
||||
cfg->ls_up.def = GetKeyName(Qt::Key_W);
|
||||
cfg->rs_left.def = GetKeyName(Qt::Key_Home);
|
||||
cfg->rs_down.def = GetKeyName(Qt::Key_PageDown);
|
||||
cfg->rs_right.def = GetKeyName(Qt::Key_End);
|
||||
cfg->rs_up.def = GetKeyName(Qt::Key_PageUp);
|
||||
cfg->start.def = GetKeyName(Qt::Key_Return);
|
||||
cfg->select.def = GetKeyName(Qt::Key_Space);
|
||||
cfg->ps.def = GetKeyName(Qt::Key_Backspace);
|
||||
cfg->square.def = GetKeyName(Qt::Key_Z);
|
||||
cfg->cross.def = GetKeyName(Qt::Key_X);
|
||||
cfg->circle.def = GetKeyName(Qt::Key_C);
|
||||
cfg->triangle.def = GetKeyName(Qt::Key_V);
|
||||
cfg->left.def = GetKeyName(Qt::Key_Left);
|
||||
cfg->down.def = GetKeyName(Qt::Key_Down);
|
||||
cfg->right.def = GetKeyName(Qt::Key_Right);
|
||||
cfg->up.def = GetKeyName(Qt::Key_Up);
|
||||
cfg->r1.def = GetKeyName(Qt::Key_E);
|
||||
cfg->r2.def = GetKeyName(Qt::Key_T);
|
||||
cfg->r3.def = GetKeyName(Qt::Key_G);
|
||||
cfg->l1.def = GetKeyName(Qt::Key_Q);
|
||||
cfg->l2.def = GetKeyName(Qt::Key_R);
|
||||
cfg->l3.def = GetKeyName(Qt::Key_F);
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (button.m_keyCode != code)
|
||||
continue;
|
||||
|
||||
button.m_pressed = pressed;
|
||||
button.m_value = pressed ? value : 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < static_cast<int>(pad->m_sticks.size()); i++)
|
||||
{
|
||||
bool is_max = pad->m_sticks[i].m_keyCodeMax == code;
|
||||
bool is_min = pad->m_sticks[i].m_keyCodeMin == code;
|
||||
|
||||
u16 normalized_value = std::max<u16>(1, static_cast<u16>(std::floor(value / 2.0)));
|
||||
|
||||
if (is_max)
|
||||
m_stick_max[i] = pressed ? 128 + normalized_value : 128;
|
||||
|
||||
if (is_min)
|
||||
m_stick_min[i] = pressed ? normalized_value : 0;
|
||||
|
||||
if (is_max || is_min)
|
||||
{
|
||||
m_stick_val[i] = m_stick_max[i] - m_stick_min[i];
|
||||
|
||||
const f32 stick_lerp_factor = (i < 2) ? m_l_stick_lerp_factor : m_r_stick_lerp_factor;
|
||||
|
||||
// to get the fastest response time possible we don't wanna use any lerp with factor 1
|
||||
if (stick_lerp_factor >= 1.0f)
|
||||
{
|
||||
pad->m_sticks[i].m_value = m_stick_val[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int keyboard_pad_handler::GetModifierCode(QKeyEvent* e)
|
||||
{
|
||||
switch (e->key())
|
||||
{
|
||||
case Qt::Key_Control:
|
||||
case Qt::Key_Alt:
|
||||
case Qt::Key_AltGr:
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_Meta:
|
||||
case Qt::Key_NumLock:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (e->modifiers() == Qt::ControlModifier)
|
||||
return Qt::ControlModifier;
|
||||
else if (e->modifiers() == Qt::AltModifier)
|
||||
return Qt::AltModifier;
|
||||
else if (e->modifiers() == Qt::MetaModifier)
|
||||
return Qt::MetaModifier;
|
||||
else if (e->modifiers() == Qt::ShiftModifier)
|
||||
return Qt::ShiftModifier;
|
||||
else if (e->modifiers() == Qt::KeypadModifier)
|
||||
return Qt::KeypadModifier;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool keyboard_pad_handler::eventFilter(QObject* target, QEvent* ev)
|
||||
{
|
||||
// !m_target is for future proofing when gsrender isn't automatically initialized on load.
|
||||
// !m_target->isVisible() is a hack since currently a guiless application will STILL inititialize a gsrender (providing a valid target)
|
||||
if (!m_target || !m_target->isVisible()|| target == m_target)
|
||||
{
|
||||
switch (ev->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
keyPressEvent(static_cast<QKeyEvent*>(ev));
|
||||
break;
|
||||
case QEvent::KeyRelease:
|
||||
keyReleaseEvent(static_cast<QKeyEvent*>(ev));
|
||||
break;
|
||||
case QEvent::MouseButtonPress:
|
||||
mousePressEvent(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
mouseReleaseEvent(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
case QEvent::MouseMove:
|
||||
mouseMoveEvent(static_cast<QMouseEvent*>(ev));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Sets the target window for the event handler, and also installs an event filter on the target. */
|
||||
void keyboard_pad_handler::SetTargetWindow(QWindow* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
{
|
||||
m_target = target;
|
||||
target->installEventFilter(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
QApplication::instance()->installEventFilter(this);
|
||||
// If this is hit, it probably means that some refactoring occurs because currently a gsframe is created in Load.
|
||||
// We still want events so filter from application instead since target is null.
|
||||
LOG_ERROR(GENERAL, "Trying to set pad handler to a null target window.");
|
||||
}
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::processKeyEvent(QKeyEvent* event, bool pressed)
|
||||
{
|
||||
if (event->isAutoRepeat())
|
||||
{
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
auto handleKey = [this, pressed, event]()
|
||||
{
|
||||
const QString name = qstr(GetKeyName(event));
|
||||
QStringList list = GetKeyNames(event);
|
||||
if (list.isEmpty())
|
||||
return;
|
||||
|
||||
bool is_num_key = list.contains("Num");
|
||||
if (is_num_key)
|
||||
list.removeAll("Num");
|
||||
|
||||
// TODO: Edge case: switching numlock keeps numpad keys pressed due to now different modifier
|
||||
|
||||
// Handle every possible key combination, for example: ctrl+A -> {ctrl, A, ctrl+A}
|
||||
for (const auto& keyname : list)
|
||||
{
|
||||
// skip the 'original keys' when handling numpad keys
|
||||
if (is_num_key && !keyname.contains("Num"))
|
||||
continue;
|
||||
// skip held modifiers when handling another key
|
||||
if (keyname != name && list.count() > 1 && (keyname == "Alt" || keyname == "AltGr" || keyname == "Ctrl" || keyname == "Meta" || keyname == "Shift"))
|
||||
continue;
|
||||
Key(GetKeyCode(keyname), pressed);
|
||||
}
|
||||
};
|
||||
|
||||
// We need to ignore keys when using rpcs3 keyboard shortcuts
|
||||
int key = event->key();
|
||||
switch (key)
|
||||
{
|
||||
case Qt::Key_Escape:
|
||||
break;
|
||||
case Qt::Key_L:
|
||||
case Qt::Key_Return:
|
||||
if (event->modifiers() != Qt::AltModifier)
|
||||
handleKey();
|
||||
break;
|
||||
case Qt::Key_P:
|
||||
case Qt::Key_S:
|
||||
case Qt::Key_R:
|
||||
case Qt::Key_E:
|
||||
if (event->modifiers() != Qt::ControlModifier)
|
||||
handleKey();
|
||||
break;
|
||||
default:
|
||||
handleKey();
|
||||
break;
|
||||
}
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->modifiers() & Qt::AltModifier)
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_I:
|
||||
m_deadzone_y = std::min(m_deadzone_y + 1, 255);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: deadzone y = %d", m_deadzone_y);
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_U:
|
||||
m_deadzone_y = std::max(0, m_deadzone_y - 1);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: deadzone y = %d", m_deadzone_y);
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_Y:
|
||||
m_deadzone_x = std::min(m_deadzone_x + 1, 255);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: deadzone x = %d", m_deadzone_x);
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_T:
|
||||
m_deadzone_x = std::max(0, m_deadzone_x - 1);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: deadzone x = %d", m_deadzone_x);
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_K:
|
||||
m_multi_y = std::min(m_multi_y + 0.1, 5.0);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: multiplier y = %d", static_cast<int>(m_multi_y * 100));
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_J:
|
||||
m_multi_y = std::max(0.0, m_multi_y - 0.1);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: multiplier y = %d", static_cast<int>(m_multi_y * 100));
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_H:
|
||||
m_multi_x = std::min(m_multi_x + 0.1, 5.0);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: multiplier x = %d", static_cast<int>(m_multi_x * 100));
|
||||
event->ignore();
|
||||
return;
|
||||
case Qt::Key_G:
|
||||
m_multi_x = std::max(0.0, m_multi_x - 0.1);
|
||||
LOG_SUCCESS(GENERAL, "mouse move adjustment: multiplier x = %d", static_cast<int>(m_multi_x * 100));
|
||||
event->ignore();
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
processKeyEvent(event, true);
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::keyReleaseEvent(QKeyEvent* event)
|
||||
{
|
||||
processKeyEvent(event, false);
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
Key(event->button(), true);
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
Key(event->button(), false, 0);
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
static int movement_x = 0;
|
||||
static int movement_y = 0;
|
||||
static int last_pos_x = 0;
|
||||
static int last_pos_y = 0;
|
||||
|
||||
if (m_target && m_target->visibility() == QWindow::Visibility::FullScreen && m_target->isActive())
|
||||
{
|
||||
// get the screen dimensions
|
||||
const QSize screen = m_target->size();
|
||||
|
||||
// get the center of the screen in global coordinates
|
||||
QPoint p_center = m_target->geometry().topLeft() + QPoint(screen.width() / 2, screen.height() / 2);
|
||||
|
||||
// reset the mouse to the center for consistent results since edge movement won't be registered
|
||||
QCursor::setPos(m_target->screen(), p_center);
|
||||
|
||||
// convert the center into screen coordinates
|
||||
p_center = m_target->mapFromGlobal(p_center);
|
||||
|
||||
// get the delta of the mouse position to the screen center
|
||||
const QPoint p_delta = event->pos() - p_center;
|
||||
|
||||
movement_x = p_delta.x();
|
||||
movement_y = p_delta.y();
|
||||
}
|
||||
else
|
||||
{
|
||||
movement_x = event->x() - last_pos_x;
|
||||
movement_y = event->y() - last_pos_y;
|
||||
|
||||
last_pos_x = event->x();
|
||||
last_pos_y = event->y();
|
||||
}
|
||||
|
||||
movement_x = m_multi_x * movement_x;
|
||||
movement_y = m_multi_y * movement_y;
|
||||
|
||||
if (movement_x < 0)
|
||||
{
|
||||
Key(mouse::move_right, false);
|
||||
Key(mouse::move_left, true, std::min(m_deadzone_x + std::abs(movement_x), 255));
|
||||
m_last_mouse_move_left = std::chrono::steady_clock::now();
|
||||
}
|
||||
else if (movement_x > 0)
|
||||
{
|
||||
Key(mouse::move_left, false);
|
||||
Key(mouse::move_right, true, std::min(m_deadzone_x + movement_x, 255));
|
||||
m_last_mouse_move_right = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
// in Qt mouse up is equivalent to movement_y < 0
|
||||
if (movement_y < 0)
|
||||
{
|
||||
Key(mouse::move_down, false);
|
||||
Key(mouse::move_up, true, std::min(m_deadzone_y + std::abs(movement_y), 255));
|
||||
m_last_mouse_move_up = std::chrono::steady_clock::now();
|
||||
}
|
||||
else if (movement_y > 0)
|
||||
{
|
||||
Key(mouse::move_up, false);
|
||||
Key(mouse::move_down, true, std::min(m_deadzone_y + movement_y, 255));
|
||||
m_last_mouse_move_down = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
std::vector<std::string> keyboard_pad_handler::ListDevices()
|
||||
{
|
||||
std::vector<std::string> list_devices;
|
||||
list_devices.push_back("Keyboard");
|
||||
return list_devices;
|
||||
}
|
||||
|
||||
std::string keyboard_pad_handler::GetMouseName(const QMouseEvent* event)
|
||||
{
|
||||
return GetMouseName(event->button());
|
||||
}
|
||||
|
||||
std::string keyboard_pad_handler::GetMouseName(u32 button)
|
||||
{
|
||||
auto it = mouse_list.find(button);
|
||||
if (it != mouse_list.end())
|
||||
return it->second;
|
||||
return "FAIL";
|
||||
}
|
||||
|
||||
QStringList keyboard_pad_handler::GetKeyNames(const QKeyEvent* keyEvent)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier)
|
||||
{
|
||||
list.append("Shift");
|
||||
list.append(QKeySequence(keyEvent->key() | Qt::ShiftModifier).toString(QKeySequence::NativeText));
|
||||
}
|
||||
if (keyEvent->modifiers() & Qt::AltModifier)
|
||||
{
|
||||
list.append("Alt");
|
||||
list.append(QKeySequence(keyEvent->key() | Qt::AltModifier).toString(QKeySequence::NativeText));
|
||||
}
|
||||
if (keyEvent->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
list.append("Ctrl");
|
||||
list.append(QKeySequence(keyEvent->key() | Qt::ControlModifier).toString(QKeySequence::NativeText));
|
||||
}
|
||||
if (keyEvent->modifiers() & Qt::MetaModifier)
|
||||
{
|
||||
list.append("Meta");
|
||||
list.append(QKeySequence(keyEvent->key() | Qt::MetaModifier).toString(QKeySequence::NativeText));
|
||||
}
|
||||
if (keyEvent->modifiers() & Qt::KeypadModifier)
|
||||
{
|
||||
list.append("Num"); // helper object, not used as actual key
|
||||
list.append(QKeySequence(keyEvent->key() | Qt::KeypadModifier).toString(QKeySequence::NativeText));
|
||||
}
|
||||
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_Alt:
|
||||
list.append("Alt");
|
||||
break;
|
||||
case Qt::Key_AltGr:
|
||||
list.append("AltGr");
|
||||
break;
|
||||
case Qt::Key_Shift:
|
||||
list.append("Shift");
|
||||
break;
|
||||
case Qt::Key_Control:
|
||||
list.append("Ctrl");
|
||||
break;
|
||||
case Qt::Key_Meta:
|
||||
list.append("Meta");
|
||||
break;
|
||||
default:
|
||||
list.append(QKeySequence(keyEvent->key()).toString(QKeySequence::NativeText));
|
||||
break;
|
||||
}
|
||||
|
||||
list.removeDuplicates();
|
||||
return list;
|
||||
}
|
||||
|
||||
std::string keyboard_pad_handler::GetKeyName(const QKeyEvent* keyEvent)
|
||||
{
|
||||
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_Meta:
|
||||
return "Meta";
|
||||
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)
|
||||
{
|
||||
return sstr(QKeySequence(keyCode).toString(QKeySequence::NativeText));
|
||||
}
|
||||
|
||||
u32 keyboard_pad_handler::GetKeyCode(const std::string& keyName)
|
||||
{
|
||||
return GetKeyCode(qstr(keyName));
|
||||
}
|
||||
|
||||
u32 keyboard_pad_handler::GetKeyCode(const QString& keyName)
|
||||
{
|
||||
if (keyName.isEmpty())
|
||||
return 0;
|
||||
else 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;
|
||||
else if (keyName == "Meta")
|
||||
return Qt::Key_Meta;
|
||||
|
||||
QKeySequence seq(keyName);
|
||||
u32 keyCode = 0;
|
||||
|
||||
if (seq.count() == 1)
|
||||
keyCode = seq[0];
|
||||
else
|
||||
LOG_NOTICE(GENERAL, "GetKeyCode(%s): seq.count() = %d", sstr(keyName), seq.count());
|
||||
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device)
|
||||
{
|
||||
if (device != "Keyboard")
|
||||
return false;
|
||||
|
||||
int index = static_cast<int>(bindings.size());
|
||||
m_pad_configs[index].load();
|
||||
pad_config* p_profile = &m_pad_configs[index];
|
||||
if (p_profile == nullptr)
|
||||
return false;
|
||||
|
||||
m_deadzone_x = p_profile->mouse_deadzone_x;
|
||||
m_deadzone_y = p_profile->mouse_deadzone_y;
|
||||
m_multi_x = p_profile->mouse_acceleration_x / 100.0;
|
||||
m_multi_y = p_profile->mouse_acceleration_y / 100.0;
|
||||
m_l_stick_lerp_factor = p_profile->l_stick_lerp_factor / 100.0f;
|
||||
m_r_stick_lerp_factor = p_profile->r_stick_lerp_factor / 100.0f;
|
||||
|
||||
auto find_key = [&](const cfg::string& name)
|
||||
{
|
||||
int key = FindKeyCode(mouse_list, name, false);
|
||||
if (key < 0)
|
||||
key = GetKeyCode(name);
|
||||
if (key < 0)
|
||||
key = 0;
|
||||
return key;
|
||||
};
|
||||
|
||||
//Fixed assign change, default is both sensor and press off
|
||||
pad->Init
|
||||
(
|
||||
CELL_PAD_STATUS_DISCONNECTED,
|
||||
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,
|
||||
p_profile->device_class_type
|
||||
);
|
||||
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->left), CELL_PAD_CTRL_LEFT);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->down), CELL_PAD_CTRL_DOWN);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->right), CELL_PAD_CTRL_RIGHT);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->up), CELL_PAD_CTRL_UP);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->start), CELL_PAD_CTRL_START);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->r3), CELL_PAD_CTRL_R3);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->l3), CELL_PAD_CTRL_L3);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, find_key(p_profile->select), CELL_PAD_CTRL_SELECT);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->ps), 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support
|
||||
//pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x0); // Reserved (and currently not in use by rpcs3 at all)
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->square), CELL_PAD_CTRL_SQUARE);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->cross), CELL_PAD_CTRL_CROSS);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->circle), CELL_PAD_CTRL_CIRCLE);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->triangle), CELL_PAD_CTRL_TRIANGLE);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->r1), CELL_PAD_CTRL_R1);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->l1), CELL_PAD_CTRL_L1);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->r2), CELL_PAD_CTRL_R2);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, find_key(p_profile->l2), CELL_PAD_CTRL_L2);
|
||||
|
||||
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X, find_key(p_profile->ls_left), find_key(p_profile->ls_right));
|
||||
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y, find_key(p_profile->ls_up), find_key(p_profile->ls_down));
|
||||
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X, find_key(p_profile->rs_left), find_key(p_profile->rs_right));
|
||||
pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y, find_key(p_profile->rs_up), find_key(p_profile->rs_down));
|
||||
|
||||
pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_X, 512);
|
||||
pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Y, 399);
|
||||
pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_Z, 512);
|
||||
pad->m_sensors.emplace_back(CELL_PAD_BTN_OFFSET_SENSOR_G, 512);
|
||||
|
||||
pad->m_vibrateMotors.emplace_back(true, 0);
|
||||
pad->m_vibrateMotors.emplace_back(false, 0);
|
||||
|
||||
bindings.push_back(pad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void keyboard_pad_handler::ThreadProc()
|
||||
{
|
||||
for (int i = 0; i < bindings.size(); i++)
|
||||
{
|
||||
if (last_connection_status[i] == false)
|
||||
{
|
||||
bindings[i]->m_port_status |= CELL_PAD_STATUS_CONNECTED;
|
||||
bindings[i]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
|
||||
last_connection_status[i] = true;
|
||||
connected_devices++;
|
||||
}
|
||||
else
|
||||
{
|
||||
static std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
now = std::chrono::steady_clock::now();
|
||||
|
||||
const double elapsed_left = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last_mouse_move_left).count() / 1000.0;
|
||||
const double elapsed_right = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last_mouse_move_right).count() / 1000.0;
|
||||
const double elapsed_up = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last_mouse_move_up).count() / 1000.0;
|
||||
const double elapsed_down = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last_mouse_move_down).count() / 1000.0;
|
||||
const double elapsed_stick = std::chrono::duration_cast<std::chrono::microseconds>(now - m_stick_time).count() / 1000.0;
|
||||
|
||||
const double mouse_interval = 30.0;
|
||||
const double stick_interval = 10.0;
|
||||
|
||||
// roughly 1-2 frames to process the next mouse move
|
||||
if (elapsed_left > mouse_interval)
|
||||
{
|
||||
Key(mouse::move_left, false);
|
||||
m_last_mouse_move_left = now;
|
||||
}
|
||||
if (elapsed_right > mouse_interval)
|
||||
{
|
||||
Key(mouse::move_right, false);
|
||||
m_last_mouse_move_right = now;
|
||||
}
|
||||
if (elapsed_up > mouse_interval)
|
||||
{
|
||||
Key(mouse::move_up, false);
|
||||
m_last_mouse_move_up = now;
|
||||
}
|
||||
if (elapsed_down > mouse_interval)
|
||||
{
|
||||
Key(mouse::move_down, false);
|
||||
m_last_mouse_move_down = now;
|
||||
}
|
||||
|
||||
if (elapsed_stick > stick_interval)
|
||||
{
|
||||
for (int j = 0; j < static_cast<int>(bindings[i]->m_sticks.size()); j++)
|
||||
{
|
||||
const f32 stick_lerp_factor = (j < 2) ? m_l_stick_lerp_factor : m_r_stick_lerp_factor;
|
||||
|
||||
// we already applied the following values on keypress if we used factor 1
|
||||
if (stick_lerp_factor < 1.0f)
|
||||
{
|
||||
const f32 v0 = static_cast<f32>(bindings[i]->m_sticks[j].m_value);
|
||||
const f32 v1 = static_cast<f32>(m_stick_val[j]);
|
||||
|
||||
// linear interpolation from the current stick value v0 to the desired stick value v1
|
||||
f32 res = lerp(v0, v1, stick_lerp_factor);
|
||||
|
||||
// round to the correct direction to prevent sticky sticks on small factors
|
||||
res = (v0 <= v1) ? std::ceil(res) : std::floor(res);
|
||||
|
||||
bindings[i]->m_sticks[j].m_value = static_cast<u16>(res);
|
||||
}
|
||||
}
|
||||
|
||||
m_stick_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
rpcs3/Input/keyboard_pad_handler.h
Normal file
111
rpcs3/Input/keyboard_pad_handler.h
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
|
||||
#include <QWindow>
|
||||
#include <QKeyEvent>
|
||||
|
||||
enum mouse
|
||||
{
|
||||
move_left = 0x05555550,
|
||||
move_right = 0x05555551,
|
||||
move_up = 0x05555552,
|
||||
move_down = 0x05555553
|
||||
};
|
||||
|
||||
class keyboard_pad_handler final : public QObject, public PadHandlerBase
|
||||
{
|
||||
// Unique button names for the config files and our pad settings dialog
|
||||
const std::unordered_map<u32, std::string> mouse_list =
|
||||
{
|
||||
{ Qt::NoButton , "" },
|
||||
{ Qt::LeftButton , "Mouse Left" },
|
||||
{ Qt::RightButton , "Mouse Right" },
|
||||
{ Qt::MiddleButton , "Mouse Middle" },
|
||||
{ Qt::BackButton , "Mouse Back" },
|
||||
{ Qt::ForwardButton , "Mouse Fwd" },
|
||||
{ Qt::TaskButton , "Mouse Task" },
|
||||
{ Qt::ExtraButton4 , "Mouse 4" },
|
||||
{ Qt::ExtraButton5 , "Mouse 5" },
|
||||
{ Qt::ExtraButton6 , "Mouse 6" },
|
||||
{ Qt::ExtraButton7 , "Mouse 7" },
|
||||
{ Qt::ExtraButton8 , "Mouse 8" },
|
||||
{ Qt::ExtraButton9 , "Mouse 9" },
|
||||
{ Qt::ExtraButton10 , "Mouse 10" },
|
||||
{ Qt::ExtraButton11 , "Mouse 11" },
|
||||
{ Qt::ExtraButton12 , "Mouse 12" },
|
||||
{ Qt::ExtraButton13 , "Mouse 13" },
|
||||
{ Qt::ExtraButton14 , "Mouse 14" },
|
||||
{ Qt::ExtraButton15 , "Mouse 15" },
|
||||
{ Qt::ExtraButton16 , "Mouse 16" },
|
||||
{ Qt::ExtraButton17 , "Mouse 17" },
|
||||
{ Qt::ExtraButton18 , "Mouse 18" },
|
||||
{ Qt::ExtraButton19 , "Mouse 19" },
|
||||
{ Qt::ExtraButton20 , "Mouse 20" },
|
||||
{ Qt::ExtraButton21 , "Mouse 21" },
|
||||
{ Qt::ExtraButton22 , "Mouse 22" },
|
||||
{ Qt::ExtraButton23 , "Mouse 23" },
|
||||
{ Qt::ExtraButton24 , "Mouse 24" },
|
||||
|
||||
{ mouse::move_left , "Mouse MLeft" },
|
||||
{ mouse::move_right , "Mouse MRight" },
|
||||
{ mouse::move_up , "Mouse MUp" },
|
||||
{ mouse::move_down , "Mouse MDown" },
|
||||
};
|
||||
|
||||
public:
|
||||
bool Init() override;
|
||||
|
||||
keyboard_pad_handler();
|
||||
|
||||
void SetTargetWindow(QWindow* target);
|
||||
void processKeyEvent(QKeyEvent* event, bool pressed);
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void keyReleaseEvent(QKeyEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override;
|
||||
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
std::vector<std::string> ListDevices() override;
|
||||
void get_next_button_press(const std::string& /*padId*/, const std::function<void(u16, std::string, std::string, std::array<int, 6>)>& /*callback*/, const std::function<void(std::string)>& /*fail_callback*/, bool /*get_blacklist*/ = false, const std::vector<std::string>& /*buttons*/ = {}) override {};
|
||||
bool bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device) override;
|
||||
void ThreadProc() override;
|
||||
|
||||
std::string GetMouseName(const QMouseEvent* event);
|
||||
std::string GetMouseName(u32 button);
|
||||
QStringList GetKeyNames(const QKeyEvent* keyEvent);
|
||||
std::string GetKeyName(const QKeyEvent* keyEvent);
|
||||
std::string GetKeyName(const u32& keyCode);
|
||||
u32 GetKeyCode(const std::string& keyName);
|
||||
u32 GetKeyCode(const QString& keyName);
|
||||
|
||||
protected:
|
||||
void Key(const u32 code, bool pressed, u16 value = 255);
|
||||
int GetModifierCode(QKeyEvent* e);
|
||||
|
||||
private:
|
||||
QWindow* m_target = nullptr;
|
||||
std::vector<std::shared_ptr<Pad>> bindings;
|
||||
|
||||
// Stick Movements
|
||||
std::chrono::steady_clock::time_point m_stick_time;
|
||||
f32 m_l_stick_lerp_factor = 1.0f;
|
||||
f32 m_r_stick_lerp_factor = 1.0f;
|
||||
u8 m_stick_min[4] = { 0, 0, 0, 0 };
|
||||
u8 m_stick_max[4] = { 128, 128, 128, 128 };
|
||||
u8 m_stick_val[4] = { 128, 128, 128, 128 };
|
||||
|
||||
// Mouse Movements
|
||||
std::chrono::steady_clock::time_point m_last_mouse_move_left;
|
||||
std::chrono::steady_clock::time_point m_last_mouse_move_right;
|
||||
std::chrono::steady_clock::time_point m_last_mouse_move_up;
|
||||
std::chrono::steady_clock::time_point m_last_mouse_move_down;
|
||||
int m_deadzone_x = 60;
|
||||
int m_deadzone_y = 60;
|
||||
double m_multi_x = 2;
|
||||
double m_multi_y = 2.5;
|
||||
};
|
||||
509
rpcs3/Input/mm_joystick_handler.cpp
Normal file
509
rpcs3/Input/mm_joystick_handler.cpp
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
#ifdef _WIN32
|
||||
#include "mm_joystick_handler.h"
|
||||
|
||||
mm_joystick_handler::mm_joystick_handler() : PadHandlerBase(pad_handler::mm)
|
||||
{
|
||||
init_configs();
|
||||
|
||||
// Define border values
|
||||
thumb_min = 0;
|
||||
thumb_max = 255;
|
||||
trigger_min = 0;
|
||||
trigger_max = 255;
|
||||
vibration_min = 0;
|
||||
vibration_max = 65535;
|
||||
|
||||
// set capabilities
|
||||
b_has_config = true;
|
||||
b_has_rumble = true;
|
||||
b_has_deadzones = true;
|
||||
|
||||
m_name_string = "Joystick #";
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
}
|
||||
|
||||
mm_joystick_handler::~mm_joystick_handler()
|
||||
{
|
||||
}
|
||||
|
||||
void mm_joystick_handler::init_config(pad_config* cfg, const std::string& name)
|
||||
{
|
||||
// Set this profile's save location
|
||||
cfg->cfg_name = name;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = axis_list.at(mmjoy_axis::joy_x_neg);
|
||||
cfg->ls_down.def = axis_list.at(mmjoy_axis::joy_y_neg);
|
||||
cfg->ls_right.def = axis_list.at(mmjoy_axis::joy_x_pos);
|
||||
cfg->ls_up.def = axis_list.at(mmjoy_axis::joy_y_pos);
|
||||
cfg->rs_left.def = axis_list.at(mmjoy_axis::joy_z_neg);
|
||||
cfg->rs_down.def = axis_list.at(mmjoy_axis::joy_r_neg);
|
||||
cfg->rs_right.def = axis_list.at(mmjoy_axis::joy_z_pos);
|
||||
cfg->rs_up.def = axis_list.at(mmjoy_axis::joy_r_pos);
|
||||
cfg->start.def = button_list.at(JOY_BUTTON9);
|
||||
cfg->select.def = button_list.at(JOY_BUTTON10);
|
||||
cfg->ps.def = button_list.at(JOY_BUTTON17);
|
||||
cfg->square.def = button_list.at(JOY_BUTTON4);
|
||||
cfg->cross.def = button_list.at(JOY_BUTTON3);
|
||||
cfg->circle.def = button_list.at(JOY_BUTTON2);
|
||||
cfg->triangle.def = button_list.at(JOY_BUTTON1);
|
||||
cfg->left.def = pov_list.at(JOY_POVLEFT);
|
||||
cfg->down.def = pov_list.at(JOY_POVBACKWARD);
|
||||
cfg->right.def = pov_list.at(JOY_POVRIGHT);
|
||||
cfg->up.def = pov_list.at(JOY_POVFORWARD);
|
||||
cfg->r1.def = button_list.at(JOY_BUTTON8);
|
||||
cfg->r2.def = button_list.at(JOY_BUTTON6);
|
||||
cfg->r3.def = button_list.at(JOY_BUTTON12);
|
||||
cfg->l1.def = button_list.at(JOY_BUTTON7);
|
||||
cfg->l2.def = button_list.at(JOY_BUTTON5);
|
||||
cfg->l3.def = button_list.at(JOY_BUTTON11);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = 0; // between 0 and 255
|
||||
cfg->rstickdeadzone.def = 0; // between 0 and 255
|
||||
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
||||
cfg->padsquircling.def = 8000;
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::Init()
|
||||
{
|
||||
if (is_init)
|
||||
return true;
|
||||
|
||||
m_devices.clear();
|
||||
u32 supported_joysticks = joyGetNumDevs();
|
||||
|
||||
if (supported_joysticks <= 0)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "mmjoy: Driver doesn't support Joysticks");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_NOTICE(GENERAL, "mmjoy: Driver supports %u joysticks", supported_joysticks);
|
||||
|
||||
for (u32 i = 0; i < supported_joysticks; i++)
|
||||
{
|
||||
MMJOYDevice dev;
|
||||
|
||||
if (GetMMJOYDevice(i, &dev) == false)
|
||||
continue;
|
||||
|
||||
m_devices.emplace(i, dev);
|
||||
}
|
||||
|
||||
is_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> mm_joystick_handler::ListDevices()
|
||||
{
|
||||
std::vector<std::string> devices;
|
||||
|
||||
if (!Init())
|
||||
return devices;
|
||||
|
||||
for (auto dev : m_devices)
|
||||
{
|
||||
devices.emplace_back(dev.second.device_name);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
std::array<u32, PadHandlerBase::button::button_count> mm_joystick_handler::get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const pad_config* profile)
|
||||
{
|
||||
std::array<u32, button::button_count> mapping{ 0 };
|
||||
|
||||
auto joy_device = std::static_pointer_cast<MMJOYDevice>(device);
|
||||
if (!joy_device)
|
||||
return mapping;
|
||||
|
||||
auto find_key = [=](const cfg::string& name)
|
||||
{
|
||||
long key = FindKeyCode(button_list, name, false);
|
||||
if (key < 0)
|
||||
key = FindKeyCode(pov_list, name, false);
|
||||
if (key < 0)
|
||||
key = FindKeyCode(axis_list, name);
|
||||
return static_cast<u64>(key);
|
||||
};
|
||||
|
||||
joy_device->trigger_left = find_key(profile->l2);
|
||||
joy_device->trigger_right = find_key(profile->r2);
|
||||
joy_device->axis_left[0] = find_key(profile->ls_left);
|
||||
joy_device->axis_left[1] = find_key(profile->ls_right);
|
||||
joy_device->axis_left[2] = find_key(profile->ls_down);
|
||||
joy_device->axis_left[3] = find_key(profile->ls_up);
|
||||
joy_device->axis_right[0] = find_key(profile->rs_left);
|
||||
joy_device->axis_right[1] = find_key(profile->rs_right);
|
||||
joy_device->axis_right[2] = find_key(profile->rs_down);
|
||||
joy_device->axis_right[3] = find_key(profile->rs_up);
|
||||
|
||||
mapping[button::up] = static_cast<u32>(find_key(profile->up));
|
||||
mapping[button::down] = static_cast<u32>(find_key(profile->down));
|
||||
mapping[button::left] = static_cast<u32>(find_key(profile->left));
|
||||
mapping[button::right] = static_cast<u32>(find_key(profile->right));
|
||||
mapping[button::cross] = static_cast<u32>(find_key(profile->cross));
|
||||
mapping[button::square] = static_cast<u32>(find_key(profile->square));
|
||||
mapping[button::circle] = static_cast<u32>(find_key(profile->circle));
|
||||
mapping[button::triangle] = static_cast<u32>(find_key(profile->triangle));
|
||||
mapping[button::l1] = static_cast<u32>(find_key(profile->l1));
|
||||
mapping[button::l2] = static_cast<u32>(joy_device->trigger_left);
|
||||
mapping[button::l3] = static_cast<u32>(find_key(profile->l3));
|
||||
mapping[button::r1] = static_cast<u32>(find_key(profile->r1));
|
||||
mapping[button::r2] = static_cast<u32>(joy_device->trigger_right);
|
||||
mapping[button::r3] = static_cast<u32>(find_key(profile->r3));
|
||||
mapping[button::start] = static_cast<u32>(find_key(profile->start));
|
||||
mapping[button::select] = static_cast<u32>(find_key(profile->select));
|
||||
mapping[button::ps] = static_cast<u32>(find_key(profile->ps));
|
||||
mapping[button::ls_left] = static_cast<u32>(joy_device->axis_left[0]);
|
||||
mapping[button::ls_right] = static_cast<u32>(joy_device->axis_left[1]);
|
||||
mapping[button::ls_down] = static_cast<u32>(joy_device->axis_left[2]);
|
||||
mapping[button::ls_up] = static_cast<u32>(joy_device->axis_left[3]);
|
||||
mapping[button::rs_left] = static_cast<u32>(joy_device->axis_right[0]);
|
||||
mapping[button::rs_right] = static_cast<u32>(joy_device->axis_right[1]);
|
||||
mapping[button::rs_down] = static_cast<u32>(joy_device->axis_right[2]);
|
||||
mapping[button::rs_up] = static_cast<u32>(joy_device->axis_right[3]);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
void mm_joystick_handler::get_next_button_press(const std::string& padId, const std::function<void(u16, std::string, std::string, std::array<int, 6>)>& callback, const std::function<void(std::string)>& fail_callback, bool get_blacklist, const std::vector<std::string>& buttons)
|
||||
{
|
||||
if (get_blacklist)
|
||||
blacklist.clear();
|
||||
|
||||
if (!Init())
|
||||
return fail_callback(padId);
|
||||
|
||||
static std::string cur_pad = "";
|
||||
static int id = -1;
|
||||
|
||||
if (cur_pad != padId)
|
||||
{
|
||||
cur_pad = padId;
|
||||
id = GetIDByName(padId);
|
||||
if (id < 0)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "MMJOY get_next_button_press for device [%s] failed with id = %d", padId, id);
|
||||
return fail_callback(padId);
|
||||
}
|
||||
}
|
||||
|
||||
JOYINFOEX js_info;
|
||||
JOYCAPS js_caps;
|
||||
js_info.dwSize = sizeof(js_info);
|
||||
js_info.dwFlags = JOY_RETURNALL;
|
||||
joyGetDevCaps(id, &js_caps, sizeof(js_caps));
|
||||
|
||||
MMRESULT status = joyGetPosEx(id, &js_info);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case JOYERR_UNPLUGGED:
|
||||
{
|
||||
return fail_callback(padId);
|
||||
}
|
||||
case JOYERR_NOERROR:
|
||||
{
|
||||
auto data = GetButtonValues(js_info, js_caps);
|
||||
|
||||
// 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 : axis_list)
|
||||
{
|
||||
u64 keycode = button.first;
|
||||
u16 value = data[keycode];
|
||||
|
||||
if (!get_blacklist && std::find(blacklist.begin(), blacklist.end(), keycode) != blacklist.end())
|
||||
continue;
|
||||
|
||||
if (value > m_thumb_threshold)
|
||||
{
|
||||
if (get_blacklist)
|
||||
{
|
||||
blacklist.emplace_back(keycode);
|
||||
LOG_ERROR(HLE, "MMJOY Calibration: Added axis [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value);
|
||||
}
|
||||
else if (value > pressed_button.first)
|
||||
pressed_button = { value, button.second };
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& button : pov_list)
|
||||
{
|
||||
u64 keycode = button.first;
|
||||
u16 value = data[keycode];
|
||||
|
||||
if (!get_blacklist && std::find(blacklist.begin(), blacklist.end(), keycode) != blacklist.end())
|
||||
continue;
|
||||
|
||||
if (value > 0)
|
||||
{
|
||||
if (get_blacklist)
|
||||
{
|
||||
blacklist.emplace_back(keycode);
|
||||
LOG_ERROR(HLE, "MMJOY Calibration: Added pov [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value);
|
||||
}
|
||||
else if (value > pressed_button.first)
|
||||
pressed_button = { value, button.second };
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& button : button_list)
|
||||
{
|
||||
u64 keycode = button.first;
|
||||
u16 value = data[keycode];
|
||||
|
||||
if (!get_blacklist && std::find(blacklist.begin(), blacklist.end(), keycode) != blacklist.end())
|
||||
continue;
|
||||
|
||||
if (value > 0)
|
||||
{
|
||||
if (get_blacklist)
|
||||
{
|
||||
blacklist.emplace_back(keycode);
|
||||
LOG_ERROR(HLE, "MMJOY Calibration: Added button [ %d = %s ] to blacklist. Value = %d", keycode, button.second, value);
|
||||
}
|
||||
else if (value > pressed_button.first)
|
||||
pressed_button = { value, button.second };
|
||||
}
|
||||
}
|
||||
|
||||
if (get_blacklist)
|
||||
{
|
||||
if (blacklist.empty())
|
||||
LOG_SUCCESS(HLE, "MMJOY Calibration: Blacklist is clear. No input spam detected");
|
||||
return;
|
||||
}
|
||||
|
||||
auto find_key = [=](const std::string& name)
|
||||
{
|
||||
long key = FindKeyCodeByString(axis_list, name, false);
|
||||
if (key < 0)
|
||||
key = FindKeyCodeByString(pov_list, name, false);
|
||||
if (key < 0)
|
||||
key = FindKeyCodeByString(button_list, name);
|
||||
return static_cast<u64>(key);
|
||||
};
|
||||
|
||||
std::array<int, 6> preview_values = { 0, 0, 0, 0, 0, 0 };
|
||||
if (buttons.size() == 10)
|
||||
{
|
||||
preview_values[0] = data[find_key(buttons[0])];
|
||||
preview_values[1] = data[find_key(buttons[1])];
|
||||
preview_values[2] = data[find_key(buttons[3])] - data[find_key(buttons[2])];
|
||||
preview_values[3] = data[find_key(buttons[5])] - data[find_key(buttons[4])];
|
||||
preview_values[4] = data[find_key(buttons[7])] - data[find_key(buttons[6])];
|
||||
preview_values[5] = data[find_key(buttons[9])] - data[find_key(buttons[8])];
|
||||
}
|
||||
|
||||
if (pressed_button.first > 0)
|
||||
return callback(pressed_button.first, pressed_button.second, padId, preview_values);
|
||||
else
|
||||
return callback(0, "", padId, preview_values);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> mm_joystick_handler::GetButtonValues(const JOYINFOEX& js_info, const JOYCAPS& js_caps)
|
||||
{
|
||||
std::unordered_map<u64, u16> button_values;
|
||||
|
||||
for (auto entry : button_list)
|
||||
{
|
||||
button_values.emplace(entry.first, js_info.dwButtons & entry.first ? 255 : 0);
|
||||
}
|
||||
|
||||
if (js_caps.wCaps & JOYCAPS_HASPOV)
|
||||
{
|
||||
if (js_caps.wCaps & JOYCAPS_POVCTS)
|
||||
{
|
||||
if (js_info.dwPOV == JOY_POVCENTERED)
|
||||
{
|
||||
button_values.emplace(JOY_POVFORWARD, 0);
|
||||
button_values.emplace(JOY_POVRIGHT, 0);
|
||||
button_values.emplace(JOY_POVBACKWARD, 0);
|
||||
button_values.emplace(JOY_POVLEFT, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto emplacePOVs = [&](float val, u64 pov_neg, u64 pov_pos)
|
||||
{
|
||||
if (val < 0)
|
||||
{
|
||||
button_values.emplace(pov_neg, static_cast<u16>(std::abs(val)));
|
||||
button_values.emplace(pov_pos, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
button_values.emplace(pov_neg, 0);
|
||||
button_values.emplace(pov_pos, static_cast<u16>(val));
|
||||
}
|
||||
};
|
||||
|
||||
float rad = static_cast<float>(js_info.dwPOV / 100 * acos(-1) / 180);
|
||||
emplacePOVs(cosf(rad) * 255.0f, JOY_POVBACKWARD, JOY_POVFORWARD);
|
||||
emplacePOVs(sinf(rad) * 255.0f, JOY_POVLEFT, JOY_POVRIGHT);
|
||||
}
|
||||
}
|
||||
else if (js_caps.wCaps & JOYCAPS_POV4DIR)
|
||||
{
|
||||
int val = static_cast<int>(js_info.dwPOV);
|
||||
|
||||
auto emplacePOV = [&button_values, &val](int pov)
|
||||
{
|
||||
int cw = pov + 4500, ccw = pov - 4500;
|
||||
bool pressed = (val == pov) || (val == cw) || (ccw < 0 ? val == 36000 - std::abs(ccw) : val == ccw);
|
||||
button_values.emplace(pov, pressed ? 255 : 0);
|
||||
};
|
||||
|
||||
emplacePOV(JOY_POVFORWARD);
|
||||
emplacePOV(JOY_POVRIGHT);
|
||||
emplacePOV(JOY_POVBACKWARD);
|
||||
emplacePOV(JOY_POVLEFT);
|
||||
}
|
||||
}
|
||||
|
||||
auto add_axis_value = [&](DWORD axis, UINT min, UINT max, u64 pos, u64 neg)
|
||||
{
|
||||
float val = ScaleStickInput2(axis, min, max);
|
||||
if (val < 0)
|
||||
{
|
||||
button_values.emplace(pos, 0);
|
||||
button_values.emplace(neg, static_cast<u16>(std::abs(val)));
|
||||
}
|
||||
else
|
||||
{
|
||||
button_values.emplace(pos, static_cast<u16>(val));
|
||||
button_values.emplace(neg, 0);
|
||||
}
|
||||
};
|
||||
|
||||
add_axis_value(js_info.dwXpos, js_caps.wXmin, js_caps.wXmax, mmjoy_axis::joy_x_pos, mmjoy_axis::joy_x_neg);
|
||||
add_axis_value(js_info.dwYpos, js_caps.wYmin, js_caps.wYmax, mmjoy_axis::joy_y_pos, mmjoy_axis::joy_y_neg);
|
||||
|
||||
if (js_caps.wCaps & JOYCAPS_HASZ)
|
||||
add_axis_value(js_info.dwZpos, js_caps.wZmin, js_caps.wZmax, mmjoy_axis::joy_z_pos, mmjoy_axis::joy_z_neg);
|
||||
|
||||
if (js_caps.wCaps & JOYCAPS_HASR)
|
||||
add_axis_value(js_info.dwRpos, js_caps.wRmin, js_caps.wRmax, mmjoy_axis::joy_r_pos, mmjoy_axis::joy_r_neg);
|
||||
|
||||
if (js_caps.wCaps & JOYCAPS_HASU)
|
||||
add_axis_value(js_info.dwUpos, js_caps.wUmin, js_caps.wUmax, mmjoy_axis::joy_u_pos, mmjoy_axis::joy_u_neg);
|
||||
|
||||
if (js_caps.wCaps & JOYCAPS_HASV)
|
||||
add_axis_value(js_info.dwVpos, js_caps.wVmin, js_caps.wVmax, mmjoy_axis::joy_v_pos, mmjoy_axis::joy_v_neg);
|
||||
|
||||
return button_values;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> mm_joystick_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<MMJOYDevice>(device);
|
||||
if (!dev)
|
||||
return std::unordered_map<u64, u16>();
|
||||
return GetButtonValues(dev->device_info, dev->device_caps);
|
||||
}
|
||||
|
||||
int mm_joystick_handler::GetIDByName(const std::string& name)
|
||||
{
|
||||
for (auto dev : m_devices)
|
||||
{
|
||||
if (dev.second.device_name == name)
|
||||
return dev.first;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::GetMMJOYDevice(int index, MMJOYDevice* dev)
|
||||
{
|
||||
if (!dev)
|
||||
return false;
|
||||
|
||||
JOYINFOEX js_info;
|
||||
JOYCAPS js_caps;
|
||||
js_info.dwSize = sizeof(js_info);
|
||||
js_info.dwFlags = JOY_RETURNALL;
|
||||
joyGetDevCaps(index, &js_caps, sizeof(js_caps));
|
||||
|
||||
dev->device_status = joyGetPosEx(index, &js_info);
|
||||
if (dev->device_status != JOYERR_NOERROR)
|
||||
return false;
|
||||
|
||||
char drv[32];
|
||||
wcstombs(drv, js_caps.szPname, 31);
|
||||
|
||||
LOG_NOTICE(GENERAL, "Joystick nr.%d found. Driver: %s", index, drv);
|
||||
|
||||
dev->device_id = index;
|
||||
dev->device_name = m_name_string + std::to_string(index + 1); // Controllers 1-n in GUI
|
||||
dev->device_info = js_info;
|
||||
dev->device_caps = js_caps;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<PadDevice> mm_joystick_handler::get_device(const std::string& device)
|
||||
{
|
||||
if (!Init())
|
||||
return false;
|
||||
|
||||
int id = GetIDByName(device);
|
||||
if (id < 0)
|
||||
return false;
|
||||
|
||||
std::shared_ptr<MMJOYDevice> joy_device = std::make_shared<MMJOYDevice>(m_devices.at(id));
|
||||
return joy_device;
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::get_is_left_trigger(u64 keyCode)
|
||||
{
|
||||
return m_dev && m_dev->trigger_left == keyCode;
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::get_is_right_trigger(u64 keyCode)
|
||||
{
|
||||
return m_dev && m_dev->trigger_right == keyCode;
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::get_is_left_stick(u64 keyCode)
|
||||
{
|
||||
return m_dev && std::find(m_dev->axis_left.begin(), m_dev->axis_left.end(), keyCode) != m_dev->axis_left.end();
|
||||
}
|
||||
|
||||
bool mm_joystick_handler::get_is_right_stick(u64 keyCode)
|
||||
{
|
||||
return m_dev && std::find(m_dev->axis_right.begin(), m_dev->axis_right.end(), keyCode) != m_dev->axis_right.end();
|
||||
}
|
||||
|
||||
PadHandlerBase::connection mm_joystick_handler::update_connection(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<MMJOYDevice>(device);
|
||||
if (!dev)
|
||||
return connection::disconnected;
|
||||
|
||||
const auto old_status = dev->device_status;
|
||||
dev->device_status = joyGetPosEx(dev->device_id, &dev->device_info);
|
||||
|
||||
if (dev->device_status == JOYERR_NOERROR && (old_status == JOYERR_NOERROR || GetMMJOYDevice(dev->device_id, dev.get())))
|
||||
{
|
||||
return connection::connected;
|
||||
}
|
||||
return connection::disconnected;
|
||||
}
|
||||
|
||||
#endif
|
||||
131
rpcs3/Input/mm_joystick_handler.h
Normal file
131
rpcs3/Input/mm_joystick_handler.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#include <Windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include "Utilities/Config.h"
|
||||
|
||||
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-" },
|
||||
};
|
||||
|
||||
struct MMJOYDevice : public PadDevice
|
||||
{
|
||||
u32 device_id{ 0 };
|
||||
std::string device_name{ "" };
|
||||
JOYINFOEX device_info{};
|
||||
JOYCAPS device_caps{};
|
||||
MMRESULT device_status = JOYERR_UNPLUGGED;
|
||||
u64 trigger_left = 0;
|
||||
u64 trigger_right = 0;
|
||||
std::vector<u64> axis_left = { 0,0,0,0 };
|
||||
std::vector<u64> axis_right = { 0,0,0,0 };
|
||||
};
|
||||
|
||||
public:
|
||||
mm_joystick_handler();
|
||||
~mm_joystick_handler();
|
||||
|
||||
bool Init() override;
|
||||
|
||||
std::vector<std::string> ListDevices() override;
|
||||
void get_next_button_press(const std::string& padId, const std::function<void(u16, std::string, std::string, std::array<int, 6>)>& callback, const std::function<void(std::string)>& fail_callback, bool get_blacklist = false, const std::vector<std::string>& buttons = {}) override;
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<u64, u16> GetButtonValues(const JOYINFOEX& js_info, const JOYCAPS& js_caps);
|
||||
int GetIDByName(const std::string& name);
|
||||
bool GetMMJOYDevice(int index, MMJOYDevice* dev);
|
||||
|
||||
bool is_init = false;
|
||||
|
||||
std::vector<u64> blacklist;
|
||||
std::shared_ptr<MMJOYDevice> m_dev;
|
||||
std::unordered_map<int, MMJOYDevice> m_devices;
|
||||
|
||||
std::array<u32, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const pad_config* profile) override;
|
||||
std::shared_ptr<PadDevice> get_device(const std::string& device) override;
|
||||
bool get_is_left_trigger(u64 keyCode) override;
|
||||
bool get_is_right_trigger(u64 keyCode) override;
|
||||
bool get_is_left_stick(u64 keyCode) override;
|
||||
bool get_is_right_stick(u64 keyCode) override;
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
|
||||
};
|
||||
261
rpcs3/Input/pad_thread.cpp
Normal file
261
rpcs3/Input/pad_thread.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#include "pad_thread.h"
|
||||
#include "ds3_pad_handler.h"
|
||||
#include "ds4_pad_handler.h"
|
||||
#ifdef _WIN32
|
||||
#include "xinput_pad_handler.h"
|
||||
#include "mm_joystick_handler.h"
|
||||
#elif HAVE_LIBEVDEV
|
||||
#include "evdev_joystick_handler.h"
|
||||
#endif
|
||||
#include "keyboard_pad_handler.h"
|
||||
#include "Emu/Io/Null/NullPadHandler.h"
|
||||
|
||||
namespace pad
|
||||
{
|
||||
atomic_t<pad_thread*> g_current = nullptr;
|
||||
std::recursive_mutex g_pad_mutex;
|
||||
std::string g_title_id;
|
||||
}
|
||||
|
||||
struct pad_setting
|
||||
{
|
||||
u32 port_status;
|
||||
u32 device_capability;
|
||||
u32 device_type;
|
||||
};
|
||||
|
||||
pad_thread::pad_thread(void *_curthread, void *_curwindow, std::string_view title_id) : curthread(_curthread), curwindow(_curwindow)
|
||||
{
|
||||
pad::g_title_id = title_id;
|
||||
Init();
|
||||
|
||||
thread = std::make_shared<std::thread>(&pad_thread::ThreadFunc, this);
|
||||
pad::g_current = this;
|
||||
}
|
||||
|
||||
pad_thread::~pad_thread()
|
||||
{
|
||||
pad::g_current = nullptr;
|
||||
active = false;
|
||||
thread->join();
|
||||
|
||||
handlers.clear();
|
||||
}
|
||||
|
||||
void pad_thread::Init()
|
||||
{
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
|
||||
// Cache old settings if possible
|
||||
std::vector<pad_setting> pad_settings;
|
||||
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
|
||||
{
|
||||
if (!m_pads[i])
|
||||
{
|
||||
pad_settings.push_back({ CELL_PAD_STATUS_DISCONNECTED, CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_ACTUATOR, CELL_PAD_DEV_TYPE_STANDARD });
|
||||
}
|
||||
else
|
||||
{
|
||||
pad_settings.push_back({ m_pads[i]->m_port_status, m_pads[i]->m_device_capability, m_pads[i]->m_device_type });
|
||||
}
|
||||
}
|
||||
|
||||
const PadInfo pad_info(m_info);
|
||||
std::memset(&m_info, 0, sizeof(m_info));
|
||||
m_info.now_connect = 0;
|
||||
m_info.system_info |= pad_info.system_info;
|
||||
m_info.ignore_input = pad_info.ignore_input;
|
||||
|
||||
handlers.clear();
|
||||
|
||||
g_cfg_input.load(pad::g_title_id);
|
||||
|
||||
std::shared_ptr<keyboard_pad_handler> keyptr;
|
||||
|
||||
// Always have a Null Pad Handler
|
||||
std::shared_ptr<NullPadHandler> nullpad = std::make_shared<NullPadHandler>();
|
||||
handlers.emplace(pad_handler::null, nullpad);
|
||||
|
||||
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
|
||||
{
|
||||
std::shared_ptr<PadHandlerBase> cur_pad_handler;
|
||||
|
||||
const auto &handler_type = g_cfg_input.player[i]->handler;
|
||||
|
||||
if (handlers.count(handler_type) != 0)
|
||||
{
|
||||
cur_pad_handler = handlers[handler_type];
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (handler_type)
|
||||
{
|
||||
case pad_handler::keyboard:
|
||||
keyptr = std::make_shared<keyboard_pad_handler>();
|
||||
keyptr->moveToThread(static_cast<QThread*>(curthread));
|
||||
keyptr->SetTargetWindow(static_cast<QWindow*>(curwindow));
|
||||
cur_pad_handler = keyptr;
|
||||
break;
|
||||
case pad_handler::ds3:
|
||||
cur_pad_handler = std::make_shared<ds3_pad_handler>();
|
||||
break;
|
||||
case pad_handler::ds4:
|
||||
cur_pad_handler = std::make_shared<ds4_pad_handler>();
|
||||
break;
|
||||
#ifdef _WIN32
|
||||
case pad_handler::xinput:
|
||||
cur_pad_handler = std::make_shared<xinput_pad_handler>();
|
||||
break;
|
||||
case pad_handler::mm:
|
||||
cur_pad_handler = std::make_shared<mm_joystick_handler>();
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
case pad_handler::evdev:
|
||||
cur_pad_handler = std::make_shared<evdev_joystick_handler>();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
handlers.emplace(handler_type, cur_pad_handler);
|
||||
}
|
||||
cur_pad_handler->Init();
|
||||
|
||||
m_pads[i] = std::make_shared<Pad>(CELL_PAD_STATUS_DISCONNECTED, pad_settings[i].device_capability, pad_settings[i].device_type);
|
||||
|
||||
if (cur_pad_handler->bindPadToDevice(m_pads[i], g_cfg_input.player[i]->device.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", g_cfg_input.player[i]->device.to_string(), handler_type.to_string());
|
||||
nullpad->bindPadToDevice(m_pads[i], g_cfg_input.player[i]->device.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pad_thread::SetRumble(const u32 pad, u8 largeMotor, bool smallMotor)
|
||||
{
|
||||
if (pad > m_pads.size())
|
||||
return;
|
||||
|
||||
if (m_pads[pad]->m_vibrateMotors.size() >= 2)
|
||||
{
|
||||
m_pads[pad]->m_vibrateMotors[0].m_value = largeMotor;
|
||||
m_pads[pad]->m_vibrateMotors[1].m_value = smallMotor ? 255 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void pad_thread::Reset(std::string_view title_id)
|
||||
{
|
||||
pad::g_title_id = title_id;
|
||||
reset = active.load();
|
||||
}
|
||||
|
||||
void pad_thread::SetEnabled(bool enabled)
|
||||
{
|
||||
is_enabled = enabled;
|
||||
}
|
||||
|
||||
void pad_thread::SetIntercepted(bool intercepted)
|
||||
{
|
||||
if (intercepted)
|
||||
{
|
||||
m_info.system_info |= CELL_PAD_INFO_INTERCEPTED;
|
||||
m_info.ignore_input = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_info.system_info &= ~CELL_PAD_INFO_INTERCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
void pad_thread::ThreadFunc()
|
||||
{
|
||||
active = true;
|
||||
while (active)
|
||||
{
|
||||
if (!is_enabled)
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reset && reset.exchange(false))
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
u32 connected_devices = 0;
|
||||
|
||||
for (auto& cur_pad_handler : handlers)
|
||||
{
|
||||
cur_pad_handler.second->ThreadProc();
|
||||
connected_devices += cur_pad_handler.second->connected_devices;
|
||||
}
|
||||
|
||||
m_info.now_connect = connected_devices + num_ldd_pad;
|
||||
|
||||
// The following section is only reached when a dialog was closed and the pads are still intercepted.
|
||||
// As long as any of the listed buttons is pressed, cellPadGetData will ignore all input (needed for Hotline Miami).
|
||||
// ignore_input was added because if we keep the pads intercepted, then some games will enter the menu due to unexpected system interception (tested with Ninja Gaiden Sigma).
|
||||
if (!(m_info.system_info & CELL_PAD_INFO_INTERCEPTED) && m_info.ignore_input)
|
||||
{
|
||||
bool any_button_pressed = false;
|
||||
|
||||
for (const auto& pad : m_pads)
|
||||
{
|
||||
if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)
|
||||
{
|
||||
for (const auto& button : pad->m_buttons)
|
||||
{
|
||||
if (button.m_pressed && (
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE ||
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_START ||
|
||||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT))
|
||||
{
|
||||
any_button_pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_button_pressed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_button_pressed)
|
||||
{
|
||||
m_info.ignore_input = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
s32 pad_thread::AddLddPad()
|
||||
{
|
||||
// Look for first null pad
|
||||
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++)
|
||||
{
|
||||
if (g_cfg_input.player[i]->handler == pad_handler::null)
|
||||
{
|
||||
m_pads[i]->ldd = true;
|
||||
num_ldd_pad++;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pad_thread::UnregisterLddPad(u32 handle)
|
||||
{
|
||||
m_pads[handle]->ldd = false;
|
||||
num_ldd_pad--;
|
||||
}
|
||||
72
rpcs3/Input/pad_thread.h
Normal file
72
rpcs3/Input/pad_thread.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include "../Utilities/types.h"
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
|
||||
struct PadInfo
|
||||
{
|
||||
u32 now_connect;
|
||||
u32 system_info;
|
||||
bool ignore_input;
|
||||
};
|
||||
|
||||
class pad_thread
|
||||
{
|
||||
public:
|
||||
pad_thread(void* _curthread, void* _curwindow, std::string_view title_id); // void * instead of QThread * and QWindow * because of include in emucore
|
||||
~pad_thread();
|
||||
|
||||
PadInfo& GetInfo() { return m_info; }
|
||||
auto& GetPads() { return m_pads; }
|
||||
void SetRumble(const u32 pad, u8 largeMotor, bool smallMotor);
|
||||
void Init();
|
||||
void Reset(std::string_view title_id);
|
||||
void SetEnabled(bool enabled);
|
||||
void SetIntercepted(bool intercepted);
|
||||
|
||||
s32 AddLddPad();
|
||||
void UnregisterLddPad(u32 handle);
|
||||
|
||||
protected:
|
||||
void ThreadFunc();
|
||||
|
||||
// List of all handlers
|
||||
std::map<pad_handler, std::shared_ptr<PadHandlerBase>> handlers;
|
||||
|
||||
// Used for pad_handler::keyboard
|
||||
void *curthread;
|
||||
void *curwindow;
|
||||
|
||||
PadInfo m_info{ 0, 0, false };
|
||||
std::array<std::shared_ptr<Pad>, CELL_PAD_MAX_PORT_NUM> m_pads;
|
||||
|
||||
atomic_t<bool> active{ false };
|
||||
atomic_t<bool> reset{ false };
|
||||
atomic_t<bool> is_enabled{ true };
|
||||
std::shared_ptr<std::thread> thread;
|
||||
|
||||
u32 num_ldd_pad = 0;
|
||||
};
|
||||
|
||||
namespace pad
|
||||
{
|
||||
extern atomic_t<pad_thread*> g_current;
|
||||
extern std::recursive_mutex g_pad_mutex;
|
||||
extern std::string g_title_id;
|
||||
|
||||
static inline class pad_thread* get_current_handler()
|
||||
{
|
||||
return verify(HERE, g_current.load());
|
||||
}
|
||||
|
||||
static inline void SetIntercepted(bool intercepted)
|
||||
{
|
||||
std::lock_guard lock(g_pad_mutex);
|
||||
const auto handler = get_current_handler();
|
||||
handler->SetIntercepted(intercepted);
|
||||
}
|
||||
}
|
||||
490
rpcs3/Input/xinput_pad_handler.cpp
Normal file
490
rpcs3/Input/xinput_pad_handler.cpp
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include "xinput_pad_handler.h"
|
||||
|
||||
namespace XINPUT_INFO
|
||||
{
|
||||
const DWORD GUIDE_BUTTON = 0x0400;
|
||||
const LPCWSTR LIBRARY_FILENAMES[] = {
|
||||
L"xinput1_3.dll", // Prioritizing 1_3 because of SCP
|
||||
L"xinput1_4.dll",
|
||||
L"xinput9_1_0.dll"
|
||||
};
|
||||
} // namespace XINPUT_INFO
|
||||
|
||||
xinput_pad_handler::xinput_pad_handler() : PadHandlerBase(pad_handler::xinput)
|
||||
{
|
||||
// Unique names for the config files and our pad settings dialog
|
||||
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-" }
|
||||
};
|
||||
|
||||
init_configs();
|
||||
|
||||
// Define border values
|
||||
thumb_min = -32768;
|
||||
thumb_max = 32767;
|
||||
trigger_min = 0;
|
||||
trigger_max = 255;
|
||||
vibration_min = 0;
|
||||
vibration_max = 65535;
|
||||
|
||||
// set capabilities
|
||||
b_has_config = true;
|
||||
b_has_rumble = true;
|
||||
b_has_deadzones = true;
|
||||
|
||||
m_name_string = "XInput Pad #";
|
||||
m_max_devices = XUSER_MAX_COUNT;
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
}
|
||||
|
||||
xinput_pad_handler::~xinput_pad_handler()
|
||||
{
|
||||
if (library)
|
||||
{
|
||||
FreeLibrary(library);
|
||||
library = nullptr;
|
||||
xinputGetExtended = nullptr;
|
||||
xinputGetState = nullptr;
|
||||
xinputSetState = nullptr;
|
||||
xinputGetBatteryInformation = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void xinput_pad_handler::init_config(pad_config* cfg, const std::string& name)
|
||||
{
|
||||
// Set this profile's save location
|
||||
cfg->cfg_name = name;
|
||||
|
||||
// Set default button mapping
|
||||
cfg->ls_left.def = button_list.at(XInputKeyCodes::LSXNeg);
|
||||
cfg->ls_down.def = button_list.at(XInputKeyCodes::LSYNeg);
|
||||
cfg->ls_right.def = button_list.at(XInputKeyCodes::LSXPos);
|
||||
cfg->ls_up.def = button_list.at(XInputKeyCodes::LSYPos);
|
||||
cfg->rs_left.def = button_list.at(XInputKeyCodes::RSXNeg);
|
||||
cfg->rs_down.def = button_list.at(XInputKeyCodes::RSYNeg);
|
||||
cfg->rs_right.def = button_list.at(XInputKeyCodes::RSXPos);
|
||||
cfg->rs_up.def = button_list.at(XInputKeyCodes::RSYPos);
|
||||
cfg->start.def = button_list.at(XInputKeyCodes::Start);
|
||||
cfg->select.def = button_list.at(XInputKeyCodes::Back);
|
||||
cfg->ps.def = button_list.at(XInputKeyCodes::Guide);
|
||||
cfg->square.def = button_list.at(XInputKeyCodes::X);
|
||||
cfg->cross.def = button_list.at(XInputKeyCodes::A);
|
||||
cfg->circle.def = button_list.at(XInputKeyCodes::B);
|
||||
cfg->triangle.def = button_list.at(XInputKeyCodes::Y);
|
||||
cfg->left.def = button_list.at(XInputKeyCodes::Left);
|
||||
cfg->down.def = button_list.at(XInputKeyCodes::Down);
|
||||
cfg->right.def = button_list.at(XInputKeyCodes::Right);
|
||||
cfg->up.def = button_list.at(XInputKeyCodes::Up);
|
||||
cfg->r1.def = button_list.at(XInputKeyCodes::RB);
|
||||
cfg->r2.def = button_list.at(XInputKeyCodes::RT);
|
||||
cfg->r3.def = button_list.at(XInputKeyCodes::RS);
|
||||
cfg->l1.def = button_list.at(XInputKeyCodes::LB);
|
||||
cfg->l2.def = button_list.at(XInputKeyCodes::LT);
|
||||
cfg->l3.def = button_list.at(XInputKeyCodes::LS);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE; // between 0 and 32767
|
||||
cfg->rstickdeadzone.def = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE; // between 0 and 32767
|
||||
cfg->ltriggerthreshold.def = XINPUT_GAMEPAD_TRIGGER_THRESHOLD; // between 0 and 255
|
||||
cfg->rtriggerthreshold.def = XINPUT_GAMEPAD_TRIGGER_THRESHOLD; // between 0 and 255
|
||||
cfg->padsquircling.def = 8000;
|
||||
|
||||
// apply defaults
|
||||
cfg->from_default();
|
||||
}
|
||||
|
||||
void xinput_pad_handler::SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32/* r*/, s32/* g*/, s32/* b*/)
|
||||
{
|
||||
int device_number = GetDeviceNumber(padId);
|
||||
if (device_number < 0)
|
||||
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)(static_cast<u32>(device_number), &vibrate);
|
||||
}
|
||||
|
||||
int xinput_pad_handler::GetDeviceNumber(const std::string& padId)
|
||||
{
|
||||
if (!Init())
|
||||
return -1;
|
||||
|
||||
size_t pos = padId.find(m_name_string);
|
||||
if (pos == std::string::npos)
|
||||
return -1;
|
||||
|
||||
int device_number = std::stoul(padId.substr(pos + 12)) - 1; // Controllers 1-n in GUI
|
||||
if (device_number >= XUSER_MAX_COUNT)
|
||||
return -1;
|
||||
|
||||
return device_number;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> xinput_pad_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
PadButtonValues values;
|
||||
auto dev = std::static_pointer_cast<XInputDevice>(device);
|
||||
if (!dev || dev->state != ERROR_SUCCESS) // the state has to be aquired with update_connection before calling this function
|
||||
return values;
|
||||
|
||||
// Try SCP first, if it fails for that pad then try normal XInput
|
||||
if (dev->is_scp_device)
|
||||
{
|
||||
return get_button_values_scp(dev->state_scp);
|
||||
}
|
||||
|
||||
return get_button_values_base(dev->state_base);
|
||||
}
|
||||
|
||||
xinput_pad_handler::PadButtonValues xinput_pad_handler::get_button_values_base(const XINPUT_STATE& state)
|
||||
{
|
||||
PadButtonValues 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;
|
||||
}
|
||||
|
||||
xinput_pad_handler::PadButtonValues xinput_pad_handler::get_button_values_scp(const SCP_EXTN& state)
|
||||
{
|
||||
PadButtonValues values;
|
||||
|
||||
// Triggers
|
||||
values[xinput_pad_handler::XInputKeyCodes::LT] = static_cast<u16>(state.SCP_L2 * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::RT] = static_cast<u16>(state.SCP_R2 * 255.0f);
|
||||
|
||||
// Sticks
|
||||
float lx = state.SCP_LX;
|
||||
float ly = state.SCP_LY;
|
||||
float rx = state.SCP_RX;
|
||||
float ry = state.SCP_RY;
|
||||
|
||||
// Left Stick X Axis
|
||||
values[xinput_pad_handler::XInputKeyCodes::LSXNeg] = lx < 0.0f ? static_cast<u16>(lx * -32768.0f) : 0;
|
||||
values[xinput_pad_handler::XInputKeyCodes::LSXPos] = lx > 0.0f ? static_cast<u16>(lx * 32767.0f) : 0;
|
||||
|
||||
// Left Stick Y Axis
|
||||
values[xinput_pad_handler::XInputKeyCodes::LSYNeg] = ly < 0.0f ? static_cast<u16>(ly * -32768.0f) : 0;
|
||||
values[xinput_pad_handler::XInputKeyCodes::LSYPos] = ly > 0.0f ? static_cast<u16>(ly * 32767.0f) : 0;
|
||||
|
||||
// Right Stick X Axis
|
||||
values[xinput_pad_handler::XInputKeyCodes::RSXNeg] = rx < 0.0f ? static_cast<u16>(rx * -32768.0f) : 0;
|
||||
values[xinput_pad_handler::XInputKeyCodes::RSXPos] = rx > 0.0f ? static_cast<u16>(rx * 32767.0f) : 0;
|
||||
|
||||
// Right Stick Y Axis
|
||||
values[xinput_pad_handler::XInputKeyCodes::RSYNeg] = ry < 0.0f ? static_cast<u16>(ry * -32768.0f) : 0;
|
||||
values[xinput_pad_handler::XInputKeyCodes::RSYPos] = ry > 0.0f ? static_cast<u16>(ry * 32767.0f) : 0;
|
||||
|
||||
// A, B, X, Y
|
||||
values[xinput_pad_handler::XInputKeyCodes::A] = static_cast<u16>(state.SCP_X * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::B] = static_cast<u16>(state.SCP_C * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::X] = static_cast<u16>(state.SCP_S * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Y] = static_cast<u16>(state.SCP_T * 255.0f);
|
||||
|
||||
// D-Pad
|
||||
values[xinput_pad_handler::XInputKeyCodes::Left] = static_cast<u16>(state.SCP_LEFT * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Right] = static_cast<u16>(state.SCP_RIGHT * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Up] = static_cast<u16>(state.SCP_UP * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Down] = static_cast<u16>(state.SCP_DOWN * 255.0f);
|
||||
|
||||
// LB, RB, LS, RS
|
||||
values[xinput_pad_handler::XInputKeyCodes::LB] = static_cast<u16>(state.SCP_L1 * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::RB] = static_cast<u16>(state.SCP_R1 * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::LS] = static_cast<u16>(state.SCP_L3 * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::RS] = static_cast<u16>(state.SCP_R3 * 255.0f);
|
||||
|
||||
// Start, Back, Guide
|
||||
values[xinput_pad_handler::XInputKeyCodes::Start] = static_cast<u16>(state.SCP_START * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Back] = static_cast<u16>(state.SCP_SELECT * 255.0f);
|
||||
values[xinput_pad_handler::XInputKeyCodes::Guide] = static_cast<u16>(state.SCP_PS * 255.0f);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
std::array<int, 6> xinput_pad_handler::get_preview_values(std::unordered_map<u64, u16> data)
|
||||
{
|
||||
return { data[LT], data[RT], data[LSXPos] - data[LSXNeg], data[LSYPos] - data[LSYNeg], data[RSXPos] - data[RSXNeg], data[RSYPos] - data[RSYNeg] };
|
||||
}
|
||||
|
||||
bool xinput_pad_handler::Init()
|
||||
{
|
||||
if (is_init)
|
||||
return true;
|
||||
|
||||
for (auto it : XINPUT_INFO::LIBRARY_FILENAMES)
|
||||
{
|
||||
library = LoadLibrary(it);
|
||||
if (library)
|
||||
{
|
||||
xinputGetExtended = reinterpret_cast<PFN_XINPUTGETEXTENDED>(GetProcAddress(library, "XInputGetExtended")); // Optional
|
||||
xinputGetState = reinterpret_cast<PFN_XINPUTGETSTATE>(GetProcAddress(library, reinterpret_cast<LPCSTR>(100)));
|
||||
if (!xinputGetState)
|
||||
xinputGetState = reinterpret_cast<PFN_XINPUTGETSTATE>(GetProcAddress(library, "XInputGetState"));
|
||||
|
||||
xinputSetState = reinterpret_cast<PFN_XINPUTSETSTATE>(GetProcAddress(library, "XInputSetState"));
|
||||
xinputGetBatteryInformation = reinterpret_cast<PFN_XINPUTGETBATTERYINFORMATION>(GetProcAddress(library, "XInputGetBatteryInformation"));
|
||||
|
||||
if (xinputGetState && xinputSetState && xinputGetBatteryInformation)
|
||||
{
|
||||
is_init = true;
|
||||
break;
|
||||
}
|
||||
|
||||
FreeLibrary(library);
|
||||
library = nullptr;
|
||||
xinputGetExtended = nullptr;
|
||||
xinputGetState = nullptr;
|
||||
xinputSetState = nullptr;
|
||||
xinputGetBatteryInformation = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_init)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> xinput_pad_handler::ListDevices()
|
||||
{
|
||||
std::vector<std::string> xinput_pads_list;
|
||||
|
||||
if (!Init())
|
||||
return xinput_pads_list;
|
||||
|
||||
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++)
|
||||
{
|
||||
DWORD result = ERROR_NOT_CONNECTED;
|
||||
|
||||
// Try SCP first, if it fails for that pad then try normal XInput
|
||||
if (xinputGetExtended)
|
||||
{
|
||||
SCP_EXTN state;
|
||||
result = xinputGetExtended(i, &state);
|
||||
}
|
||||
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
XINPUT_STATE state;
|
||||
result = xinputGetState(i, &state);
|
||||
}
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
xinput_pads_list.push_back(m_name_string + std::to_string(i + 1)); // Controllers 1-n in GUI
|
||||
}
|
||||
return xinput_pads_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<PadDevice> xinput_pad_handler::get_device(const std::string& device)
|
||||
{
|
||||
// Convert device string to u32 representing xinput device number
|
||||
int device_number = GetDeviceNumber(device);
|
||||
if (device_number < 0)
|
||||
return nullptr;
|
||||
|
||||
std::shared_ptr<XInputDevice> x_device = std::make_shared<XInputDevice>();
|
||||
x_device->deviceNumber = static_cast<u32>(device_number);
|
||||
|
||||
return x_device;
|
||||
}
|
||||
|
||||
bool xinput_pad_handler::get_is_left_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == XInputKeyCodes::LT;
|
||||
}
|
||||
|
||||
bool xinput_pad_handler::get_is_right_trigger(u64 keyCode)
|
||||
{
|
||||
return keyCode == XInputKeyCodes::RT;
|
||||
}
|
||||
|
||||
bool xinput_pad_handler::get_is_left_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case XInputKeyCodes::LSXNeg:
|
||||
case XInputKeyCodes::LSXPos:
|
||||
case XInputKeyCodes::LSYPos:
|
||||
case XInputKeyCodes::LSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool xinput_pad_handler::get_is_right_stick(u64 keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case XInputKeyCodes::RSXNeg:
|
||||
case XInputKeyCodes::RSXPos:
|
||||
case XInputKeyCodes::RSYPos:
|
||||
case XInputKeyCodes::RSYNeg:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PadHandlerBase::connection xinput_pad_handler::update_connection(const std::shared_ptr<PadDevice>& device)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<XInputDevice>(device);
|
||||
if (!dev)
|
||||
return connection::disconnected;
|
||||
|
||||
dev->state = ERROR_NOT_CONNECTED;
|
||||
dev->state_scp = {};
|
||||
dev->state_base = {};
|
||||
|
||||
// Try SCP first, if it fails for that pad then try normal XInput
|
||||
if (xinputGetExtended)
|
||||
dev->state = xinputGetExtended(dev->deviceNumber, &dev->state_scp);
|
||||
|
||||
dev->is_scp_device = dev->state == ERROR_SUCCESS;
|
||||
|
||||
if (!dev->is_scp_device)
|
||||
dev->state = xinputGetState(dev->deviceNumber, &dev->state_base);
|
||||
|
||||
if (dev->state == ERROR_SUCCESS)
|
||||
return connection::connected;
|
||||
|
||||
return connection::disconnected;
|
||||
}
|
||||
|
||||
void xinput_pad_handler::get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<XInputDevice>(device);
|
||||
if (!dev || !pad)
|
||||
return;
|
||||
|
||||
auto padnum = dev->deviceNumber;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void xinput_pad_handler::apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad)
|
||||
{
|
||||
auto dev = std::static_pointer_cast<XInputDevice>(device);
|
||||
if (!dev || !pad)
|
||||
return;
|
||||
|
||||
auto padnum = dev->deviceNumber;
|
||||
auto profile = dev->config;
|
||||
|
||||
// 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.
|
||||
size_t idx_l = profile->switch_vibration_motors ? 1 : 0;
|
||||
size_t idx_s = profile->switch_vibration_motors ? 0 : 1;
|
||||
|
||||
u16 speed_large = profile->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : static_cast<u16>(vibration_min);
|
||||
u16 speed_small = profile->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : static_cast<u16>(vibration_min);
|
||||
|
||||
dev->newVibrateData |= dev->largeVibrate != speed_large || dev->smallVibrate != speed_small;
|
||||
|
||||
dev->largeVibrate = speed_large;
|
||||
dev->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 (dev->newVibrateData && (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - dev->last_vibration) > 20ms))
|
||||
{
|
||||
XINPUT_VIBRATION vibrate;
|
||||
vibrate.wLeftMotorSpeed = speed_large * 257;
|
||||
vibrate.wRightMotorSpeed = speed_small * 257;
|
||||
|
||||
if ((*xinputSetState)(padnum, &vibrate) == ERROR_SUCCESS)
|
||||
{
|
||||
dev->newVibrateData = false;
|
||||
dev->last_vibration = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
133
rpcs3/Input/xinput_pad_handler.h
Normal file
133
rpcs3/Input/xinput_pad_handler.h
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
#include "Emu/Io/PadHandler.h"
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <Xinput.h>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
// ScpToolkit defined structure for pressure sensitive button query
|
||||
struct SCP_EXTN
|
||||
{
|
||||
float SCP_UP;
|
||||
float SCP_RIGHT;
|
||||
float SCP_DOWN;
|
||||
float SCP_LEFT;
|
||||
|
||||
float SCP_LX;
|
||||
float SCP_LY;
|
||||
|
||||
float SCP_L1;
|
||||
float SCP_L2;
|
||||
float SCP_L3;
|
||||
|
||||
float SCP_RX;
|
||||
float SCP_RY;
|
||||
|
||||
float SCP_R1;
|
||||
float SCP_R2;
|
||||
float SCP_R3;
|
||||
|
||||
float SCP_T;
|
||||
float SCP_C;
|
||||
float SCP_X;
|
||||
float SCP_S;
|
||||
|
||||
float SCP_SELECT;
|
||||
float SCP_START;
|
||||
|
||||
float SCP_PS;
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
using PadButtonValues = std::unordered_map<u64, u16>;
|
||||
|
||||
struct XInputDevice : public PadDevice
|
||||
{
|
||||
u32 deviceNumber{ 0 };
|
||||
bool newVibrateData{ true };
|
||||
u16 largeVibrate{ 0 };
|
||||
u16 smallVibrate{ 0 };
|
||||
std::chrono::high_resolution_clock::time_point last_vibration;
|
||||
bool is_scp_device{ false };
|
||||
DWORD state{ ERROR_NOT_CONNECTED }; // holds internal controller state change
|
||||
SCP_EXTN state_scp{ 0 };
|
||||
XINPUT_STATE state_base{ 0 };
|
||||
};
|
||||
|
||||
public:
|
||||
xinput_pad_handler();
|
||||
~xinput_pad_handler();
|
||||
|
||||
bool Init() override;
|
||||
|
||||
std::vector<std::string> ListDevices() override;
|
||||
void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b) override;
|
||||
void init_config(pad_config* cfg, const std::string& name) override;
|
||||
|
||||
private:
|
||||
typedef DWORD (WINAPI * PFN_XINPUTGETEXTENDED)(DWORD, SCP_EXTN *);
|
||||
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:
|
||||
int GetDeviceNumber(const std::string& padId);
|
||||
PadButtonValues get_button_values_base(const XINPUT_STATE& state);
|
||||
PadButtonValues get_button_values_scp(const SCP_EXTN& state);
|
||||
|
||||
bool is_init{ false };
|
||||
HMODULE library{ nullptr };
|
||||
PFN_XINPUTGETEXTENDED xinputGetExtended{ nullptr };
|
||||
PFN_XINPUTGETSTATE xinputGetState{ nullptr };
|
||||
PFN_XINPUTSETSTATE xinputSetState{ nullptr };
|
||||
PFN_XINPUTGETBATTERYINFORMATION xinputGetBatteryInformation{ nullptr };
|
||||
|
||||
std::shared_ptr<PadDevice> get_device(const std::string& device) override;
|
||||
bool get_is_left_trigger(u64 keyCode) override;
|
||||
bool get_is_right_trigger(u64 keyCode) override;
|
||||
bool get_is_left_stick(u64 keyCode) override;
|
||||
bool get_is_right_stick(u64 keyCode) override;
|
||||
PadHandlerBase::connection update_connection(const std::shared_ptr<PadDevice>& device) override;
|
||||
void get_extended_info(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
void apply_pad_data(const std::shared_ptr<PadDevice>& device, const std::shared_ptr<Pad>& pad) override;
|
||||
std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& device) override;
|
||||
std::array<int, 6> get_preview_values(std::unordered_map<u64, u16> data) override;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue