InputCommon: Add real Wii Remote support to ControllerInterface. Add option to connect additional Wii Remotes.

This commit is contained in:
Jordan Woyak 2020-01-21 18:50:05 -06:00
parent 4176cc77e1
commit 58448d74c5
20 changed files with 2267 additions and 236 deletions

View File

@ -234,6 +234,7 @@ void SConfig::SaveCoreSettings(IniFile& ini)
core->Set("WiiKeyboard", m_WiiKeyboard);
core->Set("WiimoteContinuousScanning", m_WiimoteContinuousScanning);
core->Set("WiimoteEnableSpeaker", m_WiimoteEnableSpeaker);
core->Set("WiimoteControllerInterface", connect_wiimotes_for_ciface);
core->Set("RunCompareServer", bRunCompareServer);
core->Set("RunCompareClient", bRunCompareClient);
core->Set("MMU", bMMU);
@ -511,6 +512,7 @@ void SConfig::LoadCoreSettings(IniFile& ini)
core->Get("WiiKeyboard", &m_WiiKeyboard, false);
core->Get("WiimoteContinuousScanning", &m_WiimoteContinuousScanning, false);
core->Get("WiimoteEnableSpeaker", &m_WiimoteEnableSpeaker, false);
core->Get("WiimoteControllerInterface", &connect_wiimotes_for_ciface, false);
core->Get("RunCompareServer", &bRunCompareServer, false);
core->Get("RunCompareClient", &bRunCompareClient, false);
core->Get("MMU", &bMMU, bMMU);

View File

@ -72,6 +72,7 @@ struct SConfig
bool m_WiiKeyboard;
bool m_WiimoteContinuousScanning;
bool m_WiimoteEnableSpeaker;
bool connect_wiimotes_for_ciface;
// ISO folder
std::vector<std::string> m_ISOFolder;

View File

@ -60,14 +60,6 @@ void CameraLogic::Update(const Common::Matrix44& transform)
using Common::Vec3;
using Common::Vec4;
constexpr int CAMERA_WIDTH = 1024;
constexpr int CAMERA_HEIGHT = 768;
// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
constexpr int CAMERA_FOV_X_DEG = 33;
constexpr int CAMERA_FOV_Y_DEG = 23;
constexpr auto CAMERA_FOV_Y = float(CAMERA_FOV_Y_DEG * MathUtil::TAU / 360);
constexpr auto CAMERA_ASPECT_RATIO = float(CAMERA_FOV_X_DEG) / CAMERA_FOV_Y_DEG;
@ -112,12 +104,12 @@ void CameraLogic::Update(const Common::Matrix44& transform)
if (point.z > 0)
{
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2);
const auto x = s32((1 - point.x / point.w) * CAMERA_RES_X / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_RES_Y / 2);
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT)
if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y)
return CameraPoint{u16(x), u16(y), u8(point_size)};
}
@ -165,7 +157,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRExtended irdata = {};
@ -186,7 +178,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRFull irdata = {};
@ -203,8 +195,8 @@ void CameraLogic::Update(const Common::Matrix44& transform)
irdata.xmin = std::max(p.x - p.size, 0);
irdata.ymin = std::max(p.y - p.size, 0);
irdata.xmax = std::min(p.x + p.size, CAMERA_WIDTH);
irdata.ymax = std::min(p.y + p.size, CAMERA_HEIGHT);
irdata.xmax = std::min(p.x + p.size, CAMERA_RES_X);
irdata.ymax = std::min(p.y + p.size, CAMERA_RES_Y);
// TODO: Is this maybe MSbs of the "intensity" value?
irdata.zero = 0;

View File

