mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-03-12 00:14:39 +01:00
Wiimote to GunCon3
This commit is contained in:
parent
d854ff03fe
commit
cb01548ca2
|
|
@ -401,6 +401,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||
Io/GameTablet.cpp
|
||||
Io/GHLtar.cpp
|
||||
Io/GunCon3.cpp
|
||||
Io/WiimoteManager.cpp
|
||||
Io/Infinity.cpp
|
||||
Io/interception.cpp
|
||||
Io/KamenRider.cpp
|
||||
|
|
@ -652,6 +653,7 @@ target_link_libraries(rpcs3_emu
|
|||
3rdparty::vulkan
|
||||
3rdparty::glew
|
||||
3rdparty::libusb
|
||||
3rdparty::hidapi
|
||||
3rdparty::wolfssl
|
||||
3rdparty::openal
|
||||
3rdparty::cubeb
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
#include "stdafx.h"
|
||||
#include "GunCon3.h"
|
||||
#include "MouseHandler.h"
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/Io/guncon3_config.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "WiimoteManager.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Emu/RSX/Overlays/overlay_cursor.h"
|
||||
|
||||
LOG_CHANNEL(guncon3_log);
|
||||
|
||||
|
|
@ -127,6 +131,17 @@ 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 +189,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)
|
||||
|
|
@ -207,6 +227,88 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
|||
GunCon3_data gc{};
|
||||
gc.stick_ax = gc.stick_ay = gc.stick_bx = gc.stick_by = 0x7f;
|
||||
|
||||
if (auto* wm = WiimoteManager::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;
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
auto it = std::lower_bound(s_instances.begin(), s_instances.end(), this, [](auto* a, auto* b) {
|
||||
return a->m_controller_index < b->m_controller_index;
|
||||
});
|
||||
// Since we sort by pointer adress/controller_index in add, and search by this ptr
|
||||
// Actually lower_bound needs a value. std::find is safer for pointer identity.
|
||||
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<size_t>(my_wiimote_index) < states.size())
|
||||
{
|
||||
const auto& ws = states[my_wiimote_index];
|
||||
|
||||
if (ws.buttons & 0x0400)
|
||||
{
|
||||
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 (ws.ir[0].x < 1023)
|
||||
{
|
||||
// Only use the primary pointer to avoid jumping between multiple IR points
|
||||
s32 raw_x = ws.ir[0].x;
|
||||
s32 raw_y = ws.ir[0].y;
|
||||
|
||||
// Map to GunCon3 range (-32768..32767)
|
||||
// X calculation (Right = 32767, Left = -32768)
|
||||
s32 x_res = 32767 - (raw_x * 65535 / 1023);
|
||||
// Y calculation (Top = 32767, Bottom = -32768)
|
||||
// Swapping to inverted mapping as per user feedback
|
||||
s32 y_res = 32767 - (raw_y * 65535 / 767);
|
||||
|
||||
gc.gun_x = static_cast<int16_t>(std::clamp(x_res, -32768, 32767));
|
||||
gc.gun_y = static_cast<int16_t>(std::clamp(y_res, -32768, 32767));
|
||||
|
||||
// Draw the actual GunCon3 output to the overlay
|
||||
// Mapping GunCon3 range back to virtual_width/height
|
||||
s16 ax = static_cast<s16>((gc.gun_x + 32768) * rsx::overlays::overlay::virtual_width / 65535);
|
||||
s16 ay = static_cast<s16>((32767 - gc.gun_y) * rsx::overlays::overlay::virtual_height / 65535);
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
// Use my_wiimote_index for color/cursor selection (0=Red, 1=Green...)
|
||||
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)
|
||||
{
|
||||
// Calculate "Z" (distance) based on spread of first two points to emulate depth sensor
|
||||
s32 dx = static_cast<s32>(ws.ir[0].x) - ws.ir[1].x;
|
||||
s32 dy = static_cast<s32>(ws.ir[0].y) - ws.ir[1].y;
|
||||
gc.gun_z = static_cast<int16_t>(std::sqrt(dx * dx + dy * dy));
|
||||
}
|
||||
}
|
||||
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_input_allowed())
|
||||
{
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
|
|
|
|||
|
|
@ -14,4 +14,7 @@ public:
|
|||
private:
|
||||
u32 m_controller_index;
|
||||
std::array<u8, 8> m_key{};
|
||||
|
||||
static inline std::vector<usb_device_guncon3*> s_instances;
|
||||
static inline std::mutex s_instances_mutex;
|
||||
};
|
||||
|
|
|
|||
259
rpcs3/Emu/Io/WiimoteManager.cpp
Normal file
259
rpcs3/Emu/Io/WiimoteManager.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#include "stdafx.h"
|
||||
#include "WiimoteManager.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include <algorithm>
|
||||
|
||||
// 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;
|
||||
|
||||
WiimoteDevice::WiimoteDevice(hid_device_info* info)
|
||||
: m_path(info->path)
|
||||
, m_serial(info->serial_number ? info->serial_number : L"")
|
||||
{
|
||||
m_handle = hid_open_path(info->path);
|
||||
if (m_handle)
|
||||
{
|
||||
// 1. Connectivity Test (Matching wiimote_test)
|
||||
u8 status_req[] = { 0x15, 0x00 };
|
||||
if (hid_write(m_handle, status_req, sizeof(status_req)) < 0)
|
||||
{
|
||||
hid_close(m_handle);
|
||||
m_handle = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Full Initialization
|
||||
if (initialize_ir())
|
||||
{
|
||||
m_state.connected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hid_close(m_handle);
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WiimoteDevice::~WiimoteDevice()
|
||||
{
|
||||
if (m_handle) hid_close(m_handle);
|
||||
}
|
||||
|
||||
bool WiimoteDevice::initialize_ir()
|
||||
{
|
||||
auto write_reg = [&](u32 addr, const std::vector<u8>& data) {
|
||||
u8 buf[22] = {0};
|
||||
buf[0] = 0x16; // Write register
|
||||
buf[1] = 0x04;
|
||||
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(20));
|
||||
return true;
|
||||
};
|
||||
|
||||
// 1. Enable IR logic / Pixel Clock
|
||||
u8 ir_on1[] = { 0x13, 0x04 };
|
||||
hid_write(m_handle, ir_on1, 2);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
u8 ir_on2[] = { 0x1a, 0x04 };
|
||||
hid_write(m_handle, ir_on2, 2);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// 2. Enable IR Camera (Matching wiimote_test order)
|
||||
if (!write_reg(0xb00030, {0x08})) return false;
|
||||
|
||||
// 3. Sensitivity Level 3 (Exactly matching wiimote_test)
|
||||
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. Reporting mode: Buttons + Accel + IR
|
||||
u8 mode[] = { 0x12, 0x00, 0x33 };
|
||||
if (hid_write(m_handle, mode, sizeof(mode)) < 0) return false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiimoteDevice::update()
|
||||
{
|
||||
if (!m_handle) 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 (int j = 0; j < 4; j++)
|
||||
{
|
||||
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) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static WiimoteManager* s_instance = nullptr;
|
||||
|
||||
WiimoteManager::WiimoteManager()
|
||||
{
|
||||
if (!s_instance)
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
WiimoteManager::~WiimoteManager()
|
||||
{
|
||||
stop();
|
||||
if (s_instance == this)
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
WiimoteManager* WiimoteManager::get_instance()
|
||||
{
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void WiimoteManager::start()
|
||||
{
|
||||
if (m_running) return;
|
||||
|
||||
// Note: hid_init() is not thread-safe. ideally should be called once at app startup.
|
||||
if (hid_init() != 0) return;
|
||||
|
||||
m_running = true;
|
||||
m_thread = std::thread(&WiimoteManager::thread_proc, this);
|
||||
}
|
||||
|
||||
void WiimoteManager::stop()
|
||||
{
|
||||
m_running = false;
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
size_t WiimoteManager::get_device_count()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return m_devices.size();
|
||||
}
|
||||
|
||||
std::vector<WiimoteState> WiimoteManager::get_states()
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
std::vector<WiimoteState> states;
|
||||
states.reserve(m_devices.size());
|
||||
|
||||
for (const auto& dev : m_devices)
|
||||
{
|
||||
states.push_back(dev->get_state());
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
|
||||
void WiimoteManager::thread_proc()
|
||||
{
|
||||
u32 counter = 0;
|
||||
while (m_running)
|
||||
{
|
||||
// Scan every 2 seconds
|
||||
if (counter++ % 200 == 0)
|
||||
{
|
||||
auto scan_and_add = [&](u16 vid, u16 pid_start, u16 pid_end)
|
||||
{
|
||||
hid_device_info* devs = hid_enumerate(vid, 0);
|
||||
hid_device_info* cur = devs;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
if (cur->product_id >= pid_start && cur->product_id <= pid_end)
|
||||
{
|
||||
bool already_owned = false;
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
for (const auto& d : m_devices)
|
||||
{
|
||||
if (d->get_path() == cur->path)
|
||||
{
|
||||
already_owned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!already_owned)
|
||||
{
|
||||
auto dev = std::make_unique<WiimoteDevice>(cur);
|
||||
if (dev->get_state().connected)
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_devices.push_back(std::move(dev));
|
||||
}
|
||||
}
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
hid_free_enumeration(devs);
|
||||
};
|
||||
|
||||
// Generic Wiimote / DolphinBar Mode 4 (Normal)
|
||||
scan_and_add(VID_NINTENDO, PID_WIIMOTE, PID_WIIMOTE);
|
||||
// Wiimote Plus
|
||||
scan_and_add(VID_NINTENDO, PID_WIIMOTE_PLUS, PID_WIIMOTE_PLUS);
|
||||
// Mayflash DolphinBar Mode 4 (Custom VID/PIDs)
|
||||
// Supports up to 4 players (1800, 1801, 1802, 1803)
|
||||
scan_and_add(VID_MAYFLASH, PID_DOLPHINBAR_START, PID_DOLPHINBAR_END);
|
||||
}
|
||||
|
||||
// Update all devices at 100Hz
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_devices.erase(std::remove_if(m_devices.begin(), m_devices.end(), [](const auto& d)
|
||||
{
|
||||
return !const_cast<WiimoteDevice&>(*d).update();
|
||||
}), m_devices.end());
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
70
rpcs3/Emu/Io/WiimoteManager.h
Normal file
70
rpcs3/Emu/Io/WiimoteManager.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Utilities/mutex.h"
|
||||
#include <hidapi/hidapi.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <chrono>
|
||||
|
||||
struct WiimoteIRPoint
|
||||
{
|
||||
u16 x = 1023;
|
||||
u16 y = 1023;
|
||||
u8 size = 0;
|
||||
};
|
||||
|
||||
struct WiimoteState
|
||||
{
|
||||
u16 buttons = 0;
|
||||
s16 acc_x = 0, acc_y = 0, acc_z = 0;
|
||||
WiimoteIRPoint ir[4];
|
||||
bool connected = false;
|
||||
};
|
||||
|
||||
class WiimoteDevice
|
||||
{
|
||||
public:
|
||||
WiimoteDevice(hid_device_info* info);
|
||||
~WiimoteDevice();
|
||||
|
||||
bool update();
|
||||
const WiimoteState& get_state() const { return m_state; }
|
||||
std::string get_path() const { return m_path; }
|
||||
std::wstring get_serial() const { return m_serial; }
|
||||
|
||||
private:
|
||||
hid_device* m_handle = nullptr;
|
||||
std::string m_path;
|
||||
std::wstring m_serial;
|
||||
WiimoteState m_state;
|
||||
|
||||
bool initialize_ir();
|
||||
};
|
||||
|
||||
class WiimoteManager
|
||||
{
|
||||
public:
|
||||
WiimoteManager();
|
||||
~WiimoteManager();
|
||||
|
||||
static WiimoteManager* get_instance();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
std::vector<WiimoteState> get_states();
|
||||
size_t get_device_count();
|
||||
|
||||
private:
|
||||
std::thread m_thread;
|
||||
atomic_t<bool> m_running{false};
|
||||
std::vector<std::unique_ptr<WiimoteDevice>> m_devices;
|
||||
shared_mutex m_mutex;
|
||||
|
||||
void thread_proc();
|
||||
};
|
||||
|
|
@ -458,6 +458,18 @@ struct AnalogSensor
|
|||
{}
|
||||
};
|
||||
|
||||
struct ir_point
|
||||
{
|
||||
u16 x = 1023;
|
||||
u16 y = 1023;
|
||||
u16 size = 0;
|
||||
};
|
||||
|
||||
struct ir_data
|
||||
{
|
||||
std::array<ir_point, 4> points;
|
||||
};
|
||||
|
||||
struct VibrateMotor
|
||||
{
|
||||
bool is_large_motor = false;
|
||||
|
|
@ -519,6 +531,7 @@ struct Pad
|
|||
|
||||
std::vector<Button> m_buttons_external;
|
||||
std::array<AnalogStick, 4> m_sticks_external{};
|
||||
ir_data m_ir{};
|
||||
|
||||
std::vector<std::shared_ptr<Pad>> copilots;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ struct cfg_root : cfg::node
|
|||
cfg::_bool keep_pads_connected{this, "Keep pads connected", false, true};
|
||||
cfg::uint<0, 100'000> pad_sleep{this, "Pad handler sleep (microseconds)", 1'000, true};
|
||||
cfg::_bool background_input_enabled{this, "Background input enabled", true, true};
|
||||
cfg::_bool wiimote_continuous_scanning{this, "Wiimote continuous scanning", false, true};
|
||||
cfg::_bool show_move_cursor{this, "Show move cursor", false, true};
|
||||
cfg::_bool paint_move_spheres{this, "Paint move spheres", false, true};
|
||||
cfg::_bool allow_move_hue_set_by_game{this, "Allow move hue set by game", false, true};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -125,6 +126,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
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ enum class emu_settings_type
|
|||
CameraFlip,
|
||||
CameraID,
|
||||
Move,
|
||||
WiimoteScan,
|
||||
Buzz,
|
||||
Turntable,
|
||||
GHLtar,
|
||||
|
|
@ -368,6 +369,7 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
|
|||
{ emu_settings_type::LockOvlIptToP1, { "Input/Output", "Lock overlay input to player one"}},
|
||||
{ emu_settings_type::PadHandlerMode, { "Input/Output", "Pad handler mode"}},
|
||||
{ emu_settings_type::PadConnection, { "Input/Output", "Keep pads connected" }},
|
||||
{ emu_settings_type::WiimoteScan, { "Input/Output", "Wiimote continuous scanning" }},
|
||||
{ emu_settings_type::KeyboardHandler, { "Input/Output", "Keyboard"}},
|
||||
{ emu_settings_type::MouseHandler, { "Input/Output", "Mouse"}},
|
||||
{ emu_settings_type::Camera, { "Input/Output", "Camera"}},
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ bool gui_application::Init()
|
|||
}
|
||||
|
||||
m_emu_settings = std::make_shared<emu_settings>();
|
||||
m_wiimote_manager = std::make_unique<WiimoteManager>();
|
||||
m_wiimote_manager->start();
|
||||
m_gui_settings = std::make_shared<gui_settings>();
|
||||
m_persistent_settings = std::make_shared<persistent_settings>();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ extern std::unique_ptr<raw_mouse_handler> g_raw_mouse_handler; // Only used for
|
|||
/** RPCS3 GUI Application Class
|
||||
* The main point of this class is to do application initialization, to hold the main and game windows and to initialize callbacks.
|
||||
*/
|
||||
#include "Emu/Io/WiimoteManager.h"
|
||||
|
||||
class gui_application : public QApplication, public main_application
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -111,6 +113,7 @@ private:
|
|||
|
||||
std::deque<std::unique_ptr<QSoundEffect>> m_sound_effects{};
|
||||
|
||||
std::unique_ptr<WiimoteManager> m_wiimote_manager;
|
||||
std::shared_ptr<emu_settings> m_emu_settings;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
std::shared_ptr<persistent_settings> m_persistent_settings;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -2992,6 +2993,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>
|
||||
|
|
|
|||
|
|
@ -1220,6 +1220,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
|
|||
m_emu_settings->EnhanceCheckBox(ui->padConnectionBox, emu_settings_type::PadConnection);
|
||||
SubscribeTooltip(ui->padConnectionBox, tooltips.settings.pad_connection);
|
||||
|
||||
m_emu_settings->EnhanceCheckBox(ui->wiimoteScanBox, emu_settings_type::WiimoteScan);
|
||||
SubscribeTooltip(ui->wiimoteScanBox, tooltips.settings.wiimote_scan);
|
||||
|
||||
m_emu_settings->EnhanceCheckBox(ui->showMoveCursorBox, emu_settings_type::ShowMoveCursor);
|
||||
SubscribeTooltip(ui->showMoveCursorBox, tooltips.settings.show_move_cursor);
|
||||
|
||||
|
|
|
|||
|
|
@ -1785,6 +1785,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="wiimoteScanBox">
|
||||
<property name="text">
|
||||
<string>Wiimote Continuous Scanning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showMoveCursorBox">
|
||||
<property name="text">
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@ public:
|
|||
|
||||
const QString pad_mode = tr("Single-threaded: All pad handlers run on the same thread sequentially.\nMulti-threaded: Each pad handler has its own thread.\nOnly use multi-threaded if you can spare the extra threads.");
|
||||
const QString pad_connection = tr("Shows all configured pads as always connected ingame even if they are physically disconnected.");
|
||||
const QString wiimote_scan = tr("Enables continuous scanning for Wiimotes. Required for some adapters like Mayflash DolphinBar to work correctly when hot-plugging.");
|
||||
const QString keyboard_handler = tr("Some games support native keyboard input.\nBasic will work in these cases.");
|
||||
const QString mouse_handler = tr("Some games support native mouse input.\nBasic or Raw will work in these cases.");
|
||||
const QString music_handler = tr("Currently only used for cellMusic emulation.\nSelect Qt to use the default output device of your operating system.\nThis may not be able to play all audio formats.");
|
||||
|
|
|
|||
125
rpcs3/rpcs3qt/wiimote_settings_dialog.cpp
Normal file
125
rpcs3/rpcs3qt/wiimote_settings_dialog.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#include "stdafx.h"
|
||||
#include "wiimote_settings_dialog.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Io/WiimoteManager.h"
|
||||
#include <QTimer>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
|
||||
wiimote_settings_dialog::wiimote_settings_dialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::wiimote_settings_dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
update_list();
|
||||
connect(ui->scanButton, &QPushButton::clicked, this, [this] { update_list(); });
|
||||
|
||||
QTimer* timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_state);
|
||||
timer->start(50);
|
||||
}
|
||||
|
||||
wiimote_settings_dialog::~wiimote_settings_dialog() = default;
|
||||
|
||||
void wiimote_settings_dialog::update_state()
|
||||
{
|
||||
int index = ui->wiimoteList->currentRow();
|
||||
auto* wm = WiimoteManager::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;
|
||||
}
|
||||
|
||||
auto states = wm->get_states();
|
||||
if (static_cast<size_t>(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 pressedButtons;
|
||||
if (state.buttons & 0x0001) pressedButtons << tr("Left");
|
||||
if (state.buttons & 0x0002) pressedButtons << tr("Right");
|
||||
if (state.buttons & 0x0004) pressedButtons << tr("Down");
|
||||
if (state.buttons & 0x0008) pressedButtons << tr("Up");
|
||||
if (state.buttons & 0x0010) pressedButtons << tr("Plus");
|
||||
if (state.buttons & 0x0100) pressedButtons << tr("2");
|
||||
if (state.buttons & 0x0200) pressedButtons << tr("1");
|
||||
if (state.buttons & 0x0400) pressedButtons << tr("B");
|
||||
if (state.buttons & 0x0800) pressedButtons << tr("A");
|
||||
if (state.buttons & 0x1000) pressedButtons << tr("Minus");
|
||||
if (state.buttons & 0x8000) pressedButtons << tr("Home");
|
||||
|
||||
QString buttonText = QString("0x%1").arg(state.buttons, 4, 16, QChar('0')).toUpper();
|
||||
if (!pressedButtons.isEmpty())
|
||||
{
|
||||
buttonText += " (" + pressedButtons.join(", ") + ")";
|
||||
}
|
||||
ui->buttonState->setText(buttonText);
|
||||
|
||||
QString irText;
|
||||
QPixmap pixmap(ui->irVisual->size());
|
||||
pixmap.fill(Qt::black);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Draw center crosshair
|
||||
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);
|
||||
|
||||
static const QColor colors[] = { Qt::red, Qt::green, Qt::blue, Qt::yellow };
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (state.ir[i].size > 0 && state.ir[i].x < 1023 && state.ir[i].y < 1023)
|
||||
{
|
||||
irText += 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
|
||||
float x = ((1023 - state.ir[i].x) / 1023.0f) * pixmap.width();
|
||||
float y = (state.ir[i].y / 767.0f) * pixmap.height();
|
||||
|
||||
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(irText.isEmpty() ? tr("No IR data") : irText);
|
||||
}
|
||||
|
||||
void wiimote_settings_dialog::update_list()
|
||||
{
|
||||
ui->wiimoteList->clear();
|
||||
auto* wm = WiimoteManager::get_instance();
|
||||
if (!wm)
|
||||
{
|
||||
ui->wiimoteList->addItem(tr("Wiimote Manager not initialized."));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t count = wm->get_device_count();
|
||||
if (count == 0)
|
||||
{
|
||||
ui->wiimoteList->addItem(tr("No Wiimotes found."));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
ui->wiimoteList->addItem(tr("Wiimote #%1").arg(i + 1));
|
||||
}
|
||||
ui->wiimoteList->setCurrentRow(0);
|
||||
}
|
||||
}
|
||||
17
rpcs3/rpcs3qt/wiimote_settings_dialog.h
Normal file
17
rpcs3/rpcs3qt/wiimote_settings_dialog.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#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();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::wiimote_settings_dialog> ui;
|
||||
void update_list();
|
||||
void update_state();
|
||||
};
|
||||
179
rpcs3/rpcs3qt/wiimote_settings_dialog.ui
Normal file
179
rpcs3/rpcs3qt/wiimote_settings_dialog.ui
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<?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="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="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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanButton">
|
||||
<property name="text">
|
||||
<string>Refresh Scan</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>
|
||||
</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