mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-03-11 16:05:23 +01:00
Wiimote to GunCon3 remapping support
This commit is contained in:
parent
cb01548ca2
commit
e20e74edda
|
|
@ -3,6 +3,8 @@
|
|||
#include "MouseHandler.h"
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/Io/guncon3_config.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
|
|
@ -250,23 +252,25 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
|||
if (my_wiimote_index >= 0 && static_cast<size_t>(my_wiimote_index) < states.size())
|
||||
{
|
||||
const auto& ws = states[my_wiimote_index];
|
||||
const auto map = wm->get_mapping();
|
||||
|
||||
if (ws.buttons & 0x0400)
|
||||
{
|
||||
gc.btn_trigger = 1;
|
||||
}
|
||||
auto is_pressed = [&](WiimoteButton btn) { return (ws.buttons & static_cast<u16>(btn)) != 0; };
|
||||
|
||||
if (is_pressed(map.trigger)) gc.btn_trigger = 1;
|
||||
|
||||
// Wiimote to GunCon3 Button Mapping
|
||||
if (ws.buttons & 0x0800) gc.btn_a1 = 1; // Wiimote A -> A1
|
||||
if (ws.buttons & 0x1000) gc.btn_a2 = 1; // Wiimote Minus -> A2
|
||||
if (ws.buttons & 0x0010) gc.btn_c1 = 1; // Wiimote Plus -> C1
|
||||
if (ws.buttons & 0x0200) gc.btn_b1 = 1; // Wiimote 1 -> B1
|
||||
if (ws.buttons & 0x0100) gc.btn_b2 = 1; // Wiimote 2 -> B2
|
||||
if (ws.buttons & 0x8000) gc.btn_b3 = 1; // Wiimote Home -> B3
|
||||
if (ws.buttons & 0x0001) gc.btn_a3 = 1; // Wiimote Left (D-pad) -> A3
|
||||
if (ws.buttons & 0x0002) gc.btn_c2 = 1; // Wiimote Right (D-pad)
|
||||
if (ws.buttons & 0x0008) gc.btn_b1 = 1; // D-pad Up -> B1 (Alt)
|
||||
if (ws.buttons & 0x0004) gc.btn_b2 = 1; // D-pad Down -> B2 (Alt)
|
||||
if (is_pressed(map.a1)) gc.btn_a1 = 1;
|
||||
if (is_pressed(map.a2)) gc.btn_a2 = 1;
|
||||
if (is_pressed(map.a3)) gc.btn_a3 = 1;
|
||||
if (is_pressed(map.b1)) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2)) gc.btn_b2 = 1;
|
||||
if (is_pressed(map.b3)) gc.btn_b3 = 1;
|
||||
if (is_pressed(map.c1)) gc.btn_c1 = 1;
|
||||
if (is_pressed(map.c2)) gc.btn_c2 = 1;
|
||||
|
||||
// Secondary / Hardcoded Alts (if kept in mapping struct)
|
||||
if (is_pressed(map.b1_alt)) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2_alt)) gc.btn_b2 = 1;
|
||||
|
||||
if (ws.ir[0].x < 1023)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Io/usb_device.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
|
||||
class usb_device_guncon3 : public usb_device_emulated
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
#include "WiimoteManager.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Utilities/File.h"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
// Nintendo
|
||||
static constexpr u16 VID_NINTENDO = 0x057e;
|
||||
|
|
@ -135,10 +137,94 @@ bool WiimoteDevice::update()
|
|||
|
||||
static WiimoteManager* s_instance = nullptr;
|
||||
|
||||
static std::string get_config_path()
|
||||
{
|
||||
return fs::get_config_dir(true) + "wiimote.yml";
|
||||
}
|
||||
|
||||
void WiimoteManager::load_config()
|
||||
{
|
||||
fs::file f(get_config_path(), fs::read);
|
||||
if (!f) return;
|
||||
|
||||
std::string line;
|
||||
std::stringstream ss(f.to_string());
|
||||
WiimoteGunConMapping map;
|
||||
|
||||
auto parse_btn = [](const std::string& val) -> WiimoteButton {
|
||||
return static_cast<WiimoteButton>(std::strtoul(val.c_str(), nullptr, 0));
|
||||
};
|
||||
|
||||
while (std::getline(ss, line))
|
||||
{
|
||||
auto pos = line.find(':');
|
||||
if (pos == std::string::npos) continue;
|
||||
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string val = line.substr(pos + 1);
|
||||
|
||||
// Trim whitespace
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
val.erase(0, val.find_first_not_of(" \t"));
|
||||
val.erase(val.find_last_not_of(" \t") + 1);
|
||||
|
||||
if (key == "trigger") map.trigger = parse_btn(val);
|
||||
else if (key == "a1") map.a1 = parse_btn(val);
|
||||
else if (key == "a2") map.a2 = parse_btn(val);
|
||||
else if (key == "a3") map.a3 = parse_btn(val);
|
||||
else if (key == "b1") map.b1 = parse_btn(val);
|
||||
else if (key == "b2") map.b2 = parse_btn(val);
|
||||
else if (key == "b3") map.b3 = parse_btn(val);
|
||||
else if (key == "c1") map.c1 = parse_btn(val);
|
||||
else if (key == "c2") map.c2 = parse_btn(val);
|
||||
}
|
||||
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_mapping = map;
|
||||
}
|
||||
|
||||
void WiimoteManager::save_config()
|
||||
{
|
||||
fs::file f(get_config_path(), fs::write + fs::create + fs::trunc);
|
||||
if (!f) return;
|
||||
|
||||
std::stringstream ss;
|
||||
// Helper to write lines
|
||||
auto write_line = [&](const char* key, WiimoteButton btn) {
|
||||
ss << key << ": " << static_cast<u16>(btn) << "\n";
|
||||
};
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
write_line("trigger", m_mapping.trigger);
|
||||
write_line("a1", m_mapping.a1);
|
||||
write_line("a2", m_mapping.a2);
|
||||
write_line("a3", m_mapping.a3);
|
||||
write_line("b1", m_mapping.b1);
|
||||
write_line("b2", m_mapping.b2);
|
||||
write_line("b3", m_mapping.b3);
|
||||
write_line("c1", m_mapping.c1);
|
||||
write_line("c2", m_mapping.c2);
|
||||
}
|
||||
|
||||
f.write(ss.str());
|
||||
}
|
||||
|
||||
WiimoteManager::WiimoteManager()
|
||||
{
|
||||
if (!s_instance)
|
||||
s_instance = this;
|
||||
|
||||
// Set default mapping explicitly to match user preference: C1=Plus, A3=Left
|
||||
// (Struct default constructor might have different values if I didn't edit header defaults)
|
||||
// Let's force it here before loading config.
|
||||
m_mapping.c1 = WiimoteButton::Plus;
|
||||
m_mapping.a3 = WiimoteButton::Left;
|
||||
// Defaults for others from struct:
|
||||
// a1=A, a2=Minus, etc.
|
||||
|
||||
load_config();
|
||||
}
|
||||
|
||||
WiimoteManager::~WiimoteManager()
|
||||
|
|
@ -177,6 +263,21 @@ size_t WiimoteManager::get_device_count()
|
|||
return m_devices.size();
|
||||
}
|
||||
|
||||
void WiimoteManager::set_mapping(const WiimoteGunConMapping& mapping)
|
||||
{
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_mapping = mapping;
|
||||
}
|
||||
save_config();
|
||||
}
|
||||
|
||||
WiimoteGunConMapping WiimoteManager::get_mapping() const
|
||||
{
|
||||
// shared_lock not strictly needed for trivial copy but good practice if it becomes complex
|
||||
return m_mapping;
|
||||
}
|
||||
|
||||
std::vector<WiimoteState> WiimoteManager::get_states()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,39 @@ struct WiimoteIRPoint
|
|||
u8 size = 0;
|
||||
};
|
||||
|
||||
enum class WiimoteButton : u16
|
||||
{
|
||||
None = 0,
|
||||
Left = 0x0001,
|
||||
Right = 0x0002,
|
||||
Down = 0x0004,
|
||||
Up = 0x0008,
|
||||
Plus = 0x0010,
|
||||
Two = 0x0100,
|
||||
One = 0x0200,
|
||||
B = 0x0400,
|
||||
A = 0x0800,
|
||||
Minus = 0x1000,
|
||||
Home = 0x8000
|
||||
};
|
||||
|
||||
struct WiimoteGunConMapping
|
||||
{
|
||||
WiimoteButton trigger = WiimoteButton::B;
|
||||
WiimoteButton a1 = WiimoteButton::A;
|
||||
WiimoteButton a2 = WiimoteButton::Minus;
|
||||
WiimoteButton a3 = WiimoteButton::Left;
|
||||
WiimoteButton b1 = WiimoteButton::One;
|
||||
WiimoteButton b2 = WiimoteButton::Two;
|
||||
WiimoteButton b3 = WiimoteButton::Home;
|
||||
WiimoteButton c1 = WiimoteButton::Plus;
|
||||
WiimoteButton c2 = WiimoteButton::Right;
|
||||
|
||||
// Secondary mappings (optional, e.g. D-Pad acting as buttons)
|
||||
WiimoteButton b1_alt = WiimoteButton::Up;
|
||||
WiimoteButton b2_alt = WiimoteButton::Down;
|
||||
};
|
||||
|
||||
struct WiimoteState
|
||||
{
|
||||
u16 buttons = 0;
|
||||
|
|
@ -60,11 +93,17 @@ public:
|
|||
std::vector<WiimoteState> get_states();
|
||||
size_t get_device_count();
|
||||
|
||||
void set_mapping(const WiimoteGunConMapping& mapping);
|
||||
WiimoteGunConMapping get_mapping() const;
|
||||
|
||||
private:
|
||||
std::thread m_thread;
|
||||
atomic_t<bool> m_running{false};
|
||||
std::vector<std::unique_ptr<WiimoteDevice>> m_devices;
|
||||
shared_mutex m_mutex;
|
||||
WiimoteGunConMapping m_mapping;
|
||||
|
||||
void thread_proc();
|
||||
void load_config();
|
||||
void save_config();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,14 +13,142 @@ wiimote_settings_dialog::wiimote_settings_dialog(QWidget* parent)
|
|||
ui->setupUi(this);
|
||||
update_list();
|
||||
connect(ui->scanButton, &QPushButton::clicked, this, [this] { update_list(); });
|
||||
connect(ui->restoreDefaultsButton, &QPushButton::clicked, this, &wiimote_settings_dialog::restore_defaults);
|
||||
|
||||
QTimer* timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_state);
|
||||
timer->start(50);
|
||||
|
||||
populate_mappings();
|
||||
}
|
||||
|
||||
wiimote_settings_dialog::~wiimote_settings_dialog() = default;
|
||||
|
||||
void wiimote_settings_dialog::populate_mappings()
|
||||
{
|
||||
auto* wm = WiimoteManager::get_instance();
|
||||
if (!wm) return;
|
||||
|
||||
const QPair<QString, WiimoteButton> buttons[] = {
|
||||
{ tr("None"), WiimoteButton::None },
|
||||
{ tr("A"), WiimoteButton::A },
|
||||
{ tr("B"), WiimoteButton::B },
|
||||
{ tr("Plus (+)"), WiimoteButton::Plus },
|
||||
{ tr("Minus (-)"), WiimoteButton::Minus },
|
||||
{ tr("Home"), WiimoteButton::Home },
|
||||
{ tr("1"), WiimoteButton::One },
|
||||
{ tr("2"), WiimoteButton::Two },
|
||||
{ tr("D-Pad Up"), WiimoteButton::Up },
|
||||
{ tr("D-Pad Down"), WiimoteButton::Down },
|
||||
{ tr("D-Pad Left"), WiimoteButton::Left },
|
||||
{ tr("D-Pad Right"), WiimoteButton::Right },
|
||||
};
|
||||
|
||||
QComboBox* boxes[] = {
|
||||
ui->cb_trigger, ui->cb_a1, ui->cb_a2, ui->cb_c1,
|
||||
ui->cb_b1, ui->cb_b2, ui->cb_b3, ui->cb_a3, ui->cb_c2
|
||||
};
|
||||
|
||||
WiimoteGunConMapping current = wm->get_mapping();
|
||||
WiimoteButton* targets[] = {
|
||||
¤t.trigger, ¤t.a1, ¤t.a2, ¤t.c1,
|
||||
¤t.b1, ¤t.b2, ¤t.b3, ¤t.a3, ¤t.c2
|
||||
};
|
||||
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
boxes[i]->setMinimumWidth(150); // Make combo boxes wider for better readability
|
||||
|
||||
for (const auto& pair : buttons)
|
||||
{
|
||||
boxes[i]->addItem(pair.first, QVariant::fromValue(static_cast<u16>(pair.second)));
|
||||
}
|
||||
|
||||
// Set current selection
|
||||
int index = boxes[i]->findData(QVariant::fromValue(static_cast<u16>(*targets[i])));
|
||||
if (index >= 0) boxes[i]->setCurrentIndex(index);
|
||||
|
||||
// Connect change signal
|
||||
connect(boxes[i], QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) {
|
||||
apply_mappings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::restore_defaults()
|
||||
{
|
||||
auto* wm = WiimoteManager::get_instance();
|
||||
if (!wm) return;
|
||||
|
||||
// Reset to default mapping
|
||||
WiimoteGunConMapping default_map;
|
||||
wm->set_mapping(default_map);
|
||||
|
||||
// Update UI
|
||||
ui->cb_trigger->blockSignals(true);
|
||||
ui->cb_a1->blockSignals(true);
|
||||
ui->cb_a2->blockSignals(true);
|
||||
ui->cb_c1->blockSignals(true);
|
||||
ui->cb_b1->blockSignals(true);
|
||||
ui->cb_b2->blockSignals(true);
|
||||
ui->cb_b3->blockSignals(true);
|
||||
ui->cb_a3->blockSignals(true);
|
||||
ui->cb_c2->blockSignals(true);
|
||||
|
||||
auto set_box = [](QComboBox* box, WiimoteButton btn) {
|
||||
int index = box->findData(QVariant::fromValue(static_cast<u16>(btn)));
|
||||
if (index >= 0) box->setCurrentIndex(index);
|
||||
};
|
||||
|
||||
set_box(ui->cb_trigger, default_map.trigger);
|
||||
set_box(ui->cb_a1, default_map.a1);
|
||||
set_box(ui->cb_a2, default_map.a2);
|
||||
set_box(ui->cb_c1, default_map.c1);
|
||||
set_box(ui->cb_b1, default_map.b1);
|
||||
set_box(ui->cb_b2, default_map.b2);
|
||||
set_box(ui->cb_b3, default_map.b3);
|
||||
set_box(ui->cb_a3, default_map.a3);
|
||||
set_box(ui->cb_c2, default_map.c2);
|
||||
|
||||
ui->cb_trigger->blockSignals(false);
|
||||
ui->cb_a1->blockSignals(false);
|
||||
ui->cb_a2->blockSignals(false);
|
||||
ui->cb_c1->blockSignals(false);
|
||||
ui->cb_b1->blockSignals(false);
|
||||
ui->cb_b2->blockSignals(false);
|
||||
ui->cb_b3->blockSignals(false);
|
||||
ui->cb_a3->blockSignals(false);
|
||||
ui->cb_c2->blockSignals(false);
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::apply_mappings()
|
||||
{
|
||||
auto* wm = WiimoteManager::get_instance();
|
||||
if (!wm) return;
|
||||
|
||||
WiimoteGunConMapping map;
|
||||
auto get_btn = [](QComboBox* box) {
|
||||
return static_cast<WiimoteButton>(box->currentData().toUInt());
|
||||
};
|
||||
|
||||
map.trigger = get_btn(ui->cb_trigger);
|
||||
map.a1 = get_btn(ui->cb_a1);
|
||||
map.a2 = get_btn(ui->cb_a2);
|
||||
map.c1 = get_btn(ui->cb_c1);
|
||||
map.b1 = get_btn(ui->cb_b1);
|
||||
map.b2 = get_btn(ui->cb_b2);
|
||||
map.b3 = get_btn(ui->cb_b3);
|
||||
map.a3 = get_btn(ui->cb_a3);
|
||||
map.c2 = get_btn(ui->cb_c2);
|
||||
|
||||
// Preserve alts or add UI for them later. For now, keep defaults or sync with main if matched
|
||||
// To be safe, we can just leave alts as default Up/Down for now since they are D-Pad shortcuts
|
||||
// Or we can reset them if the user maps Up/Down to something else to avoid conflict?
|
||||
// For simplicity, let's keep defaults in the struct constructor.
|
||||
|
||||
wm->set_mapping(map);
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::update_state()
|
||||
{
|
||||
int index = ui->wiimoteList->currentRow();
|
||||
|
|
|
|||
|
|
@ -14,4 +14,7 @@ private:
|
|||
std::unique_ptr<Ui::wiimote_settings_dialog> ui;
|
||||
void update_list();
|
||||
void update_state();
|
||||
void populate_mappings();
|
||||
void apply_mappings();
|
||||
void restore_defaults();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,6 +77,105 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="mappingGroupBox">
|
||||
<property name="title">
|
||||
<string>Button Mapping (GunCon3 <- Wiimote)</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lb_trigger">
|
||||
<property name="text">
|
||||
<string>Trigger:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cb_trigger"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lb_a1">
|
||||
<property name="text">
|
||||
<string>A1 (Left Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cb_a1"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="lb_a2">
|
||||
<property name="text">
|
||||
<string>A2 (Left Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="cb_a2"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="lb_c1">
|
||||
<property name="text">
|
||||
<string>C1 (Start - Left Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="cb_c1"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="lb_b1">
|
||||
<property name="text">
|
||||
<string>B1 (Right Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="cb_b1"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="lb_b2">
|
||||
<property name="text">
|
||||
<string>B2 (Right Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="cb_b2"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="lb_b3">
|
||||
<property name="text">
|
||||
<string>B3 (Right Side):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="cb_b3"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="lb_a3">
|
||||
<property name="text">
|
||||
<string>A3 (Analog Button):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="cb_a3"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="lb_c2">
|
||||
<property name="text">
|
||||
<string>C2 (Select - Analog Button):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="cb_c2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="irGroupBox">
|
||||
<property name="title">
|
||||
|
|
@ -114,6 +213,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreDefaultsButton">
|
||||
<property name="text">
|
||||
<string>Restore Defaults</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
|
|||
Loading…
Reference in a new issue