@ -20,6 +20,8 @@ namespace WiimoteEmu
// Four bytes for two objects. Filled with 0xFF if empty
struct IRBasic
{
using IRObject = Common::TVec2<u16>;
u8 x1;
u8 y1;
u8 x2hi : 2;
@ -28,6 +30,9 @@ struct IRBasic
u8 y1hi : 2;
u8 x2;
u8 y2;
auto GetObject1() const { return IRObject(x1hi << 8 | x1, y1hi << 8 | y1); }
auto GetObject2() const { return IRObject(x2hi << 8 | x2, y2hi << 8 | y2); }
};
static_assert(sizeof(IRBasic) == 5, "Wrong size");
@ -62,6 +67,14 @@ static_assert(sizeof(IRFull) == 9, "Wrong size");
class CameraLogic : public I2CSlave
{
public:
static constexpr int CAMERA_RES_X = 1024;
static constexpr int CAMERA_RES_Y = 768;
// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
static constexpr int CAMERA_FOV_X_DEG = 33;
static constexpr int CAMERA_FOV_Y_DEG = 23;
enum : u8
{
IR_MODE_BASIC = 1,

View File

@ -54,11 +54,15 @@ double CalculateStopDistance(double velocity, double max_accel)
return velocity * velocity / (2 * std::copysign(max_accel, velocity));
}
// Note that 'gyroscope' is rotation of world around device.
Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
const Common::Matrix33& gyroscope, float accel_weight)
} // namespace
namespace WiimoteEmu
{
const auto gyro_vec = gyroscope * Common::Vec3{0, 0, 1};
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal)
{
const auto gyro_vec = gyroscope * accelerometer_normal;
const auto normalized_accel = accelerometer.Normalized();
const auto cos_angle = normalized_accel.Dot(gyro_vec);
@ -76,10 +80,6 @@ Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
}
}
} // namespace
namespace WiimoteEmu
{
IMUCursorState::IMUCursorState() : rotation{Common::Matrix33::Identity()}
{
}
@ -203,17 +203,17 @@ void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float t
}
}
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g)
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g)
{
const auto scaled_accel = accel * (one_g - zero_g) / float(GRAVITY_ACCELERATION);
// 10-bit integers.
constexpr long MAX_VALUE = (1 << 10) - 1;
return {u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))};
return WiimoteCommon::AccelData(
{u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))});
}
void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed)
@ -311,28 +311,24 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
}
// Apply rotation from gyro data.
const auto gyro_rotation = Common::Matrix33::FromQuaternion(ang_vel->x * time_elapsed / -2,
ang_vel->y * time_elapsed / -2,
ang_vel->z * time_elapsed / -2, 1);
const auto gyro_rotation = GetMatrixFromGyroscope(*ang_vel * -1 * time_elapsed);
state->rotation = gyro_rotation * state->rotation;
// If we have some non-zero accel data use it to adjust gyro drift.
constexpr auto ACCEL_WEIGHT = 0.02f;
auto const accel = imu_accelerometer_group->GetState().value_or(Common::Vec3{});
if (accel.LengthSquared())
state->rotation = ComplementaryFilter(accel, state->rotation, ACCEL_WEIGHT);
const auto inv_rotation = state->rotation.Inverted();
state->rotation = ComplementaryFilter(state->rotation, accel, ACCEL_WEIGHT);
// Clamp yaw within configured bounds.
const auto yaw = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).x);
const auto yaw = GetYaw(state->rotation);
const auto max_yaw = float(imu_ir_group->GetTotalYaw() / 2);
auto target_yaw = std::clamp(yaw, -max_yaw, max_yaw);
// Handle the "Recenter" button being pressed.
if (imu_ir_group->controls[0]->GetState<bool>())
{
state->recentered_pitch = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).z);
state->recentered_pitch = GetPitch(state->rotation);
target_yaw = 0;
}
@ -390,10 +386,33 @@ Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel)
axis.LengthSquared() ? axis.Normalized() : Common::Vec3{0, 1, 0});
}
Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro)
{
return Common::Matrix33::FromQuaternion(gyro.x / 2, gyro.y / 2, gyro.z / 2, 1);
}
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle)
{
return Common::Matrix33::RotateZ(angle.z) * Common::Matrix33::RotateY(angle.y) *
Common::Matrix33::RotateX(angle.x);
}
float GetPitch(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.y, Common::Vec2(vec.x, vec.z).Length());
}
float GetRoll(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.x, vec.z);
}
float GetYaw(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation.Inverted() * Common::Vec3{0, 1, 0};
return std::atan2(vec.x, vec.y);
}
} // namespace WiimoteEmu

View File

@ -54,12 +54,26 @@ struct MotionState : PositionalState, RotationalState
{
};
// Note that 'gyroscope' is rotation of world around device.
// Alternative accelerometer_normal can be supplied to correct from non-accelerometer data.
// e.g. Used for yaw/pitch correction with IR data.
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal = {0, 0, 1});
// Estimate orientation from accelerometer data.
Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel);
// Get a rotation matrix from current gyro data.
Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro);
// Build a rotational matrix from euler angles.
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle);
float GetPitch(const Common::Matrix33& world_rotation);
float GetRoll(const Common::Matrix33& world_rotation);
float GetYaw(const Common::Matrix33& world_rotation);
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target,
const Common::Vec3& max_jerk, float time_elapsed);
@ -75,7 +89,6 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed);
// Convert m/s/s acceleration data to the format used by Wiimote/Nunchuk (10-bit unsigned integers).
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g);
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g);
} // namespace WiimoteEmu

View File

@ -236,10 +236,6 @@ void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
// Update status struct
m_status.extension = m_extension_port.IsDeviceConnected();
// Based on testing, old WiiLi.org docs, and WiiUse library:
// Max battery level seems to be 0xc8 (decimal 200)
constexpr u8 MAX_BATTERY_LEVEL = 0xc8;
m_status.battery = u8(std::lround(m_battery_setting.GetValue() / 100 * MAX_BATTERY_LEVEL));
if (Core::WantsDeterminism())

View File

