mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-03-10 23:45:16 +01:00
Merge 0bb398387b into 71f0d5c602
This commit is contained in:
commit
ca8c8e63c8
|
|
@ -402,6 +402,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||
Io/GHLtar.cpp
|
||||
Io/GunCon3.cpp
|
||||
Io/Infinity.cpp
|
||||
Io/wiimote_config.cpp
|
||||
Io/interception.cpp
|
||||
Io/KamenRider.cpp
|
||||
Io/KeyboardHandler.cpp
|
||||
|
|
@ -652,6 +653,7 @@ target_link_libraries(rpcs3_emu
|
|||
3rdparty::vulkan
|
||||
3rdparty::glew
|
||||
3rdparty::libusb
|
||||
3rdparty::hidapi
|
||||
3rdparty::wolfssl
|
||||
3rdparty::openal
|
||||
3rdparty::cubeb
|
||||
|
|
|
|||
|
|
@ -3,9 +3,15 @@
|
|||
#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"
|
||||
#include "Input/wiimote_handler.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
LOG_CHANNEL(guncon3_log);
|
||||
|
||||
|
|
@ -127,6 +133,18 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
|
|||
: usb_device_emulated(location)
|
||||
, m_controller_index(controller_index)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
s_instances.push_back(this);
|
||||
// Sort instances by controller index (P1 < P2 < P3...)
|
||||
// This ensures that the first available GunCon (e.g. at P3) takes the first Wiimote,
|
||||
// and the second (e.g. at P4) takes the second Wiimote.
|
||||
std::sort(s_instances.begin(), s_instances.end(), [](auto* a, auto* b)
|
||||
{
|
||||
return a->m_controller_index < b->m_controller_index;
|
||||
});
|
||||
}
|
||||
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE,
|
||||
UsbDeviceDescriptor {
|
||||
.bcdUSB = 0x0110,
|
||||
|
|
@ -174,6 +192,11 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
|
|||
|
||||
usb_device_guncon3::~usb_device_guncon3()
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
if (auto it = std::find(s_instances.begin(), s_instances.end(), this); it != s_instances.end())
|
||||
{
|
||||
s_instances.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
|
|
@ -188,6 +211,81 @@ void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wVa
|
|||
|
||||
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
|
||||
s64 my_wiimote_index = -1;
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
auto found = std::find(s_instances.begin(), s_instances.end(), this);
|
||||
if (found != s_instances.end())
|
||||
{
|
||||
my_wiimote_index = std::distance(s_instances.begin(), found);
|
||||
}
|
||||
}
|
||||
|
||||
if (my_wiimote_index < 0 || static_cast<usz>(my_wiimote_index) >= states.size())
|
||||
return false;
|
||||
|
||||
const auto& ws = states[my_wiimote_index];
|
||||
if (!ws.connected)
|
||||
return false;
|
||||
|
||||
const auto& map = get_wiimote_config().guncon_mapping;
|
||||
const auto is_pressed = [&](wiimote_button btn) { return (ws.buttons & static_cast<u16>(btn)) != 0; };
|
||||
|
||||
if (is_pressed(map.trigger.get())) gc.btn_trigger = 1;
|
||||
|
||||
// Wiimote to GunCon3 Button Mapping
|
||||
if (is_pressed(map.a1.get())) gc.btn_a1 = 1;
|
||||
if (is_pressed(map.a2.get())) gc.btn_a2 = 1;
|
||||
if (is_pressed(map.a3.get())) gc.btn_a3 = 1;
|
||||
if (is_pressed(map.b1.get())) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2.get())) gc.btn_b2 = 1;
|
||||
if (is_pressed(map.b3.get())) gc.btn_b3 = 1;
|
||||
if (is_pressed(map.c1.get())) gc.btn_c1 = 1;
|
||||
if (is_pressed(map.c2.get())) gc.btn_c2 = 1;
|
||||
|
||||
// Secondary / Hardcoded Alts
|
||||
if (is_pressed(map.b1_alt.get())) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2_alt.get())) gc.btn_b2 = 1;
|
||||
|
||||
if (ws.ir[0].x < 1023)
|
||||
{
|
||||
// Map Wiimote IR (0..1023) to GunCon3 range (-32768..32767)
|
||||
const s32 raw_x = ws.ir[0].x;
|
||||
const s32 raw_y = ws.ir[0].y;
|
||||
|
||||
const s32 x_res = SHRT_MAX - (raw_x * USHRT_MAX / 1023);
|
||||
const s32 y_res = SHRT_MAX - (raw_y * USHRT_MAX / 767);
|
||||
|
||||
gc.gun_x = static_cast<s16>(std::clamp(x_res, SHRT_MIN, SHRT_MAX));
|
||||
gc.gun_y = static_cast<s16>(std::clamp(y_res, SHRT_MIN, SHRT_MAX));
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
const s16 ax = static_cast<s16>((gc.gun_x + SHRT_MAX + 1) * rsx::overlays::overlay::virtual_width / USHRT_MAX);
|
||||
const s16 ay = static_cast<s16>((SHRT_MAX - gc.gun_y) * rsx::overlays::overlay::virtual_height / USHRT_MAX);
|
||||
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + my_wiimote_index, ax, ay, { 1.0f, 1.0f, 1.0f, 1.0f }, 100'000, false);
|
||||
}
|
||||
|
||||
if (ws.ir[1].x < 1023)
|
||||
{
|
||||
const s32 dx = static_cast<s32>(ws.ir[0].x) - ws.ir[1].x;
|
||||
const s32 dy = static_cast<s32>(ws.ir[0].y) - ws.ir[1].y;
|
||||
gc.gun_z = static_cast<s16>(std::sqrt(dx * dx + dy * dy));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
@ -213,6 +311,12 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
|||
return;
|
||||
}
|
||||
|
||||
if (handle_wiimote(gc))
|
||||
{
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_cfg.io.mouse == mouse_handler::null)
|
||||
{
|
||||
guncon3_log.warning("GunCon3 requires a Mouse Handler enabled");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Io/usb_device.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
class usb_device_guncon3 : public usb_device_emulated
|
||||
{
|
||||
|
|
@ -14,4 +16,9 @@ public:
|
|||
private:
|
||||
u32 m_controller_index;
|
||||
std::array<u8, 8> m_key{};
|
||||
|
||||
bool handle_wiimote(struct GunCon3_data& gc);
|
||||
|
||||
static inline std::vector<usb_device_guncon3*> s_instances;
|
||||
static inline std::mutex s_instances_mutex;
|
||||
};
|
||||
|
|
|
|||
58
rpcs3/Emu/Io/wiimote_config.cpp
Normal file
58
rpcs3/Emu/Io/wiimote_config.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "stdafx.h"
|
||||
#include "wiimote_config.h"
|
||||
#include "Utilities/File.h"
|
||||
|
||||
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;
|
||||
return instance;
|
||||
}
|
||||
|
||||
cfg_wiimote::cfg_wiimote()
|
||||
: cfg::node()
|
||||
, path(fs::get_config_dir(true) + "wiimote.yml")
|
||||
{
|
||||
}
|
||||
|
||||
bool cfg_wiimote::load()
|
||||
{
|
||||
if (fs::file f{path, fs::read})
|
||||
{
|
||||
return cfg::node::from_string(f.to_string());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cfg_wiimote::save() const
|
||||
{
|
||||
if (!cfg::node::save(path))
|
||||
{
|
||||
wiimote_log.error("Failed to save wiimote config to '%s'", path);
|
||||
}
|
||||
}
|
||||
35
rpcs3/Emu/Io/wiimote_config.h
Normal file
35
rpcs3/Emu/Io/wiimote_config.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
#include "Input/wiimote_types.h"
|
||||
|
||||
struct cfg_wiimote : cfg::node
|
||||
{
|
||||
cfg_wiimote();
|
||||
bool load();
|
||||
void save() const;
|
||||
|
||||
cfg::_bool use_for_guncon{ this, "UseForGunCon", true };
|
||||
|
||||
struct node_guncon_mapping : cfg::node
|
||||
{
|
||||
node_guncon_mapping(cfg::node* _parent) : cfg::node(_parent, "GunCon Mapping") {}
|
||||
|
||||
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::_enum<wiimote_button> b1_alt{ this, "B1_Alt", wiimote_button::Up };
|
||||
cfg::_enum<wiimote_button> b2_alt{ this, "B2_Alt", wiimote_button::Down };
|
||||
} guncon_mapping{ this };
|
||||
|
||||
const std::string path;
|
||||
};
|
||||
|
||||
cfg_wiimote& get_wiimote_config();
|
||||
|
|
@ -11,7 +11,8 @@ namespace rsx
|
|||
enum cursor_offset : u32
|
||||
{
|
||||
cell_gem = 0, // CELL_GEM_MAX_NUM = 4 Move controllers
|
||||
last = 4
|
||||
wiimote_ir = 4, // 4 points per Wiimote * 4 Wiimotes (up to 16 points)
|
||||
last = 20
|
||||
};
|
||||
|
||||
class cursor_item
|
||||
|
|
|
|||
104
rpcs3/Input/hid_instance.cpp
Normal file
104
rpcs3/Input/hid_instance.cpp
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#include "stdafx.h"
|
||||
#include "hid_instance.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Emu/System.h"
|
||||
#include "Utilities/Thread.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "3rdparty/hidapi/hidapi/mac/hidapi_darwin.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(hid_log, "HID");
|
||||
|
||||
std::mutex g_hid_mutex;
|
||||
|
||||
hid_instance::~hid_instance()
|
||||
{
|
||||
std::lock_guard lock(m_hid_mutex);
|
||||
|
||||
// Only exit HIDAPI once on exit. HIDAPI uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
hid_log.notice("Exiting HIDAPI...");
|
||||
|
||||
if (hid_exit() != 0)
|
||||
{
|
||||
hid_log.error("hid_exit failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hid_instance::initialize()
|
||||
{
|
||||
std::lock_guard lock(m_hid_mutex);
|
||||
|
||||
// Only init HIDAPI once. HIDAPI uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
hid_log.notice("Initializing HIDAPI ...");
|
||||
|
||||
#if defined(__APPLE__)
|
||||
int error_code = 0;
|
||||
Emu.BlockingCallFromMainThread([&error_code]()
|
||||
{
|
||||
error_code = hid_init();
|
||||
hid_darwin_set_open_exclusive(0);
|
||||
}, false);
|
||||
#else
|
||||
const int error_code = hid_init();
|
||||
#endif
|
||||
if (error_code != 0)
|
||||
{
|
||||
hid_log.fatal("hid_init error %d: %s", error_code, hid_error(nullptr));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void hid_instance::enumerate_devices(u16 vid, u16 pid, std::function<void(hid_device_info*)> callback)
|
||||
{
|
||||
std::lock_guard lock(g_hid_mutex);
|
||||
#if defined(__APPLE__)
|
||||
Emu.BlockingCallFromMainThread([&]()
|
||||
{
|
||||
hid_device_info* devs = hid_enumerate(vid, pid);
|
||||
callback(devs);
|
||||
hid_free_enumeration(devs);
|
||||
}, false);
|
||||
#else
|
||||
hid_device_info* devs = hid_enumerate(vid, pid);
|
||||
callback(devs);
|
||||
hid_free_enumeration(devs);
|
||||
#endif
|
||||
}
|
||||
|
||||
hid_device* hid_instance::open_path(const char* path)
|
||||
{
|
||||
std::lock_guard lock(g_hid_mutex);
|
||||
#if defined(__APPLE__)
|
||||
if (!thread_ctrl::is_main())
|
||||
{
|
||||
hid_device* dev = nullptr;
|
||||
Emu.BlockingCallFromMainThread([&]() { dev = hid_open_path(path); }, false);
|
||||
return dev;
|
||||
}
|
||||
#endif
|
||||
return hid_open_path(path);
|
||||
}
|
||||
|
||||
void hid_instance::close(hid_device* dev)
|
||||
{
|
||||
if (!dev) return;
|
||||
|
||||
std::lock_guard lock(g_hid_mutex);
|
||||
#if defined(__APPLE__)
|
||||
Emu.BlockingCallFromMainThread([&]() { hid_close(dev); }, false);
|
||||
#else
|
||||
hid_close(dev);
|
||||
#endif
|
||||
}
|
||||
32
rpcs3/Input/hid_instance.h
Normal file
32
rpcs3/Input/hid_instance.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <hidapi.h>
|
||||
|
||||
struct hid_instance
|
||||
{
|
||||
public:
|
||||
hid_instance() = default;
|
||||
~hid_instance();
|
||||
|
||||
static hid_instance& get_instance()
|
||||
{
|
||||
static hid_instance instance {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool initialize();
|
||||
|
||||
static void enumerate_devices(u16 vid, u16 pid, std::function<void(hid_device_info*)> callback);
|
||||
|
||||
static hid_device* open_path(const char* path);
|
||||
static void close(hid_device* dev);
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
std::mutex m_hid_mutex;
|
||||
};
|
||||
|
||||
extern std::mutex g_hid_mutex;
|
||||
|
|
@ -4,15 +4,11 @@
|
|||
#include "dualsense_pad_handler.h"
|
||||
#include "skateboard_pad_handler.h"
|
||||
#include "ps_move_handler.h"
|
||||
#include "hid_instance.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Utilities/Timer.h"
|
||||
#include "Emu/System.h"
|
||||
#include "pad_thread.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "3rdparty/hidapi/hidapi/mac/hidapi_darwin.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -23,74 +19,6 @@ std::vector<android_usb_device> g_android_usb_devices;
|
|||
std::mutex g_android_usb_devices_mutex;
|
||||
#endif
|
||||
|
||||
// Global mutex to allow "hid_enumerate()" and "hid_open_path()" are accessed by one thread at a time
|
||||
// (e.g. thread running "process()" and thread running enumerate_devices()).
|
||||
// It avoids the emulation crash in case the controller gets disconnected (e.g. due to inactivity)
|
||||
std::mutex g_hid_mutex;
|
||||
|
||||
struct hid_instance
|
||||
{
|
||||
public:
|
||||
hid_instance() = default;
|
||||
~hid_instance()
|
||||
{
|
||||
std::lock_guard lock(m_hid_mutex);
|
||||
|
||||
// Only exit HIDAPI once on exit. HIDAPI uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
hid_log.notice("Exiting HIDAPI...");
|
||||
|
||||
if (hid_exit() != 0)
|
||||
{
|
||||
hid_log.error("hid_exit failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static hid_instance& get_instance()
|
||||
{
|
||||
static hid_instance instance {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool initialize()
|
||||
{
|
||||
std::lock_guard lock(m_hid_mutex);
|
||||
|
||||
// Only init HIDAPI once. HIDAPI uses a global state internally...
|
||||
if (m_initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
hid_log.notice("Initializing HIDAPI ...");
|
||||
|
||||
#if defined(__APPLE__)
|
||||
int error_code = 0;
|
||||
Emu.BlockingCallFromMainThread([&error_code]()
|
||||
{
|
||||
error_code = hid_init();
|
||||
hid_darwin_set_open_exclusive(0);
|
||||
}, false);
|
||||
#else
|
||||
const int error_code = hid_init();
|
||||
#endif
|
||||
if (error_code != 0)
|
||||
{
|
||||
hid_log.fatal("hid_init error %d: %s", error_code, hid_error(nullptr));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
std::mutex m_hid_mutex;
|
||||
};
|
||||
|
||||
hid_device* HidDevice::open()
|
||||
{
|
||||
// Lock before calling "hid_open_path()"
|
||||
|
|
|
|||
392
rpcs3/Input/wiimote_handler.cpp
Normal file
392
rpcs3/Input/wiimote_handler.cpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#include "stdafx.h"
|
||||
#include "wiimote_handler.h"
|
||||
#include "Input/hid_instance.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Io/wiimote_config.h"
|
||||
#include "util/logs.hpp"
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
LOG_CHANNEL(wiimote_log, "Wiimote");
|
||||
|
||||
// Nintendo
|
||||
static constexpr u16 VID_NINTENDO = 0x057e;
|
||||
static constexpr u16 PID_WIIMOTE = 0x0306;
|
||||
static constexpr u16 PID_WIIMOTE_PLUS = 0x0330;
|
||||
|
||||
// Mayflash DolphinBar
|
||||
static constexpr u16 VID_MAYFLASH = 0x0079;
|
||||
static constexpr u16 PID_DOLPHINBAR_START = 0x1800;
|
||||
static constexpr u16 PID_DOLPHINBAR_END = 0x1803;
|
||||
|
||||
wiimote_device::wiimote_device()
|
||||
{
|
||||
m_state.connected = false;
|
||||
}
|
||||
|
||||
wiimote_device::~wiimote_device()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool wiimote_device::open(hid_device_info* info)
|
||||
{
|
||||
if (m_handle) return false;
|
||||
|
||||
m_path = info->path;
|
||||
m_serial = info->serial_number ? info->serial_number : L"";
|
||||
|
||||
m_handle = hid_instance::open_path(info->path);
|
||||
|
||||
if (m_handle)
|
||||
{
|
||||
// 1. Connectivity Test (Matching wiimote_test)
|
||||
constexpr std::array<u8, 2> status_req = { 0x15, 0x00 };
|
||||
if (hid_write(m_handle, status_req.data(), status_req.size()) < 0)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Full Initialization
|
||||
if (initialize_ir())
|
||||
{
|
||||
m_state.connected = true;
|
||||
m_last_ir_check = std::chrono::steady_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void wiimote_device::close()
|
||||
{
|
||||
hid_instance::close(m_handle);
|
||||
m_handle = nullptr;
|
||||
|
||||
m_state = {}; // Reset state including connected = false
|
||||
m_path.clear();
|
||||
m_serial.clear();
|
||||
}
|
||||
|
||||
bool wiimote_device::write_reg(u32 addr, const std::vector<u8>& data)
|
||||
{
|
||||
u8 buf[22] = {0};
|
||||
buf[0] = 0x16; // Write register
|
||||
buf[1] = 0x06; // Register Space + Request Acknowledgement
|
||||
buf[2] = (addr >> 16) & 0xFF;
|
||||
buf[3] = (addr >> 8) & 0xFF;
|
||||
buf[4] = addr & 0xFF;
|
||||
buf[5] = static_cast<u8>(data.size());
|
||||
std::copy(data.begin(), data.end(), &buf[6]);
|
||||
if (hid_write(m_handle, buf, sizeof(buf)) < 0) return false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wiimote_device::initialize_ir()
|
||||
{
|
||||
// 1. Enable IR logic / Pixel Clock (Requesting Acknowledgement for stability)
|
||||
constexpr std::array<u8, 2> ir_on1 = { 0x13, 0x06 };
|
||||
hid_write(m_handle, ir_on1.data(), ir_on1.size());
|
||||
thread_ctrl::wait_for(50'000);
|
||||
constexpr std::array<u8, 2> ir_on2 = { 0x1a, 0x06 };
|
||||
hid_write(m_handle, ir_on2.data(), ir_on2.size());
|
||||
thread_ctrl::wait_for(50'000);
|
||||
|
||||
// 2. Enable IR Camera (Wii-style sequence)
|
||||
if (!write_reg(0xb00030, {0x01})) return false;
|
||||
|
||||
// 3. Sensitivity Level 3 (Exactly matching wiimote_test / official levels)
|
||||
if (!write_reg(0xb00000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x41})) return false;
|
||||
if (!write_reg(0xb0001a, {0x40, 0x00})) return false;
|
||||
|
||||
// 4. IR Mode: Extended (3 bytes per point)
|
||||
if (!write_reg(0xb00033, {0x03})) return false;
|
||||
|
||||
// 5. Finalize IR Enable
|
||||
if (!write_reg(0xb00030, {0x08})) return false;
|
||||
|
||||
// 6. Reporting mode: Buttons + Accel + IR (Continuous)
|
||||
constexpr std::array<u8, 3> mode = { 0x12, 0x04, 0x33 };
|
||||
if (hid_write(m_handle, mode.data(), mode.size()) < 0) return false;
|
||||
thread_ctrl::wait_for(100'000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wiimote_device::verify_ir_health()
|
||||
{
|
||||
if (!m_handle) return false;
|
||||
|
||||
// Request device status to verify communication
|
||||
constexpr std::array<u8, 2> status_req = { 0x15, 0x00 };
|
||||
if (hid_write(m_handle, status_req.data(), status_req.size()) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to read a response within reasonable time
|
||||
u8 buf[22];
|
||||
const int res = hid_read_timeout(m_handle, buf, sizeof(buf), 100);
|
||||
|
||||
// If we got a response, device is alive. If timeout or error, it's dead.
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
bool wiimote_device::update()
|
||||
{
|
||||
if (!m_handle) return false;
|
||||
|
||||
// Every 3 seconds, verify IR is still working
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - m_last_ir_check > std::chrono::seconds(3))
|
||||
{
|
||||
m_last_ir_check = now;
|
||||
if (!verify_ir_health())
|
||||
{
|
||||
// Device not responding - attempt to reinitialize IR
|
||||
if (!initialize_ir())
|
||||
{
|
||||
// Failed to reinitialize - close and mark disconnected
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 buf[22];
|
||||
int res;
|
||||
|
||||
// Fully drain the buffer until empty to ensure we have the most recent data.
|
||||
// This avoids getting stuck behind a backlog of old reports (e.g. from before IR was enabled).
|
||||
while ((res = hid_read_timeout(m_handle, buf, sizeof(buf), 0)) > 0)
|
||||
{
|
||||
// All data reports (0x30-0x3F) carry buttons in the same location (first 2 bytes).
|
||||
// We mask out accelerometer LSBs (bits 5,6 of both bytes).
|
||||
if ((buf[0] & 0xF0) == 0x30)
|
||||
{
|
||||
m_state.buttons = (buf[1] | (buf[2] << 8)) & 0x9F1F;
|
||||
}
|
||||
|
||||
// Mode 0x33: Buttons + Accel + IR (Extended Format)
|
||||
if (buf[0] == 0x33)
|
||||
{
|
||||
// Parse Accelerometer (byte 1: bits 5,6 are X LSBs; byte 2: bit 5 Y LSB, bit 6 Z LSB)
|
||||
m_state.acc_x = (buf[3] << 2) | ((buf[1] >> 5) & 3);
|
||||
m_state.acc_y = (buf[4] << 2) | ((buf[2] >> 5) & 1);
|
||||
m_state.acc_z = (buf[5] << 2) | ((buf[2] >> 6) & 1);
|
||||
|
||||
// Each IR point is 3 bytes in Extended report 0x33.
|
||||
for (usz j = 0; j < MAX_WIIMOTE_IR_POINTS; j++)
|
||||
{
|
||||
const u8* ir = &buf[6 + j * 3];
|
||||
m_state.ir[j].x = (ir[0] | ((ir[2] & 0x30) << 4));
|
||||
m_state.ir[j].y = (ir[1] | ((ir[2] & 0xC0) << 2));
|
||||
m_state.ir[j].size = ir[2] & 0x0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hid_read_timeout returns -1 on error (e.g. device disconnected).
|
||||
if (res < 0)
|
||||
{
|
||||
// Device error - try to recover once before giving up
|
||||
if (!initialize_ir())
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unique_ptr<wiimote_handler> s_instance;
|
||||
|
||||
wiimote_handler::wiimote_handler()
|
||||
{
|
||||
// Pre-initialize Wiimote slots (standard for DolphinBar and typical local multiplayer)
|
||||
for (usz i = 0; i < MAX_WIIMOTES; i++)
|
||||
{
|
||||
m_devices.push_back(std::make_unique<wiimote_device>());
|
||||
}
|
||||
|
||||
get_wiimote_config().load();
|
||||
}
|
||||
|
||||
wiimote_handler::~wiimote_handler()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
wiimote_handler* wiimote_handler::get_instance()
|
||||
{
|
||||
static std::mutex mtx;
|
||||
std::lock_guard lock(mtx);
|
||||
|
||||
if (!s_instance)
|
||||
{
|
||||
s_instance = std::make_unique<wiimote_handler>();
|
||||
s_instance->start();
|
||||
}
|
||||
return s_instance.get();
|
||||
}
|
||||
|
||||
void wiimote_handler::start()
|
||||
{
|
||||
if (m_running) return;
|
||||
|
||||
// Note: initialize() is thread-safe and handles multiple calls.
|
||||
if (!hid_instance::get_instance().initialize()) return;
|
||||
|
||||
m_running = true;
|
||||
m_thread = std::make_unique<named_thread<std::function<void()>>>("WiiMoteManager", [this]() { thread_proc(); });
|
||||
}
|
||||
|
||||
void wiimote_handler::stop()
|
||||
{
|
||||
m_running = false;
|
||||
m_thread.reset();
|
||||
}
|
||||
|
||||
usz wiimote_handler::get_device_count()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return m_devices.size();
|
||||
}
|
||||
|
||||
std::vector<wiimote_state> wiimote_handler::get_states()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
std::vector<wiimote_state> states;
|
||||
states.reserve(m_devices.size());
|
||||
|
||||
for (const auto& dev : m_devices)
|
||||
{
|
||||
states.push_back(dev->get_state());
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
|
||||
void wiimote_handler::thread_proc()
|
||||
{
|
||||
u32 counter = 0;
|
||||
while (m_running && thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Scan every 2 seconds
|
||||
if (counter++ % 200 == 0)
|
||||
{
|
||||
const auto scan_and_add = [&](u16 vid, std::initializer_list<std::pair<u16, u16>> ranges)
|
||||
{
|
||||
struct info_t
|
||||
{
|
||||
std::string path;
|
||||
u16 product_id;
|
||||
std::wstring serial;
|
||||
};
|
||||
std::vector<info_t> candidates;
|
||||
|
||||
hid_instance::enumerate_devices(vid, 0, [&](hid_device_info* devs)
|
||||
{
|
||||
for (hid_device_info* cur = devs; cur; cur = cur->next)
|
||||
{
|
||||
for (const auto& range : ranges)
|
||||
{
|
||||
if (cur->product_id >= range.first && cur->product_id <= range.second)
|
||||
{
|
||||
candidates.push_back({ cur->path, cur->product_id, cur->serial_number ? cur->serial_number : L"" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const auto& candidate : candidates)
|
||||
{
|
||||
// 1. Check if this physical device is already connected to any slot
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
bool already_connected = false;
|
||||
for (const auto& d : m_devices)
|
||||
{
|
||||
if (d->get_state().connected && d->get_path() == candidate.path)
|
||||
{
|
||||
already_connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (already_connected) continue;
|
||||
}
|
||||
|
||||
// 2. Determine target slot
|
||||
int slot_idx = -1;
|
||||
if (vid == VID_MAYFLASH)
|
||||
{
|
||||
// DolphinBar Mode 4: PIDs 0x1800-0x1803 correspond to Players 1-4
|
||||
slot_idx = candidate.product_id - PID_DOLPHINBAR_START;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic Wiimote: Find first available slot
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (usz i = 0; i < m_devices.size(); i++)
|
||||
{
|
||||
if (!m_devices[i]->get_state().connected)
|
||||
{
|
||||
slot_idx = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Connect to slot
|
||||
if (slot_idx >= 0 && slot_idx < static_cast<int>(m_devices.size()))
|
||||
{
|
||||
bool already_connected = false;
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
already_connected = m_devices[slot_idx]->get_state().connected;
|
||||
}
|
||||
|
||||
if (!already_connected)
|
||||
{
|
||||
// Re-create a temporary info struct for open()
|
||||
hid_device_info temp_info = {};
|
||||
temp_info.path = const_cast<char*>(candidate.path.c_str());
|
||||
temp_info.serial_number = const_cast<wchar_t*>(candidate.serial.c_str());
|
||||
temp_info.product_id = candidate.product_id;
|
||||
|
||||
// We call open() without holding m_mutex to avoid deadlock with main thread
|
||||
// wiimote_device::open() internally handles m_handle and connectivity state
|
||||
m_devices[slot_idx]->open(&temp_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Generic Wiimote / Wiimote Plus / DolphinBar Mode 4 (Normal)
|
||||
scan_and_add(VID_NINTENDO, {{PID_WIIMOTE, PID_WIIMOTE}, {PID_WIIMOTE_PLUS, PID_WIIMOTE_PLUS}});
|
||||
// Mayflash DolphinBar Mode 4 (Custom VID/PIDs)
|
||||
scan_and_add(VID_MAYFLASH, {{PID_DOLPHINBAR_START, PID_DOLPHINBAR_END}});
|
||||
}
|
||||
|
||||
// Update all devices at 100Hz
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
for (auto& d : m_devices)
|
||||
{
|
||||
if (d->get_state().connected)
|
||||
{
|
||||
d->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_ctrl::wait_for(10'000);
|
||||
}
|
||||
}
|
||||
75
rpcs3/Input/wiimote_handler.h
Normal file
75
rpcs3/Input/wiimote_handler.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "wiimote_types.h"
|
||||
#include "Utilities/mutex.h"
|
||||
#include <hidapi.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
|
||||
struct wiimote_state
|
||||
{
|
||||
u16 buttons = 0;
|
||||
s16 acc_x = 0;
|
||||
s16 acc_y = 0;
|
||||
s16 acc_z = 0;
|
||||
std::array<wiimote_ir_point, MAX_WIIMOTE_IR_POINTS> ir {};
|
||||
bool connected = false;
|
||||
};
|
||||
|
||||
class wiimote_device
|
||||
{
|
||||
public:
|
||||
wiimote_device();
|
||||
~wiimote_device();
|
||||
|
||||
bool open(hid_device_info* info);
|
||||
void close();
|
||||
|
||||
bool update();
|
||||
const wiimote_state& get_state() const { return m_state; }
|
||||
const std::string& get_path() const { return m_path; }
|
||||
const std::wstring& get_serial() const { return m_serial; }
|
||||
|
||||
private:
|
||||
hid_device* m_handle = nullptr;
|
||||
std::string m_path;
|
||||
std::wstring m_serial;
|
||||
wiimote_state m_state {};
|
||||
std::chrono::steady_clock::time_point m_last_ir_check;
|
||||
|
||||
bool initialize_ir();
|
||||
bool verify_ir_health();
|
||||
bool write_reg(u32 addr, const std::vector<u8>& data);
|
||||
};
|
||||
|
||||
class wiimote_handler
|
||||
{
|
||||
public:
|
||||
wiimote_handler();
|
||||
~wiimote_handler();
|
||||
|
||||
static wiimote_handler* get_instance();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool is_running() const { return m_running; }
|
||||
|
||||
std::vector<wiimote_state> get_states();
|
||||
usz get_device_count();
|
||||
|
||||
private:
|
||||
std::unique_ptr<named_thread<std::function<void()>>> m_thread;
|
||||
atomic_t<bool> m_running{false};
|
||||
std::vector<std::unique_ptr<wiimote_device>> m_devices;
|
||||
shared_mutex m_mutex;
|
||||
|
||||
void thread_proc();
|
||||
};
|
||||
29
rpcs3/Input/wiimote_types.h
Normal file
29
rpcs3/Input/wiimote_types.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
|
||||
static constexpr usz MAX_WIIMOTES = 4;
|
||||
static constexpr usz MAX_WIIMOTE_IR_POINTS = 4;
|
||||
|
||||
struct wiimote_ir_point
|
||||
{
|
||||
u16 x = 1023;
|
||||
u16 y = 1023;
|
||||
u8 size = 0;
|
||||
};
|
||||
|
||||
enum class wiimote_button : 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
|
||||
};
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\3rdparty\hidapi\hidapi\hidapi;..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include</AdditionalIncludeDirectories>
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
|
@ -88,6 +88,7 @@
|
|||
<ClCompile Include="Emu\Io\GameTablet.cpp" />
|
||||
<ClCompile Include="Emu\Io\GHLtar.cpp" />
|
||||
<ClCompile Include="Emu\Io\GunCon3.cpp" />
|
||||
<ClCompile Include="Emu\Io\wiimote_config.cpp" />
|
||||
<ClCompile Include="Emu\Io\midi_config_types.cpp" />
|
||||
<ClCompile Include="Emu\Io\MouseHandler.cpp" />
|
||||
<ClCompile Include="Emu\Io\pad_types.cpp" />
|
||||
|
|
@ -602,6 +603,7 @@
|
|||
<ClInclude Include="Emu\Io\GHLtar.h" />
|
||||
<ClInclude Include="Emu\Io\ghltar_config.h" />
|
||||
<ClInclude Include="Emu\Io\guncon3_config.h" />
|
||||
<ClInclude Include="Emu\Io\wiimote_config.h" />
|
||||
<ClInclude Include="Emu\Io\GunCon3.h" />
|
||||
<ClInclude Include="Emu\Io\interception.h" />
|
||||
<ClInclude Include="Emu\Io\Keyboard.h" />
|
||||
|
|
|
|||
|
|
@ -957,6 +957,9 @@
|
|||
<ClCompile Include="Emu\Io\GunCon3.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\wiimote_config.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\Buzz.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -2590,6 +2593,9 @@
|
|||
<ClInclude Include="Emu\Io\guncon3_config.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\wiimote_config.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\topshotelite_config.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@
|
|||
<ClCompile Include="Input\camera_video_sink.cpp" />
|
||||
<ClCompile Include="Input\dualsense_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\gui_pad_thread.cpp" />
|
||||
<ClCompile Include="Input\hid_instance.cpp" />
|
||||
<ClCompile Include="Input\hid_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\mouse_gyro_handler.cpp" />
|
||||
<ClCompile Include="Input\ps_move_calibration.cpp" />
|
||||
|
|
@ -236,6 +237,9 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Debug\moc_camera_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_wiimote_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_ps_move_tracker_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
@ -539,6 +543,9 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Release\moc_camera_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_wiimote_settings_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_ps_move_tracker_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
@ -825,6 +832,7 @@
|
|||
<ClCompile Include="rpcs3qt\breakpoint_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\call_stack_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\wiimote_settings_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\gui_game_info.cpp" />
|
||||
<ClCompile Include="rpcs3qt\log_level_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\permissions.cpp" />
|
||||
|
|
@ -944,6 +952,7 @@
|
|||
<ClCompile Include="rpcs3qt\save_data_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\save_data_list_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\settings_dialog.cpp" />
|
||||
<ClCompile Include="Input\wiimote_handler.cpp" />
|
||||
<ClCompile Include="Input\xinput_pad_handler.cpp" />
|
||||
<ClCompile Include="util\console.cpp" />
|
||||
<ClCompile Include="\rpcs3qt\*.cpp" />
|
||||
|
|
@ -969,6 +978,7 @@
|
|||
<ClInclude Include="Input\dualsense_pad_handler.h" />
|
||||
<ClInclude Include="Input\evdev_joystick_handler.h" />
|
||||
<ClInclude Include="Input\gui_pad_thread.h" />
|
||||
<ClInclude Include="Input\hid_instance.h" />
|
||||
<ClInclude Include="Input\hid_pad_handler.h" />
|
||||
<ClInclude Include="Input\keyboard_pad_handler.h" />
|
||||
<CustomBuild Include="rpcs3qt\gs_frame.h">
|
||||
|
|
@ -1150,6 +1160,7 @@
|
|||
<ClInclude Include="module_verifier.hpp" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_about_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_camera_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_wiimote_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_ps_move_tracker_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_main_window.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_music_player_dialog.h" />
|
||||
|
|
@ -1224,6 +1235,16 @@
|
|||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\wiimote_settings_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
|
|
@ -2023,6 +2044,8 @@
|
|||
</CustomBuild>
|
||||
<ClInclude Include="rpcs3qt\trophy_notification_helper.h" />
|
||||
<ClInclude Include="rpcs3qt\user_account.h" />
|
||||
<ClInclude Include="Input\wiimote_handler.h" />
|
||||
<ClInclude Include="Input\wiimote_types.h" />
|
||||
<ClInclude Include="Input\xinput_pad_handler.h" />
|
||||
<ClInclude Include="util\console.h" />
|
||||
<ClInclude Include="\rpcs3qt\*.h" />
|
||||
|
|
@ -2174,6 +2197,16 @@
|
|||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\wiimote_settings_dialog.ui">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.ui">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||
|
|
|
|||
|
|
@ -315,6 +315,9 @@
|
|||
<ClCompile Include="Input\mm_joystick_handler.cpp">
|
||||
<Filter>Io\MMJoystick</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\wiimote_handler.cpp">
|
||||
<Filter>Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\xinput_pad_handler.cpp">
|
||||
<Filter>Io\XInput</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -897,6 +900,9 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Release\moc_log_viewer.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\hid_instance.cpp">
|
||||
<Filter>Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\hid_pad_handler.cpp">
|
||||
<Filter>Io</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -954,6 +960,15 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Release\moc_camera_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\wiimote_settings_dialog.cpp">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_wiimote_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_wiimote_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\ps_move_tracker_dialog.cpp">
|
||||
<Filter>Gui\ps_move_tracker_dialog</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1283,6 +1298,12 @@
|
|||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
<Filter>Io\DS4</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\wiimote_handler.h">
|
||||
<Filter>Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\wiimote_types.h">
|
||||
<Filter>Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\xinput_pad_handler.h">
|
||||
<Filter>Io\XInput</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -1397,6 +1418,9 @@
|
|||
<ClInclude Include="rpcs3qt\progress_dialog.h">
|
||||
<Filter>Gui\misc dialogs</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\hid_instance.h">
|
||||
<Filter>Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\hid_pad_handler.h">
|
||||
<Filter>Io</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -1412,6 +1436,9 @@
|
|||
<ClInclude Include="QTGeneratedFiles\ui_camera_settings_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="QTGeneratedFiles\ui_wiimote_settings_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="QTGeneratedFiles\ui_ps_move_tracker_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -1771,9 +1798,15 @@
|
|||
<CustomBuild Include="rpcs3qt\camera_settings_dialog.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\wiimote_settings_dialog.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\camera_settings_dialog.h">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\wiimote_settings_dialog.h">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\ps_move_tracker_dialog.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</CustomBuild>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ add_library(rpcs3_ui STATIC
|
|||
breakpoint_list.cpp
|
||||
call_stack_list.cpp
|
||||
camera_settings_dialog.cpp
|
||||
wiimote_settings_dialog.cpp
|
||||
cg_disasm_window.cpp
|
||||
cheat_manager.cpp
|
||||
clans_settings_dialog.cpp
|
||||
|
|
@ -126,6 +127,7 @@ add_library(rpcs3_ui STATIC
|
|||
|
||||
about_dialog.ui
|
||||
camera_settings_dialog.ui
|
||||
wiimote_settings_dialog.ui
|
||||
main_window.ui
|
||||
music_player_dialog.ui
|
||||
pad_led_settings_dialog.ui
|
||||
|
|
@ -155,6 +157,7 @@ add_library(rpcs3_ui STATIC
|
|||
../Input/dualsense_pad_handler.cpp
|
||||
../Input/evdev_joystick_handler.cpp
|
||||
../Input/gui_pad_thread.cpp
|
||||
../Input/hid_instance.cpp
|
||||
../Input/hid_pad_handler.cpp
|
||||
../Input/keyboard_pad_handler.cpp
|
||||
../Input/mm_joystick_handler.cpp
|
||||
|
|
@ -172,6 +175,7 @@ add_library(rpcs3_ui STATIC
|
|||
../Input/sdl_instance.cpp
|
||||
../Input/sdl_pad_handler.cpp
|
||||
../Input/skateboard_pad_handler.cpp
|
||||
../Input/wiimote_handler.cpp
|
||||
../Input/xinput_pad_handler.cpp
|
||||
|
||||
"../resources.qrc"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "gui_settings.h"
|
||||
#include "input_dialog.h"
|
||||
#include "camera_settings_dialog.h"
|
||||
#include "wiimote_settings_dialog.h"
|
||||
#include "ps_move_tracker_dialog.h"
|
||||
#include "ipc_settings_dialog.h"
|
||||
#include "shortcut_utils.h"
|
||||
|
|
@ -3004,6 +3005,12 @@ void main_window::CreateConnects()
|
|||
dlg->open();
|
||||
});
|
||||
|
||||
connect(ui->confWiimoteAct, &QAction::triggered, this, [this]()
|
||||
{
|
||||
wiimote_settings_dialog* dlg = new wiimote_settings_dialog(this);
|
||||
dlg->show();
|
||||
});
|
||||
|
||||
const auto open_rpcn_settings = [this]
|
||||
{
|
||||
rpcn_settings_dialog dlg(this);
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@
|
|||
</widget>
|
||||
<addaction name="confPadsAct"/>
|
||||
<addaction name="menuMice"/>
|
||||
<addaction name="confWiimoteAct"/>
|
||||
<addaction name="confCamerasAct"/>
|
||||
<addaction name="actionPS_Move_Tracker"/>
|
||||
</widget>
|
||||
|
|
@ -1425,6 +1426,11 @@
|
|||
<string>GunCon 3</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="confWiimoteAct">
|
||||
<property name="text">
|
||||
<string>Wiimotes</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="confTopShotEliteAct">
|
||||
<property name="text">
|
||||
<string>Top Shot Elite</string>
|
||||
|
|
|
|||
288
rpcs3/rpcs3qt/wiimote_settings_dialog.cpp
Normal file
288
rpcs3/rpcs3qt/wiimote_settings_dialog.cpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
#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)
|
||||
, ui(new Ui::wiimote_settings_dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_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
|
||||
};
|
||||
|
||||
ui->useForGunCon->setChecked(get_wiimote_config().use_for_guncon.get());
|
||||
connect(ui->useForGunCon, &QCheckBox::toggled, this, [](bool checked)
|
||||
{
|
||||
get_wiimote_config().use_for_guncon.set(checked);
|
||||
});
|
||||
|
||||
update_list();
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &wiimote_settings_dialog::restore_defaults);
|
||||
|
||||
connect(this, &QDialog::finished, this, []()
|
||||
{
|
||||
get_wiimote_config().save();
|
||||
});
|
||||
|
||||
// Timer updates both state AND device list (auto-refresh)
|
||||
QTimer* timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_state);
|
||||
connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_list);
|
||||
timer->start(50);
|
||||
|
||||
populate_mappings();
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::populate_mappings()
|
||||
{
|
||||
const auto& cfg = get_wiimote_config().guncon_mapping;
|
||||
|
||||
const std::array<std::pair<QString, wiimote_button>, 12> buttons = {
|
||||
{ { tr("None"), wiimote_button::None },
|
||||
{ tr("A"), wiimote_button::A },
|
||||
{ tr("B"), wiimote_button::B },
|
||||
{ tr("Plus (+)"), wiimote_button::Plus },
|
||||
{ tr("Minus (-)"), wiimote_button::Minus },
|
||||
{ tr("Home"), wiimote_button::Home },
|
||||
{ tr("1"), wiimote_button::One },
|
||||
{ tr("2"), wiimote_button::Two },
|
||||
{ tr("D-Pad Up"), wiimote_button::Up },
|
||||
{ tr("D-Pad Down"), wiimote_button::Down },
|
||||
{ tr("D-Pad Left"), wiimote_button::Left },
|
||||
{ tr("D-Pad Right"), wiimote_button::Right } }
|
||||
};
|
||||
|
||||
const std::array<wiimote_button, 9> targets = {
|
||||
cfg.trigger.get(), cfg.a1.get(), cfg.a2.get(), cfg.c1.get(),
|
||||
cfg.b1.get(), cfg.b2.get(), cfg.b3.get(), cfg.a3.get(), cfg.c2.get()
|
||||
};
|
||||
|
||||
ensure(m_boxes.size() == targets.size());
|
||||
|
||||
for (usz i = 0; i < m_boxes.size(); ++i)
|
||||
{
|
||||
m_boxes[i]->setMinimumWidth(150); // Make combo boxes wider for better readability
|
||||
|
||||
for (const auto& [name, btn] : buttons)
|
||||
{
|
||||
m_boxes[i]->addItem(name, QVariant::fromValue(static_cast<u16>(btn)));
|
||||
}
|
||||
|
||||
// Set current selection
|
||||
const int index = m_boxes[i]->findData(QVariant::fromValue(static_cast<u16>(targets[i])));
|
||||
if (index >= 0) m_boxes[i]->setCurrentIndex(index);
|
||||
|
||||
// Connect change signal
|
||||
connect(m_boxes[i], &QComboBox::currentIndexChanged, this, [this](int)
|
||||
{
|
||||
apply_mappings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::restore_defaults()
|
||||
{
|
||||
// Reset to default mapping
|
||||
get_wiimote_config().from_default();
|
||||
|
||||
ui->useForGunCon->setChecked(get_wiimote_config().use_for_guncon.get());
|
||||
|
||||
// Update UI
|
||||
for (auto* box : m_boxes) box->blockSignals(true);
|
||||
|
||||
const auto& cfg = get_wiimote_config().guncon_mapping;
|
||||
const std::array<wiimote_button, 9> targets = {
|
||||
cfg.trigger.get(), cfg.a1.get(), cfg.a2.get(), cfg.c1.get(),
|
||||
cfg.b1.get(), cfg.b2.get(), cfg.b3.get(), cfg.a3.get(), cfg.c2.get()
|
||||
};
|
||||
|
||||
ensure(m_boxes.size() == targets.size());
|
||||
|
||||
for (usz i = 0; i < m_boxes.size(); ++i)
|
||||
{
|
||||
const int index = m_boxes[i]->findData(QVariant::fromValue(static_cast<u16>(targets[i])));
|
||||
if (index >= 0) m_boxes[i]->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
for (auto* box : m_boxes) box->blockSignals(false);
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::apply_mappings()
|
||||
{
|
||||
auto& cfg = get_wiimote_config().guncon_mapping;
|
||||
const std::array<cfg::_enum<wiimote_button>*, 9> targets = {
|
||||
&cfg.trigger, &cfg.a1, &cfg.a2, &cfg.c1,
|
||||
&cfg.b1, &cfg.b2, &cfg.b3, &cfg.a3, &cfg.c2
|
||||
};
|
||||
|
||||
ensure(m_boxes.size() == targets.size());
|
||||
|
||||
for (usz i = 0; i < m_boxes.size(); ++i)
|
||||
{
|
||||
targets[i]->set(static_cast<wiimote_button>(m_boxes[i]->currentData().toUInt()));
|
||||
}
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::update_state()
|
||||
{
|
||||
const int index = ui->wiimoteList->currentRow();
|
||||
auto* wm = wiimote_handler::get_instance();
|
||||
const auto running = wm && wm->is_running();
|
||||
const auto states = running ? wm->get_states() : std::vector<wiimote_state>{};
|
||||
|
||||
if (!running || index < 0 || static_cast<usz>(index) >= states.size())
|
||||
{
|
||||
ui->connectionStatus->setText(tr("N/A"));
|
||||
ui->buttonState->setText(tr("N/A"));
|
||||
ui->irData->setText(tr("N/A"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& state = states[index];
|
||||
ui->connectionStatus->setText(state.connected ? tr("Connected") : tr("Disconnected"));
|
||||
|
||||
QStringList pressed_buttons;
|
||||
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())
|
||||
{
|
||||
button_text += " (" + pressed_buttons.join(", ") + ")";
|
||||
}
|
||||
ui->buttonState->setText(button_text);
|
||||
|
||||
QString ir_text;
|
||||
const int w = ui->irVisual->width();
|
||||
const int h = ui->irVisual->height();
|
||||
QPixmap pixmap(w, h);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// 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(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 };
|
||||
|
||||
for (usz i = 0; i < state.ir.size(); ++i)
|
||||
{
|
||||
if (state.ir[i].size > 0 && state.ir[i].x < 1023 && state.ir[i].y < 1023)
|
||||
{
|
||||
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 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]);
|
||||
painter.drawEllipse(QPointF(x, y), 4, 4);
|
||||
painter.drawText(QPointF(x + 6, y + 6), QString::number(i));
|
||||
}
|
||||
}
|
||||
ui->irVisual->setPixmap(pixmap);
|
||||
ui->irData->setText(ir_text.isEmpty() ? tr("No IR data") : ir_text);
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::update_list()
|
||||
{
|
||||
auto* wm = wiimote_handler::get_instance();
|
||||
if (!wm || !wm->is_running())
|
||||
{
|
||||
const QString text = tr("Wiimote Manager not initialized.");
|
||||
|
||||
if (ui->wiimoteList->count() != 1 || ui->wiimoteList->item(0)->text() != text)
|
||||
{
|
||||
ui->wiimoteList->clear();
|
||||
ui->wiimoteList->addItem(text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto states = wm->get_states();
|
||||
|
||||
if (states.empty())
|
||||
{
|
||||
const QString text = tr("No Wiimotes found.");
|
||||
if (ui->wiimoteList->count() != 1 || ui->wiimoteList->item(0)->text() != text)
|
||||
{
|
||||
ui->wiimoteList->clear();
|
||||
ui->wiimoteList->addItem(text);
|
||||
}
|
||||
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();
|
||||
ui->wiimoteList->clear();
|
||||
|
||||
for (usz i = 0; i < states.size(); i++)
|
||||
{
|
||||
QString label = tr("Wiimote #%1").arg(i + 1);
|
||||
if (!states[i].connected) label += " (" + tr("Disconnected") + ")";
|
||||
ui->wiimoteList->addItem(label);
|
||||
}
|
||||
|
||||
if (current_row >= 0 && current_row < ui->wiimoteList->count())
|
||||
{
|
||||
ui->wiimoteList->setCurrentRow(current_row);
|
||||
}
|
||||
else if (ui->wiimoteList->count() > 0)
|
||||
{
|
||||
ui->wiimoteList->setCurrentRow(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (usz i = 0; i < std::min(static_cast<usz>(ui->wiimoteList->count()), states.size()); i++)
|
||||
{
|
||||
QListWidgetItem* item = ui->wiimoteList->item(static_cast<int>(i));
|
||||
if (!item) continue;
|
||||
QString label = tr("Wiimote #%1").arg(i + 1);
|
||||
if (!states[i].connected) label += " (" + tr("Disconnected") + ")";
|
||||
if (item->text() != label)
|
||||
{
|
||||
item->setText(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
rpcs3/rpcs3qt/wiimote_settings_dialog.h
Normal file
22
rpcs3/rpcs3qt/wiimote_settings_dialog.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include "ui_wiimote_settings_dialog.h"
|
||||
|
||||
class wiimote_settings_dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit wiimote_settings_dialog(QWidget* parent = nullptr);
|
||||
~wiimote_settings_dialog() = default;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::wiimote_settings_dialog> ui;
|
||||
std::vector<QComboBox*> m_boxes;
|
||||
|
||||
void update_list();
|
||||
void update_state();
|
||||
void populate_mappings();
|
||||
void apply_mappings();
|
||||
void restore_defaults();
|
||||
};
|
||||
270
rpcs3/rpcs3qt/wiimote_settings_dialog.ui
Normal file
270
rpcs3/rpcs3qt/wiimote_settings_dialog.ui
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>wiimote_settings_dialog</class>
|
||||
<widget class="QDialog" name="wiimote_settings_dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<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">
|
||||
<string>Connected Wiimotes</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QListWidget" name="wiimoteList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="detailsGroupBox">
|
||||
<property name="title">
|
||||
<string>Wiimote Details</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelStatus">
|
||||
<property name="text">
|
||||
<string>Status:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="connectionStatus">
|
||||
<property name="text">
|
||||
<string>Disconnected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelButtons">
|
||||
<property name="text">
|
||||
<string>Buttons:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="buttonState">
|
||||
<property name="text">
|
||||
<string>0x0000</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelIR">
|
||||
<property name="text">
|
||||
<string>IR Data:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="irData">
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</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">
|
||||
<string>IR Sensor View (All Pointers)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="irVisual">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>256</width>
|
||||
<height>192</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: black; border: 1px solid gray;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<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>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>wiimote_settings_dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>wiimote_settings_dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
Loading…
Reference in a new issue