Enhance Wiimote support for GunCon emulation: Add configuration options and improve UI integration

This commit is contained in:
Barış Hamil 2026-02-14 22:53:18 +03:00
parent f9e8fd535f
commit 6828bec394
7 changed files with 164 additions and 106 deletions

View file

@ -3,6 +3,7 @@
#include "MouseHandler.h"
#include "Emu/IdManager.h"
#include "Emu/Io/guncon3_config.h"
#include "Emu/Io/wiimote_config.h"
#include "Emu/Cell/lv2/sys_usbd.h"
#include "Emu/system_config.h"
#include "Emu/RSX/Overlays/overlay_cursor.h"
@ -212,11 +213,14 @@ extern bool is_input_allowed();
bool usb_device_guncon3::handle_wiimote(GunCon3_data& gc)
{
if (!get_wiimote_config().use_for_guncon.get())
return false;
auto* wm = wiimote_handler::get_instance();
auto states = wm->get_states();
// Determine which Wiimote to use based on our ordinal position among all GunCons
int my_wiimote_index = -1;
s64 my_wiimote_index = -1;
{
std::lock_guard lock(s_instances_mutex);
auto found = std::find(s_instances.begin(), s_instances.end(), this);

View file

@ -4,6 +4,30 @@
LOG_CHANNEL(wiimote_log, "Wiimote");
template <>
void fmt_class_string<wiimote_button>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](wiimote_button value)
{
switch (value)
{
case wiimote_button::None: return "None";
case wiimote_button::Left: return "Left";
case wiimote_button::Right: return "Right";
case wiimote_button::Down: return "Down";
case wiimote_button::Up: return "Up";
case wiimote_button::Plus: return "Plus";
case wiimote_button::Two: return "Two";
case wiimote_button::One: return "One";
case wiimote_button::B: return "B";
case wiimote_button::A: return "A";
case wiimote_button::Minus: return "Minus";
case wiimote_button::Home: return "Home";
}
return unknown;
});
}
cfg_wiimote& get_wiimote_config()
{
static cfg_wiimote instance;
@ -20,14 +44,14 @@ bool cfg_wiimote::load()
{
if (fs::file f{path, fs::read})
{
return from_string(f.to_string());
return this->cfg::node::from_string(f.to_string());
}
return false;
}
void cfg_wiimote::save() const
{
if (!cfg::node::save(path))
if (!this->cfg::node::save(path))
{
wiimote_log.error("Failed to save wiimote config to '%s'", path);
}

View file

@ -1,29 +1,32 @@
#pragma once
#include "Utilities/Config.h"
#include "Input/wiimote_handler.h"
struct cfg_wiimote final : cfg::node
struct cfg_wiimote : cfg::node
{
cfg_wiimote();
bool load();
void save() const;
cfg::_bool use_for_guncon{ this, "UseForGunCon", true };
struct node_mapping : cfg::node
{
node_mapping(cfg::node* _parent) : cfg::node(_parent, "Mapping") {}
cfg::uint<0, 0xFFFF> trigger{ this, "Trigger", 0x0400 };
cfg::uint<0, 0xFFFF> a1{ this, "A1", 0x0800 };
cfg::uint<0, 0xFFFF> a2{ this, "A2", 0x1000 };
cfg::uint<0, 0xFFFF> a3{ this, "A3", 0x0001 };
cfg::uint<0, 0xFFFF> b1{ this, "B1", 0x0200 };
cfg::uint<0, 0xFFFF> b2{ this, "B2", 0x0100 };
cfg::uint<0, 0xFFFF> b3{ this, "B3", 0x8000 };
cfg::uint<0, 0xFFFF> c1{ this, "C1", 0x0010 };
cfg::uint<0, 0xFFFF> c2{ this, "C2", 0x0002 };
cfg::_enum<wiimote_button> trigger{ this, "Trigger", wiimote_button::B };
cfg::_enum<wiimote_button> a1{ this, "A1", wiimote_button::A };
cfg::_enum<wiimote_button> a2{ this, "A2", wiimote_button::Minus };
cfg::_enum<wiimote_button> a3{ this, "A3", wiimote_button::Left };
cfg::_enum<wiimote_button> b1{ this, "B1", wiimote_button::One };
cfg::_enum<wiimote_button> b2{ this, "B2", wiimote_button::Two };
cfg::_enum<wiimote_button> b3{ this, "B3", wiimote_button::Home };
cfg::_enum<wiimote_button> c1{ this, "C1", wiimote_button::Plus };
cfg::_enum<wiimote_button> c2{ this, "C2", wiimote_button::Right };
cfg::uint<0, 0xFFFF> b1_alt{ this, "B1_Alt", 0x0008 };
cfg::uint<0, 0xFFFF> b2_alt{ this, "B2_Alt", 0x0004 };
cfg::_enum<wiimote_button> b1_alt{ this, "B1_Alt", wiimote_button::Up };
cfg::_enum<wiimote_button> b2_alt{ this, "B2_Alt", wiimote_button::Down };
} mapping{ this };
const std::string path;

View file

@ -213,17 +213,17 @@ void wiimote_handler::load_config()
if (cfg.load())
{
std::unique_lock lock(m_mutex);
m_mapping.trigger = static_cast<wiimote_button>(cfg.mapping.trigger.get());
m_mapping.a1 = static_cast<wiimote_button>(cfg.mapping.a1.get());
m_mapping.a2 = static_cast<wiimote_button>(cfg.mapping.a2.get());
m_mapping.a3 = static_cast<wiimote_button>(cfg.mapping.a3.get());
m_mapping.b1 = static_cast<wiimote_button>(cfg.mapping.b1.get());
m_mapping.b2 = static_cast<wiimote_button>(cfg.mapping.b2.get());
m_mapping.b3 = static_cast<wiimote_button>(cfg.mapping.b3.get());
m_mapping.c1 = static_cast<wiimote_button>(cfg.mapping.c1.get());
m_mapping.c2 = static_cast<wiimote_button>(cfg.mapping.c2.get());
m_mapping.b1_alt = static_cast<wiimote_button>(cfg.mapping.b1_alt.get());
m_mapping.b2_alt = static_cast<wiimote_button>(cfg.mapping.b2_alt.get());
m_mapping.trigger = cfg.mapping.trigger.get();
m_mapping.a1 = cfg.mapping.a1.get();
m_mapping.a2 = cfg.mapping.a2.get();
m_mapping.a3 = cfg.mapping.a3.get();
m_mapping.b1 = cfg.mapping.b1.get();
m_mapping.b2 = cfg.mapping.b2.get();
m_mapping.b3 = cfg.mapping.b3.get();
m_mapping.c1 = cfg.mapping.c1.get();
m_mapping.c2 = cfg.mapping.c2.get();
m_mapping.b1_alt = cfg.mapping.b1_alt.get();
m_mapping.b2_alt = cfg.mapping.b2_alt.get();
}
}
@ -232,17 +232,17 @@ void wiimote_handler::save_config()
{
std::shared_lock lock(m_mutex);
auto& cfg = get_wiimote_config();
cfg.mapping.trigger.set(static_cast<u16>(m_mapping.trigger));
cfg.mapping.a1.set(static_cast<u16>(m_mapping.a1));
cfg.mapping.a2.set(static_cast<u16>(m_mapping.a2));
cfg.mapping.a3.set(static_cast<u16>(m_mapping.a3));
cfg.mapping.b1.set(static_cast<u16>(m_mapping.b1));
cfg.mapping.b2.set(static_cast<u16>(m_mapping.b2));
cfg.mapping.b3.set(static_cast<u16>(m_mapping.b3));
cfg.mapping.c1.set(static_cast<u16>(m_mapping.c1));
cfg.mapping.c2.set(static_cast<u16>(m_mapping.c2));
cfg.mapping.b1_alt.set(static_cast<u16>(m_mapping.b1_alt));
cfg.mapping.b2_alt.set(static_cast<u16>(m_mapping.b2_alt));
cfg.mapping.trigger.set(m_mapping.trigger);
cfg.mapping.a1.set(m_mapping.a1);
cfg.mapping.a2.set(m_mapping.a2);
cfg.mapping.a3.set(m_mapping.a3);
cfg.mapping.b1.set(m_mapping.b1);
cfg.mapping.b2.set(m_mapping.b2);
cfg.mapping.b3.set(m_mapping.b3);
cfg.mapping.c1.set(m_mapping.c1);
cfg.mapping.c2.set(m_mapping.c2);
cfg.mapping.b1_alt.set(m_mapping.b1_alt);
cfg.mapping.b2_alt.set(m_mapping.b2_alt);
}
get_wiimote_config().save();
}

View file

@ -102,6 +102,8 @@ public:
void start();
void stop();
bool is_running() const { return m_running; }
std::vector<wiimote_state> get_states();
usz get_device_count();

View file

@ -1,9 +1,12 @@
#include "stdafx.h"
#include "wiimote_settings_dialog.h"
#include "Input/wiimote_handler.h"
#include "Emu/Io/wiimote_config.h"
#include <QTimer>
#include <QPainter>
#include <QPixmap>
#include <QCheckBox>
#include <QPushButton>
wiimote_settings_dialog::wiimote_settings_dialog(QWidget* parent)
: QDialog(parent)
@ -16,8 +19,18 @@ wiimote_settings_dialog::wiimote_settings_dialog(QWidget* parent)
ui->cb_b1, ui->cb_b2, ui->cb_b3, ui->cb_a3, ui->cb_c2
};
if (auto* use_guncon = findChild<QCheckBox*>( "useForGunCon"))
{
use_guncon->setChecked(get_wiimote_config().use_for_guncon.get());
connect(use_guncon, &QCheckBox::toggled, this, [](bool checked)
{
get_wiimote_config().use_for_guncon.set(checked);
get_wiimote_config().save();
});
}
update_list();
connect(ui->restoreDefaultsButton, &QPushButton::clicked, this, &wiimote_settings_dialog::restore_defaults);
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &wiimote_settings_dialog::restore_defaults);
// Timer updates both state AND device list (auto-refresh)
QTimer* timer = new QTimer(this);
@ -88,6 +101,10 @@ void wiimote_settings_dialog::restore_defaults()
const wiimote_guncon_mapping default_map {};
wm->set_mapping(default_map);
get_wiimote_config().use_for_guncon.set(true);
if (auto* use_guncon = findChild<QCheckBox*>( "useForGunCon"))
use_guncon->setChecked(true);
// Update UI
for (auto* box : m_boxes) box->blockSignals(true);
@ -132,16 +149,9 @@ void wiimote_settings_dialog::update_state()
{
const int index = ui->wiimoteList->currentRow();
auto* wm = wiimote_handler::get_instance();
if (!wm || index < 0)
{
ui->connectionStatus->setText(tr("N/A"));
ui->buttonState->setText(tr("N/A"));
ui->irData->setText(tr("N/A"));
return;
}
const auto states = wm && wm->is_running() ? wm->get_states() : std::vector<wiimote_state>{};
const auto states = wm->get_states();
if (static_cast<usz>(index) >= states.size())
if (!wm || !wm->is_running() || index < 0 || static_cast<usz>(index) >= states.size())
{
ui->connectionStatus->setText(tr("N/A"));
ui->buttonState->setText(tr("N/A"));
@ -153,17 +163,18 @@ void wiimote_settings_dialog::update_state()
ui->connectionStatus->setText(state.connected ? tr("Connected") : tr("Disconnected"));
QStringList pressed_buttons;
if (state.buttons & 0x0001) pressed_buttons << tr("Left");
if (state.buttons & 0x0002) pressed_buttons << tr("Right");
if (state.buttons & 0x0004) pressed_buttons << tr("Down");
if (state.buttons & 0x0008) pressed_buttons << tr("Up");
if (state.buttons & 0x0010) pressed_buttons << tr("Plus");
if (state.buttons & 0x0100) pressed_buttons << tr("2");
if (state.buttons & 0x0200) pressed_buttons << tr("1");
if (state.buttons & 0x0400) pressed_buttons << tr("B");
if (state.buttons & 0x0800) pressed_buttons << tr("A");
if (state.buttons & 0x1000) pressed_buttons << tr("Minus");
if (state.buttons & 0x8000) pressed_buttons << tr("Home");
const auto is_pressed = [&](wiimote_button btn) { return (state.buttons & static_cast<u16>(btn)) != 0; };
if (is_pressed(wiimote_button::Left)) pressed_buttons << tr("Left");
if (is_pressed(wiimote_button::Right)) pressed_buttons << tr("Right");
if (is_pressed(wiimote_button::Down)) pressed_buttons << tr("Down");
if (is_pressed(wiimote_button::Up)) pressed_buttons << tr("Up");
if (is_pressed(wiimote_button::Plus)) pressed_buttons << tr("Plus");
if (is_pressed(wiimote_button::Two)) pressed_buttons << tr("2");
if (is_pressed(wiimote_button::One)) pressed_buttons << tr("1");
if (is_pressed(wiimote_button::B)) pressed_buttons << tr("B");
if (is_pressed(wiimote_button::A)) pressed_buttons << tr("A");
if (is_pressed(wiimote_button::Minus)) pressed_buttons << tr("Minus");
if (is_pressed(wiimote_button::Home)) pressed_buttons << tr("Home");
QString button_text = QString("0x%1").arg(state.buttons, 4, 16, QChar('0')).toUpper();
if (!pressed_buttons.isEmpty())
@ -173,15 +184,32 @@ void wiimote_settings_dialog::update_state()
ui->buttonState->setText(button_text);
QString ir_text;
QPixmap pixmap(ui->irVisual->size());
const int w = ui->irVisual->width();
const int h = ui->irVisual->height();
QPixmap pixmap(w, h);
pixmap.fill(Qt::black);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
// Draw center crosshair
// Calculate 4:3 drawing area (Wiimote IR space is 1024x768)
int draw_w, draw_h;
if (w * 3 > h * 4) // wider than 4:3
{
draw_h = h;
draw_w = h * 4 / 3;
}
else
{
draw_w = w;
draw_h = w * 3 / 4;
}
const int offset_x = (w - draw_w) / 2;
const int offset_y = (h - draw_h) / 2;
// Draw center crosshair in the 4:3 area
painter.setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
painter.drawLine(pixmap.width() / 2, 0, pixmap.width() / 2, pixmap.height());
painter.drawLine(0, pixmap.height() / 2, pixmap.width(), pixmap.height() / 2);
painter.drawLine(offset_x + draw_w / 2, offset_y, offset_x + draw_w / 2, offset_y + draw_h);
painter.drawLine(offset_x, offset_y + draw_h / 2, offset_x + draw_w, offset_y + draw_h / 2);
static const std::array<QColor, MAX_WIIMOTE_IR_POINTS> colors = { Qt::red, Qt::green, Qt::blue, Qt::yellow };
@ -191,10 +219,9 @@ void wiimote_settings_dialog::update_state()
{
ir_text += QString("[%1: %2, %3] ").arg(i).arg(state.ir[i].x).arg(state.ir[i].y);
// Map 0..1023 X and 0..767 Y to pixmap coordinates
// Wiimote X/Y are inverted relative to pointing direction
const float x = ((1023 - state.ir[i].x) / 1023.0f) * pixmap.width();
const float y = (state.ir[i].y / 767.0f) * pixmap.height();
// Map 0..1023 X and 0..767 Y to 4:3 drawing area
const float x = offset_x + ((1023.0f - state.ir[i].x) / 1023.0f) * draw_w;
const float y = offset_y + (state.ir[i].y / 767.0f) * draw_h;
painter.setPen(colors[i]);
painter.setBrush(colors[i]);
@ -209,7 +236,7 @@ void wiimote_settings_dialog::update_state()
void wiimote_settings_dialog::update_list()
{
auto* wm = wiimote_handler::get_instance();
if (!wm)
if (!wm || !wm->is_running())
{
if (ui->wiimoteList->count() != 1 || ui->wiimoteList->item(0)->text() != tr("Wiimote Manager not initialized."))
{
@ -221,7 +248,17 @@ void wiimote_settings_dialog::update_list()
const auto states = wm->get_states();
// Only update if the list content actually changed (avoid flicker)
if (states.empty())
{
if (ui->wiimoteList->count() != 1 || ui->wiimoteList->item(0)->text() != tr("No Wiimotes found."))
{
ui->wiimoteList->clear();
ui->wiimoteList->addItem(tr("No Wiimotes found."));
}
return;
}
// Only update if the list count changed (avoid flicker)
if (static_cast<usz>(ui->wiimoteList->count()) != states.size())
{
const int current_row = ui->wiimoteList->currentRow();
@ -251,13 +288,9 @@ void wiimote_settings_dialog::update_list()
QString label = tr("Wiimote #%1").arg(i + 1);
if (!states[i].connected) label += " (" + tr("Disconnected") + ")";
if (static_cast<int>(i) < ui->wiimoteList->count())
if (QListWidgetItem* item = ui->wiimoteList->item(static_cast<int>(i)); item && item->text() != label)
{
QListWidgetItem* item = ui->wiimoteList->item(static_cast<int>(i));
if (item && item->text() != label)
{
item->setText(label);
}
item->setText(label);
}
}
}

View file

@ -14,6 +14,22 @@
<string>Wiimote Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="generalGroupBox">
<property name="title">
<string>General Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="useForGunCon">
<property name="text">
<string>Enable Wiimote for GunCon Emulation</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@ -205,38 +221,14 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="restoreDefaultsButton">
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>