@ -257,12 +257,13 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
@implementation SearchBT
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
error:(IOReturn)error
aborted:(BOOL)aborted {
aborted:(BOOL)aborted
{
done = true;
}
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
device:(IOBluetoothDevice*)device {
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender device:(IOBluetoothDevice*)device
{
NOTICE_LOG(WIIMOTE, "Discovered Bluetooth device at %s: %s", [[device addressString] UTF8String],
[[device name] UTF8String]);
@ -274,11 +275,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
@implementation ConnectBT
- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
data:(unsigned char*)data
length:(NSUInteger)length {
length:(NSUInteger)length
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;
std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);
for (int i = 0; i < MAX_WIIMOTES; i++)
{
@ -314,11 +316,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;
std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);
for (int i = 0; i < MAX_WIIMOTES; i++)
{

View File

@ -26,6 +26,7 @@
#include "Core/HW/WiimoteReal/IOWin.h"
#include "Core/HW/WiimoteReal/IOdarwin.h"
#include "Core/HW/WiimoteReal/IOhidapi.h"
#include "InputCommon/ControllerInterface/Wiimote/Wiimote.h"
#include "InputCommon/InputConfig.h"
#include "SFML/Network.hpp"
@ -35,7 +36,7 @@ namespace WiimoteReal
using namespace WiimoteCommon;
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote>);
static void TryToConnectWiimote(std::unique_ptr<Wiimote>);
static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>&, unsigned int);
static void HandleWiimoteDisconnect(int index);
static bool g_real_wiimotes_initialized = false;
@ -45,7 +46,7 @@ static bool g_real_wiimotes_initialized = false;
static std::unordered_set<std::string> s_known_ids;
static std::mutex s_known_ids_mutex;
std::mutex g_wiimotes_mutex;
std::recursive_mutex g_wiimotes_mutex;
// Real wii remotes assigned to a particular slot.
std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
@ -72,22 +73,64 @@ std::vector<WiimotePoolEntry> g_wiimote_pool;
WiimoteScanner g_wiimote_scanner;
static void ProcessWiimotePool()
// Attempt to fill a real wiimote slot from the pool or by stealing from ControllerInterface.
static void TryToFillWiimoteSlot(u32 index)
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (auto it = g_wiimote_pool.begin(); it != g_wiimote_pool.end();)
if (g_wiimotes[index] || WiimoteCommon::GetSource(index) != WiimoteSource::Real)
return;
// If the pool is empty, attempt to steal from ControllerInterface.
if (g_wiimote_pool.empty())
{
if (it->IsExpired())
{
INFO_LOG(WIIMOTE, "Removing expired wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else
{
++it;
}
ciface::Wiimote::ReleaseDevices(1);
// Still empty?
if (g_wiimote_pool.empty())
return;
}
if (TryToConnectWiimoteToSlot(g_wiimote_pool.front().wiimote, index))
g_wiimote_pool.erase(g_wiimote_pool.begin());
}
// Attempts to fill enabled real wiimote slots.
// Push/pull wiimotes to/from ControllerInterface as needed.
void ProcessWiimotePool()
{
std::lock_guard lk(g_wiimotes_mutex);
for (u32 index = 0; index != MAX_WIIMOTES; ++index)
TryToFillWiimoteSlot(index);
if (SConfig::GetInstance().connect_wiimotes_for_ciface)
{
for (auto& entry : g_wiimote_pool)
ciface::Wiimote::AddDevice(std::move(entry.wiimote));
g_wiimote_pool.clear();
}
else
{
ciface::Wiimote::ReleaseDevices();
}
}
void AddWiimoteToPool(std::unique_ptr<Wiimote> wiimote)
{
// Our real wiimote class requires an index.
// Within the pool it's only going to be used for logging purposes.
static constexpr int POOL_WIIMOTE_INDEX = 99;
if (!wiimote->Connect(POOL_WIIMOTE_INDEX))
{
ERROR_LOG(WIIMOTE, "Failed to connect real wiimote.");
return;
}
std::lock_guard lk(g_wiimotes_mutex);
g_wiimote_pool.emplace_back(WiimotePoolEntry{std::move(wiimote)});
}
Wiimote::Wiimote() = default;
@ -165,7 +208,7 @@ void Wiimote::ResetDataReporting()
OutputReportMode rpt = {};
rpt.mode = InputReportID::ReportCore;
rpt.continuous = 0;
QueueReport(OutputReportID::ReportMode, &rpt, sizeof(rpt));
QueueReport(rpt);
}
void Wiimote::ClearReadQueue()
@ -241,11 +284,11 @@ void Wiimote::InterruptChannel(const u16 channel, const void* const data, const
else if (rpt[1] == u8(OutputReportID::SpeakerData) &&
(!SConfig::GetInstance().m_WiimoteEnableSpeaker || !m_speaker_enable || m_speaker_mute))
{
rpt.resize(3);
// Translate undesired speaker data reports into rumble reports.
rpt[1] = u8(OutputReportID::Rumble);
// Keep only the rumble bit.
rpt[2] &= 0x1;
rpt.resize(3);
}
WriteReport(std::move(rpt));
@ -380,11 +423,16 @@ static bool IsDataReport(const Report& rpt)
return rpt.size() >= 2 && rpt[1] >= u8(InputReportID::ReportCore);
}
bool Wiimote::GetNextReport(Report* report)
{
return m_read_reports.Pop(*report);
}
// Returns the next report that should be sent
Report& Wiimote::ProcessReadQueue()
{
// Pop through the queued reports
while (m_read_reports.Pop(m_last_input_report))
while (GetNextReport(&m_last_input_report))
{
if (!IsDataReport(m_last_input_report))
{
@ -452,26 +500,16 @@ void Wiimote::Prepare()
bool Wiimote::PrepareOnThread()
{
// core buttons, no continuous reporting
// TODO: use the structs..
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 0,
// Set reporting mode to non-continuous core buttons and turn on rumble.
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1,
u8(InputReportID::ReportCore)};
// Set the active LEDs and turn on rumble.
u8 static led_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::LED), 0};
led_report[2] = u8(u8(LED::LED_1) << (m_index % WIIMOTE_BALANCE_BOARD) | 0x1);
// Turn off rumble
u8 static const rumble_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::Rumble), 0};
// Request status report
// Request status and turn off rumble.
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::RequestStatus), 0};
// TODO: check for sane response?
return (IOWrite(mode_report, sizeof(mode_report)) && IOWrite(led_report, sizeof(led_report)) &&
(Common::SleepCurrentThread(200), IOWrite(rumble_report, sizeof(rumble_report))) &&
IOWrite(req_status_report, sizeof(req_status_report)));
return IOWrite(mode_report, sizeof(mode_report)) &&
(Common::SleepCurrentThread(200), IOWrite(req_status_report, sizeof(req_status_report)));
}
void Wiimote::EmuStart()
@ -499,32 +537,20 @@ void Wiimote::EmuPause()
DisablePowerAssertionInternal();
}
static unsigned int CalculateConnectedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
unsigned int connected_wiimotes = 0;
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
if (g_wiimotes[i])
++connected_wiimotes;
return connected_wiimotes;
}
static unsigned int CalculateWantedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
// Figure out how many real Wiimotes are required
unsigned int wanted_wiimotes = 0;
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
if (WiimoteCommon::GetSource(i) == WiimoteSource::Real && !g_wiimotes[i])
++wanted_wiimotes;
return wanted_wiimotes;
}
static unsigned int CalculateWantedBB()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
unsigned int wanted_bb = 0;
if (WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) == WiimoteSource::Real &&
!g_wiimotes[WIIMOTE_BALANCE_BOARD])
@ -564,14 +590,63 @@ bool WiimoteScanner::IsReady() const
static void CheckForDisconnectedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
if (g_wiimotes[i] && !g_wiimotes[i]->IsConnected())
HandleWiimoteDisconnect(i);
}
void WiimoteScanner::PoolThreadFunc()
{
Common::SetCurrentThreadName("Wiimote Pool Thread");
// Toggle between 1010 and 0101.
u8 led_value = 0b1010;
auto next_time = std::chrono::steady_clock::now();
while (m_scan_thread_running.IsSet())
{
std::this_thread::sleep_until(next_time);
next_time += std::chrono::milliseconds(250);
std::lock_guard lk(g_wiimotes_mutex);
// Remove stale pool entries.
for (auto it = g_wiimote_pool.begin(); it != g_wiimote_pool.end();)
{
if (!it->wiimote->IsConnected())
{
INFO_LOG(WIIMOTE, "Removing disconnected wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else if (it->IsExpired())
{
INFO_LOG(WIIMOTE, "Removing expired wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else
{
++it;
}
}
// Make wiimote pool LEDs dance.
for (auto& wiimote : g_wiimote_pool)
{
OutputReportLeds leds = {};
leds.leds = led_value;
wiimote.wiimote->QueueReport(leds);
}
led_value ^= 0b1111;
}
}
void WiimoteScanner::ThreadFunc()
{
std::thread pool_thread(&WiimoteScanner::PoolThreadFunc, this);
Common::SetCurrentThreadName("Wiimote Scanning Thread");
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has started.");
@ -594,22 +669,28 @@ void WiimoteScanner::ThreadFunc()
{
m_scan_mode_changed_event.WaitFor(std::chrono::milliseconds(500));
ProcessWiimotePool();
// Does stuff needed to detect disconnects on Windows
for (const auto& backend : m_backends)
backend->Update();
CheckForDisconnectedWiimotes();
if (m_scan_mode.load() == WiimoteScanMode::DO_NOT_SCAN)
continue;
if (!g_real_wiimotes_initialized)
continue;
// If we don't want Wiimotes in ControllerInterface, we may not need them at all.
if (!SConfig::GetInstance().connect_wiimotes_for_ciface)
{
// We don't want any remotes in passthrough mode or running in GC mode.
const bool core_running = Core::GetState() != Core::State::Uninitialized;
if (SConfig::GetInstance().m_bt_passthrough_enabled ||
(core_running && !SConfig::GetInstance().bWii))
continue;
// Does stuff needed to detect disconnects on Windows
for (const auto& backend : m_backends)
backend->Update();
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
continue;
// We don't want any remotes if we already connected everything we need.
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
continue;
}
for (const auto& backend : m_backends)
{
@ -617,7 +698,7 @@ void WiimoteScanner::ThreadFunc()
Wiimote* found_board = nullptr;
backend->FindWiimotes(found_wiimotes, found_board);
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::unique_lock wm_lk(g_wiimotes_mutex);
for (auto* wiimote : found_wiimotes)
{
@ -626,7 +707,8 @@ void WiimoteScanner::ThreadFunc()
s_known_ids.insert(wiimote->GetId());
}
TryToConnectWiimote(std::unique_ptr<Wiimote>(wiimote));
AddWiimoteToPool(std::unique_ptr<Wiimote>(wiimote));
ProcessWiimotePool();
}
if (found_board)
@ -641,32 +723,32 @@ void WiimoteScanner::ThreadFunc()
}
}
if (m_scan_mode.load() == WiimoteScanMode::SCAN_ONCE)
m_scan_mode.store(WiimoteScanMode::DO_NOT_SCAN);
// Stop scanning if not in continous mode.
auto scan_mode = WiimoteScanMode::SCAN_ONCE;
m_scan_mode.compare_exchange_strong(scan_mode, WiimoteScanMode::DO_NOT_SCAN);
}
{
std::lock_guard<std::mutex> lg(m_backends_mutex);
m_backends.clear();
}
pool_thread.join();
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has stopped.");
}
bool Wiimote::Connect(int index)
{
m_index = index;
m_need_prepare.Set();
if (!m_run_thread.IsSet())
{
m_need_prepare.Set();
m_run_thread.Set();
StartThread();
m_thread_ready_event.Wait();
}
else
{
IOWakeup();
}
return IsConnected();
}
@ -729,6 +811,11 @@ int Wiimote::GetIndex() const
return m_index;
}
void Wiimote::SetChannel(u16 channel)
{
m_channel = channel;
}
void LoadSettings()
{
std::string ini_filename = File::GetUserPath(D_CONFIG_IDX) + WIIMOTE_INI_NAME ".ini";
@ -763,8 +850,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
g_wiimote_scanner.StartThread();
}
if (SConfig::GetInstance().m_WiimoteContinuousScanning &&
!SConfig::GetInstance().m_bt_passthrough_enabled)
if (SConfig::GetInstance().m_WiimoteContinuousScanning)
g_wiimote_scanner.SetScanMode(WiimoteScanMode::CONTINUOUSLY_SCAN);
else
g_wiimote_scanner.SetScanMode(WiimoteScanMode::DO_NOT_SCAN);
@ -774,7 +860,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
{
int timeout = 100;
g_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE);
while (CalculateWantedWiimotes() > CalculateConnectedWiimotes() && timeout)
while (CalculateWantedWiimotes() && timeout)
{
Common::SleepCurrentThread(100);
timeout--;
@ -805,9 +891,13 @@ void Shutdown()
NOTICE_LOG(WIIMOTE, "WiimoteReal::Shutdown");
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
HandleWiimoteDisconnect(i);
// Release remotes from ControllerInterface and empty the pool.
ciface::Wiimote::ReleaseDevices();
g_wiimote_pool.clear();
}
void Resume()
@ -836,6 +926,13 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
return false;
}
wm->Prepare();
// Set LEDs.
OutputReportLeds led_report = {};
led_report.leds = u8(1 << (i % WIIMOTE_BALANCE_BOARD));
wm->QueueReport(led_report);
g_wiimotes[i] = std::move(wm);
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
@ -844,22 +941,6 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
return true;
}
static void TryToConnectWiimote(std::unique_ptr<Wiimote> wm)
{
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
{
if (TryToConnectWiimoteToSlot(wm, i))
return;
}
INFO_LOG(WIIMOTE, "No open slot for real wiimote, adding it to the pool.");
wm->Connect(0);
// Turn on LED 1 and 4 to make it apparant this remote is in the pool.
const u8 led_value = u8(LED::LED_1) | u8(LED::LED_4);
wm->QueueReport(OutputReportID::LED, &led_value, 1);
g_wiimote_pool.emplace_back(WiimotePoolEntry{std::move(wm)});
}
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote> wm)
{
if (TryToConnectWiimoteToSlot(wm, WIIMOTE_BALANCE_BOARD))
@ -882,14 +963,14 @@ void Refresh()
void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size)
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
if (g_wiimotes[wiimote_number])
g_wiimotes[wiimote_number]->InterruptChannel(channel_id, data, size);
}
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size)
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
if (g_wiimotes[wiimote_number])
g_wiimotes[wiimote_number]->ControlChannel(channel_id, data, size);
}
@ -946,25 +1027,17 @@ bool IsNewWiimote(const std::string& identifier)
void HandleWiimoteSourceChange(unsigned int index)
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::lock_guard wm_lk(g_wiimotes_mutex);
if (WiimoteCommon::GetSource(index) != WiimoteSource::Real)
{
if (auto removed_wiimote = std::move(g_wiimotes[index]))
{
removed_wiimote->EmuStop();
// Try to use this removed wiimote in another slot.
TryToConnectWiimote(std::move(removed_wiimote));
}
}
else if (WiimoteCommon::GetSource(index) == WiimoteSource::Real)
{
// Try to fill this slot from the pool.
if (!g_wiimote_pool.empty())
{
if (TryToConnectWiimoteToSlot(g_wiimote_pool.front().wiimote, index))
g_wiimote_pool.erase(g_wiimote_pool.begin());
}
}
if (auto removed_wiimote = std::move(g_wiimotes[index]))
AddWiimoteToPool(std::move(removed_wiimote));
ProcessWiimotePool();
}
}; // namespace WiimoteReal
void HandleWiimotesInControllerInterfaceSettingChange()
{
ProcessWiimotePool();
}
} // namespace WiimoteReal

View File

@ -65,6 +65,7 @@ public:
void Update();
bool CheckForButtonPress();
bool GetNextReport(Report* report);
Report& ProcessReadQueue();
void Read();
@ -101,8 +102,16 @@ public:
void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size);
template <typename T>
void QueueReport(const T& report)
{
QueueReport(report.REPORT_ID, &report, sizeof(report));
}
int GetIndex() const;
void SetChannel(u16 channel);
protected:
Wiimote();
@ -173,6 +182,7 @@ public:
private:
void ThreadFunc();
void PoolThreadFunc();
std::vector<std::unique_ptr<WiimoteScannerBackend>> m_backends;
mutable std::mutex m_backends_mutex;
@ -183,10 +193,13 @@ private:
std::atomic<WiimoteScanMode> m_scan_mode{WiimoteScanMode::DO_NOT_SCAN};
};
extern std::mutex g_wiimotes_mutex;
// Mutex is recursive as ControllerInterface may call AddWiimoteToPool within ProcessWiimotePool.
extern std::recursive_mutex g_wiimotes_mutex;
extern WiimoteScanner g_wiimote_scanner;
extern std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
void AddWiimoteToPool(std::unique_ptr<Wiimote>);
void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void Update(int wiimote_number);
@ -202,4 +215,7 @@ void HandleWiimoteSourceChange(unsigned int wiimote_number);
void InitAdapterClass();
#endif
void HandleWiimotesInControllerInterfaceSettingChange();
void ProcessWiimotePool();
} // namespace WiimoteReal

View File

@ -72,8 +72,6 @@ ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
CreateMainLayout();
LoadSettings();
ConnectWidgets();
OnEmulationStateChanged(Core::GetState() != Core::State::Uninitialized);
}
void ControllersWindow::CreateGamecubeLayout()
@ -157,6 +155,7 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
m_wiimote_ciface = new QCheckBox(tr("Connect Wii Remotes for Emulated Controllers"));
m_wiimote_layout->setVerticalSpacing(7);
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
@ -192,12 +191,14 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
}
int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 1, 1, 2);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_ciface, m_wiimote_layout->rowCount(), 0, 1, -1);
int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 0, 1, 3);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
}
void ControllersWindow::CreateCommonLayout()
@ -232,10 +233,15 @@ void ControllersWindow::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
&ControllersWindow::UpdateDisabledWiimoteControls);
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this, &ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this,
&WiimoteReal::HandleWiimotesInControllerInterfaceSettingChange);
connect(m_wiimote_continuous_scanning, &QCheckBox::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_common_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
@ -259,7 +265,7 @@ void ControllersWindow::ConnectWidgets()
&ControllersWindow::SaveSettings);
connect(m_wiimote_boxes[i],
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&ControllersWindow::OnWiimoteTypeChanged);
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
&ControllersWindow::OnWiimoteConfigure);
@ -273,45 +279,50 @@ void ControllersWindow::ConnectWidgets()
}
}
void ControllersWindow::OnWiimoteModeChanged(bool passthrough)
void ControllersWindow::OnWiimoteModeChanged()
{
SaveSettings();
m_wiimote_sync->setEnabled(passthrough);
m_wiimote_reset->setEnabled(passthrough);
// Make sure continuous scanning setting is applied.
WiimoteReal::Initialize(::Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const int index = m_wiimote_boxes[i]->currentIndex();
if (i < 2)
m_wiimote_pt_labels[i]->setEnabled(passthrough);
m_wiimote_labels[i]->setEnabled(!passthrough);
m_wiimote_boxes[i]->setEnabled(!passthrough);
m_wiimote_buttons[i]->setEnabled(!passthrough && index != 0 && index != 2);
}
m_wiimote_refresh->setEnabled(!passthrough);
m_wiimote_real_balance_board->setEnabled(!passthrough);
m_wiimote_speaker_data->setEnabled(!passthrough);
m_wiimote_continuous_scanning->setEnabled(!passthrough);
UpdateDisabledWiimoteControls();
}
void ControllersWindow::OnWiimoteTypeChanged(int type)
void ControllersWindow::UpdateDisabledWiimoteControls()
{
const auto* box = static_cast<QComboBox*>(QObject::sender());
const bool running = Core::GetState() != Core::State::Uninitialized;
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);
const bool running_gc = running && !SConfig::GetInstance().bWii;
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;
m_wiimote_sync->setEnabled(enable_passthrough);
m_wiimote_reset->setEnabled(enable_passthrough);
for (auto* pt_label : m_wiimote_pt_labels)
pt_label->setEnabled(enable_passthrough);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
if (m_wiimote_boxes[i] == box)
{
const int index = box->currentIndex();
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
return;
}
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
m_wiimote_boxes[i]->setEnabled(enable_emu_bt);
const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1;
m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote);
}
SaveSettings();
m_wiimote_real_balance_board->setEnabled(enable_emu_bt);
m_wiimote_speaker_data->setEnabled(enable_emu_bt);
const bool ciface_wiimotes = m_wiimote_ciface->isChecked();
m_wiimote_refresh->setEnabled((enable_emu_bt || ciface_wiimotes) &&
!m_wiimote_continuous_scanning->isChecked());
m_wiimote_continuous_scanning->setEnabled(enable_emu_bt || ciface_wiimotes);
}
void ControllersWindow::OnGCTypeChanged(int type)
@ -375,30 +386,6 @@ void ControllersWindow::OnWiimoteRefreshPressed()
WiimoteReal::Refresh();
}
void ControllersWindow::OnEmulationStateChanged(bool running)
{
const bool passthrough = SConfig::GetInstance().m_bt_passthrough_enabled;
if (!SConfig::GetInstance().bWii)
{
m_wiimote_sync->setEnabled(!running && passthrough);
m_wiimote_reset->setEnabled(!running && passthrough);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
m_wiimote_boxes[i]->setEnabled(!running && !passthrough);
}
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);
if (!SConfig::GetInstance().bWii)
{
m_wiimote_real_balance_board->setEnabled(!running && !passthrough);
m_wiimote_continuous_scanning->setEnabled(!running && !passthrough);
m_wiimote_speaker_data->setEnabled(!running && !passthrough);
}
}
void ControllersWindow::OnGCPadConfigure()
{
size_t index;
@ -489,14 +476,12 @@ void ControllersWindow::LoadSettings()
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6);
}
const WiimoteSource source = WiimoteCommon::GetSource(int(i));
m_wiimote_boxes[i]->setCurrentIndex(int(source));
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
m_wiimote_boxes[i]->setCurrentIndex(int(WiimoteCommon::GetSource(u32(i))));
}
m_wiimote_real_balance_board->setChecked(WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) ==
WiimoteSource::Real);
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
m_wiimote_ciface->setChecked(SConfig::GetInstance().connect_wiimotes_for_ciface);
m_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);
m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
@ -506,12 +491,13 @@ void ControllersWindow::LoadSettings()
else
m_wiimote_emu->setChecked(true);
OnWiimoteModeChanged(SConfig::GetInstance().m_bt_passthrough_enabled);
OnWiimoteModeChanged();
}
void ControllersWindow::SaveSettings()
{
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
SConfig::GetInstance().connect_wiimotes_for_ciface = m_wiimote_ciface->isChecked();
SConfig::GetInstance().m_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
SConfig::GetInstance().m_BackgroundInput = m_common_bg_input->isChecked();
@ -522,9 +508,8 @@ void ControllersWindow::SaveSettings()
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const auto source = WiimoteSource(m_wiimote_boxes[i]->currentIndex());
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
WiimoteCommon::SetSource(static_cast<u32>(i), source);
const int index = m_wiimote_boxes[i]->currentIndex();
WiimoteCommon::SetSource(u32(i), WiimoteSource(index));
}
UICommon::SaveWiimoteSources();

View File

@ -27,9 +27,8 @@ public:
explicit ControllersWindow(QWidget* parent);
private:
void OnEmulationStateChanged(bool running);
void OnWiimoteModeChanged(bool passthrough);
void OnWiimoteTypeChanged(int state);
void OnWiimoteModeChanged();
void UpdateDisabledWiimoteControls();
void OnGCTypeChanged(int state);
void SaveSettings();
void OnBluetoothPassthroughSyncPressed();
@ -72,6 +71,7 @@ private:
QCheckBox* m_wiimote_continuous_scanning;
QCheckBox* m_wiimote_real_balance_board;
QCheckBox* m_wiimote_speaker_data;
QCheckBox* m_wiimote_ciface;
QPushButton* m_wiimote_refresh;
// Common

View File

@ -806,9 +806,8 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;
m_state *= Common::Matrix33::FromQuaternion(angular_velocity.x / -INDICATOR_UPDATE_FREQ / 2,
angular_velocity.y / INDICATOR_UPDATE_FREQ / 2,
angular_velocity.z / -INDICATOR_UPDATE_FREQ / 2, 1);
m_state *= WiimoteEmu::GetMatrixFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
INDICATOR_UPDATE_FREQ);
// Reset orientation when stable for a bit:
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;

View File

@ -50,6 +50,8 @@ add_library(inputcommon
ControllerInterface/ControllerInterface.h
ControllerInterface/Device.cpp
ControllerInterface/Device.h
ControllerInterface/Wiimote/Wiimote.cpp
ControllerInterface/Wiimote/Wiimote.h
ControlReference/ControlReference.cpp
ControlReference/ControlReference.h
ControlReference/ExpressionParser.cpp

View File

@ -50,7 +50,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the yaw axis.")},
15, 0, 180);
15, 0, 360);
AddSetting(&m_pitch_setting,
// i18n: Refers to an amount of rotational movement about the "pitch" axis.
@ -59,7 +59,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the pitch axis.")},
15, 0, 180);
15, 0, 360);
AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);

View File

@ -7,6 +7,7 @@
#include <algorithm>
#include "Common/Logging/Log.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#ifdef CIFACE_USE_WIN32
#include "InputCommon/ControllerInterface/Win32/Win32.h"
@ -131,7 +132,8 @@ void ControllerInterface::RefreshDevices()
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
ciface::DualShockUDPClient::PopulateDevices();
#endif
ciface::Wiimote::PopulateDevices();
WiimoteReal::ProcessWiimotePool();
m_is_populating_devices = false;
InvokeDevicesChangedCallbacks();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,276 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <chrono>
#include <memory>
#include <vector>
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Camera.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/MotionPlus.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ciface::Wiimote
{
using namespace WiimoteCommon;
void AddDevice(std::unique_ptr<WiimoteReal::Wiimote>);
void ReleaseDevices(std::optional<u32> count = std::nullopt);
class Device final : public Core::Device
{
public:
Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote);
~Device();
std::string GetName() const override;
std::string GetSource() const override;
void UpdateInput() override;
private:
using Clock = std::chrono::steady_clock;
enum class ExtensionID
{
Nunchuk,
Classic,
Unsupported,
};
class MotionPlusState
{
public:
void SetCalibrationData(const WiimoteEmu::MotionPlus::CalibrationData&);
void ProcessData(const WiimoteEmu::MotionPlus::DataFormat&);
using PassthroughMode = WiimoteEmu::MotionPlus::PassthroughMode;
// State is unknown by default.
std::optional<PassthroughMode> current_mode;
// The last known state of the passthrough port flag.
// Used to detect passthrough extension port events.
std::optional<bool> passthrough_port;
Common::Vec3 gyro_data = {};
std::optional<WiimoteEmu::MotionPlus::CalibrationBlocks> calibration;
private:
// Used to perform realtime calibration.
std::optional<Common::Vec3> m_dynamic_calibration = {};
Common::Vec3 m_new_dynamic_calibration = {};
u32 m_new_calibration_frames = 0;
};
struct NunchukState
{
using CalibrationData = WiimoteEmu::Nunchuk::CalibrationData;
void SetCalibrationData(const CalibrationData&);
void ProcessData(const WiimoteEmu::Nunchuk::DataFormat&);
Common::Vec2 stick = {};
Common::Vec3 accel = {};
u8 buttons = 0;
struct Calibration
{
CalibrationData::AccelCalibration accel;
CalibrationData::StickCalibration stick;
};
std::optional<Calibration> calibration;
};
struct ClassicState
{
using CalibrationData = WiimoteEmu::Classic::CalibrationData;
void SetCalibrationData(const CalibrationData&);
void ProcessData(const WiimoteEmu::Classic::DataFormat&);
std::array<Common::Vec2, 2> sticks = {};
std::array<float, 2> triggers = {};
u16 buttons = 0;
struct Calibration
{
CalibrationData::StickCalibration left_stick;
CalibrationData::StickCalibration right_stick;
CalibrationData::TriggerCalibration left_trigger;
CalibrationData::TriggerCalibration right_trigger;
};
std::optional<Calibration> calibration;
};
struct IRState
{
static u32 GetDesiredIRSensitivity();
void ProcessData(const std::array<WiimoteEmu::IRBasic, 2>&);
bool IsFullyConfigured() const;
u32 current_sensitivity = u32(-1);
bool enabled = false;
bool mode_set = false;
// Average of visible IR "objects".
Common::Vec2 center_position = {};
bool is_hidden = true;
};
class ReportHandler
{
public:
enum class HandlerResult
{
Handled,
NotHandled,
};
ReportHandler(Clock::time_point expired_time);
template <typename R, typename T>
void AddHandler(std::function<R(const T&)>);
HandlerResult TryToHandleReport(const WiimoteReal::Report& report);
bool IsExpired() const;
private:
const Clock::time_point m_expired_time;
std::vector<std::function<HandlerResult(const WiimoteReal::Report& report)>> m_callbacks;
};
using AckReportHandler = std::function<ReportHandler::HandlerResult(const InputReportAck& reply)>;
static AckReportHandler MakeAckHandler(OutputReportID report_id,
std::function<void(WiimoteCommon::ErrorCode)> callback);
// TODO: Make parameter const. (need to modify DataReportManipulator)
void ProcessInputReport(WiimoteReal::Report& report);
void ProcessMotionPlusExtensionData(const u8* data, u32 size);
void ProcessNormalExtensionData(const u8* data, u32 size);
void ProcessExtensionEvent(bool connected);
void ProcessExtensionID(u8 id_0, u8 id_4, u8 id_5);
void ProcessStatusReport(const InputReportStatus&);
void RunTasks();
bool IsPerformingTask() const;
template <typename T>
void QueueReport(T&& report, std::function<void(ErrorCode)> ack_callback = {});
template <typename... T>
void AddReportHandler(T&&... callbacks);
using ReadResponse = std::optional<std::vector<u8>>;
void ReadData(AddressSpace space, u8 slave, u16 address, u16 size,
std::function<void(ReadResponse)> callback);
void AddReadDataReplyHandler(AddressSpace space, u8 slave, u16 address, u16 size,
std::vector<u8> starting_data,
std::function<void(ReadResponse)> callback);
template <typename T = std::initializer_list<u8>, typename C>
void WriteData(AddressSpace space, u8 slave, u16 address, T&& data, C&& callback);
void ReadActiveExtensionID();
void SetIRSensitivity(u32 level);
void ConfigureSpeaker();
void ConfigureIRCamera();
u8 GetDesiredLEDValue() const;
void TriggerMotionPlusModeChange();
void TriggerMotionPlusCalibration();
bool IsMotionPlusStateKnown() const;
bool IsMotionPlusActive() const;
bool IsMotionPlusInDesiredMode() const;
bool IsWaitingForMotionPlus() const;
void WaitForMotionPlus();
void HandleMotionPlusNonResponse();
void UpdateRumble();
void UpdateOrientation();
void UpdateExtensionNumberInput();
std::unique_ptr<WiimoteReal::Wiimote> m_wiimote;
// Buttons.
DataReportManipulator::CoreData m_core_data = {};
// Accelerometer.
Common::Vec3 m_accel_data = {};
std::optional<AccelCalibrationData::Calibration> m_accel_calibration;
// Pitch, Roll, Yaw inputs.
Common::Vec3 m_rotation_inputs = {};
MotionPlusState m_mplus_state = {};
NunchukState m_nunchuk_state = {};
ClassicState m_classic_state = {};
IRState m_ir_state = {};
// Used to poll for M+ periodically and wait for it to reset.
Clock::time_point m_mplus_wait_time = Clock::now();
// The desired mode is set based on the attached normal extension.
std::optional<MotionPlusState::PassthroughMode> m_mplus_desired_mode;
// Status report is requested every so often to update the battery level.
Clock::time_point m_status_outdated_time = Clock::now();
u8 m_battery = 0;
u8 m_leds = 0;
bool m_speaker_configured = false;
// The last known state of the extension port status flag.
// Used to detect extension port events.
std::optional<bool> m_extension_port;
// Note this refers to the passthrough extension when M+ is active.
std::optional<ExtensionID> m_extension_id;
// Rumble state must be saved to set the proper flag in every output report.
bool m_rumble = false;
// For pulse of rumble motor to simulate multiple levels.
ControlState m_rumble_level = 0;
Clock::time_point m_last_rumble_change = Clock::now();
// Assume mode is disabled so one gets set.
InputReportID m_reporting_mode = InputReportID::ReportDisabled;
// Used only to provide a value for a specialty "input". (for attached extension passthrough)
WiimoteEmu::ExtensionNumber m_extension_number_input = WiimoteEmu::ExtensionNumber::NONE;
bool m_mplus_attached_input = false;
// Holds callbacks for output report replies.
std::list<ReportHandler> m_report_handlers;
// World rotation. (used to rotate IR data and provide pitch, roll, yaw inputs)
Common::Matrix33 m_orientation = Common::Matrix33::Identity();
Clock::time_point m_last_report_time = Clock::now();
};
} // namespace ciface::Wiimote

View File

@ -75,6 +75,7 @@
<ClCompile Include="ControlReference\ExpressionParser.cpp" />
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
<ClCompile Include="ControllerInterface\Win32\Win32.cpp" />
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp" />
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" />
<ClCompile Include="ControlReference\FunctionExpression.cpp" />
<ClCompile Include="GCAdapter.cpp">
@ -122,6 +123,7 @@
<ClInclude Include="ControlReference\ExpressionParser.h" />
<ClInclude Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.h" />
<ClInclude Include="ControllerInterface\Win32\Win32.h" />
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h" />
<ClInclude Include="ControllerInterface\XInput\XInput.h" />
<ClInclude Include="GCAdapter.h" />
<ClInclude Include="GCPadStatus.h" />
@ -139,4 +141,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -110,6 +110,9 @@
<ClCompile Include="ControllerInterface\Win32\Win32.cpp">
<Filter>ControllerInterface\Win32</Filter>
</ClCompile>
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp">
<Filter>ControllerInterface\Wiimote</Filter>
</ClCompile>
<ClCompile Include="ControlReference\ExpressionParser.cpp">
<Filter>ControllerInterface</Filter>
</ClCompile>
@ -218,6 +221,9 @@
<ClInclude Include="ControllerInterface\Win32\Win32.h">
<Filter>ControllerInterface\Win32</Filter>
</ClInclude>
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h">
<Filter>ControllerInterface\Wiimote</Filter>
</ClInclude>
<ClInclude Include="ControlReference\ExpressionParser.h">
<Filter>ControllerInterface</Filter>
</ClInclude>
@ -248,4 +254,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>