WiimoteReal: Improve state changes and minor code cleanups.

This commit is contained in:
Jordan Woyak 2019-03-07 18:44:23 -06:00
parent 909e9322e7
commit 49218c32ed
10 changed files with 185 additions and 191 deletions

View File

@ -6,6 +6,7 @@
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
@ -137,21 +138,29 @@ void Pause()
// An L2CAP packet is passed from the Core to the Wiimote on the HID CONTROL channel.
void ControlChannel(int number, u16 channel_id, const void* data, u32 size)
{
if (g_wiimote_sources[number])
if (WIIMOTE_SRC_EMU == g_wiimote_sources[number])
{
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(number))
->ControlChannel(channel_id, data, size);
}
else
{
WiimoteReal::ControlChannel(number, channel_id, data, size);
}
}
// An L2CAP packet is passed from the Core to the Wiimote on the HID INTERRUPT channel.
void InterruptChannel(int number, u16 channel_id, const void* data, u32 size)
{
if (g_wiimote_sources[number])
if (WIIMOTE_SRC_EMU == g_wiimote_sources[number])
{
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(number))
->InterruptChannel(channel_id, data, size);
}
else
{
WiimoteReal::InterruptChannel(number, channel_id, data, size);
}
}
bool ButtonPressed(int number)
@ -215,6 +224,26 @@ unsigned int GetAttached()
void DoState(PointerWrap& p)
{
for (int i = 0; i < MAX_BBMOTES; ++i)
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(i))->DoState(p);
{
auto state_wiimote_source = u8(g_wiimote_sources[i]);
p.Do(state_wiimote_source);
if (WIIMOTE_SRC_EMU == state_wiimote_source)
{
// Sync complete state of emulated wiimotes.
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(i))->DoState(p);
}
if (p.GetMode() == PointerWrap::MODE_READ)
{
// If using a real wiimote or the save-state source does not match the current source,
// then force a reconnection on load.
if (WIIMOTE_SRC_REAL == g_wiimote_sources[i] || state_wiimote_source != g_wiimote_sources[i])
{
Connect(i, false);
Connect(i, true);
}
}
}
}
} // namespace Wiimote

View File

@ -4,6 +4,9 @@
#pragma once
#include <array>
#include <atomic>
#include "Common/Common.h"
#include "Common/CommonTypes.h"
@ -45,7 +48,7 @@ enum
WIIMOTE_SRC_REAL = 2,
};
extern unsigned int g_wiimote_sources[MAX_BBMOTES];
extern std::array<std::atomic<u32>, MAX_BBMOTES> g_wiimote_sources;
namespace Wiimote
{

View File

@ -590,9 +590,6 @@ void Wiimote::DoState(PointerWrap& p)
p.Do(m_shake_step);
p.DoMarker("Wiimote");
if (p.GetMode() == PointerWrap::MODE_READ)
RealState();
}
ExtensionNumber Wiimote::GetActiveExtensionNumber() const
@ -600,14 +597,4 @@ ExtensionNumber Wiimote::GetActiveExtensionNumber() const
return m_active_extension;
}
void Wiimote::RealState()
{
using namespace WiimoteReal;
if (g_wiimotes[m_index])
{
g_wiimotes[m_index]->SetChannel(m_reporting_channel);
}
}
} // namespace WiimoteEmu

View File

@ -20,6 +20,7 @@
#include "Core/Config/WiimoteInputSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
@ -30,7 +31,6 @@
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/Control/Output.h"
@ -503,14 +503,6 @@ void Wiimote::ControlChannel(const u16 channel_id, const void* data, u32 size)
m_reporting_channel = channel_id;
// FYI: ControlChannel is piped through WiimoteEmu before WiimoteReal just so we can sync the
// channel on state load. This is ugly.
if (WIIMOTE_SRC_REAL == g_wiimote_sources[m_index])
{
WiimoteReal::ControlChannel(m_index, channel_id, data, size);
return;
}
const auto& hidp = *reinterpret_cast<const HIDPacket*>(data);
DEBUG_LOG(WIIMOTE, "Emu ControlChannel (page: %i, type: 0x%02x, param: 0x%02x)", m_index,
@ -559,14 +551,6 @@ void Wiimote::InterruptChannel(const u16 channel_id, const void* data, u32 size)
m_reporting_channel = channel_id;
// FYI: InterruptChannel is piped through WiimoteEmu before WiimoteReal just so we can sync the
// channel on state load. This is ugly.
if (WIIMOTE_SRC_REAL == g_wiimote_sources[m_index])
{
WiimoteReal::InterruptChannel(m_index, channel_id, data, size);
return;
}
const auto& hidp = *reinterpret_cast<const HIDPacket*>(data);
switch (hidp.type)

View File

@ -35,12 +35,6 @@ class Output;
class Tilt;
} // namespace ControllerEmu
// Needed for friendship:
namespace WiimoteReal
{
class Wiimote;
} // namespace WiimoteReal
namespace WiimoteEmu
{
enum class WiimoteGroup
@ -87,8 +81,6 @@ void UpdateCalibrationDataChecksum(T& data, int cksum_bytes)
class Wiimote : public ControllerEmu::EmulatedController
{
friend class WiimoteReal::Wiimote;
public:
enum : u8
{
@ -173,8 +165,6 @@ private:
void SendDataReport();
bool ProcessReadDataRequest();
void RealState();
void SetRumble(bool on);
void CallbackInterruptChannel(const u8* data, u32 size);

View File

@ -284,7 +284,7 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
for (int i = 0; i < MAX_WIIMOTES; i++)
{
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i].get());
if (!wm)
continue;
if ([device isEqual:wm->m_btd])
@ -325,7 +325,7 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
for (int i = 0; i < MAX_WIIMOTES; i++)
{
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i].get());
if (!wm)
continue;
if ([device isEqual:wm->m_btd])

View File

@ -19,8 +19,8 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/IOAndroid.h"
#include "Core/HW/WiimoteReal/IOLinux.h"
#include "Core/HW/WiimoteReal/IOWin.h"
@ -30,14 +30,14 @@
#include "SFML/Network.hpp"
unsigned int g_wiimote_sources[MAX_BBMOTES];
std::array<std::atomic<u32>, MAX_BBMOTES> g_wiimote_sources;
namespace WiimoteReal
{
using namespace WiimoteCommon;
static void TryToConnectBalanceBoard(Wiimote*);
static void TryToConnectWiimote(Wiimote*);
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote>);
static void TryToConnectWiimote(std::unique_ptr<Wiimote>);
static void HandleWiimoteDisconnect(int index);
static bool g_real_wiimotes_initialized = false;
@ -49,18 +49,19 @@ static std::mutex s_known_ids_mutex;
std::mutex g_wiimotes_mutex;
Wiimote* g_wiimotes[MAX_BBMOTES];
std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
WiimoteScanner g_wiimote_scanner;
Wiimote::Wiimote() : m_index(), m_last_input_report(), m_channel(0), m_rumble_state()
{
}
void Wiimote::Shutdown()
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.erase(GetId());
StopThread();
ClearReadQueue();
m_write_reports.Clear();
NOTICE_LOG(WIIMOTE, "Disconnected real wiimote.");
}
// to be called from CPU thread
@ -70,9 +71,31 @@ void Wiimote::WriteReport(Report rpt)
{
bool const new_rumble_state = (rpt[2] & 0x1) != 0;
// If this is a rumble report and the rumble state didn't change, ignore.
if (rpt[1] == u8(OutputReportID::Rumble) && new_rumble_state == m_rumble_state)
return;
switch (WiimoteCommon::OutputReportID(rpt[1]))
{
case OutputReportID::Rumble:
// If this is a rumble report and the rumble state didn't change, we can drop this report.
if (new_rumble_state == m_rumble_state)
return;
break;
case OutputReportID::SpeakerEnable:
m_speaker_enable = (rpt[2] & 0x4) != 0;
break;
case OutputReportID::SpeakerMute:
m_speaker_mute = (rpt[2] & 0x4) != 0;
break;
case OutputReportID::ReportMode:
// Force non-continuous reporting for less BT traffic.
// We duplicate reports to maintain 200hz anyways.
rpt[2] &= ~0x4;
break;
default:
break;
}
m_rumble_state = new_rumble_state;
}
@ -82,42 +105,27 @@ void Wiimote::WriteReport(Report rpt)
}
// to be called from CPU thread
void Wiimote::QueueReport(u8 rpt_id, const void* data, unsigned int size)
void Wiimote::QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size)
{
auto const queue_data = static_cast<const u8*>(data);
Report rpt(size + 2);
rpt[0] = WR_SET_REPORT | BT_OUTPUT;
rpt[1] = rpt_id;
rpt[1] = u8(rpt_id);
std::copy_n(queue_data, size, rpt.begin() + 2);
WriteReport(std::move(rpt));
}
void Wiimote::DisableDataReporting()
void Wiimote::ResetDataReporting()
{
m_last_input_report.clear();
// This accomplishes very little:
// "core" reporting in non-continuous mode is a wiimote's initial state.
// FYI: This also disables rumble.
OutputReportMode rpt = {};
rpt.mode = InputReportID::ReportCore;
rpt.continuous = 0;
rpt.rumble = 0;
QueueReport(u8(OutputReportID::ReportMode), &rpt, sizeof(rpt));
}
void Wiimote::EnableDataReporting(u8 mode)
{
m_last_input_report.clear();
OutputReportMode rpt = {};
rpt.mode = InputReportID(mode);
rpt.continuous = 1;
QueueReport(u8(OutputReportID::ReportMode), &rpt, sizeof(rpt));
}
void Wiimote::SetChannel(u16 channel)
{
m_channel = channel;
QueueReport(OutputReportID::ReportMode, &rpt, sizeof(rpt));
}
void Wiimote::ClearReadQueue()
@ -136,7 +144,13 @@ void Wiimote::ControlChannel(const u16 channel, const void* const data, const u3
if (channel == 99)
{
if (m_really_disconnect)
{
DisconnectInternal();
}
else
{
EmuStop();
}
}
else
{
@ -164,8 +178,6 @@ void Wiimote::InterruptChannel(const u16 channel, const void* const data, const
auto const report_data = static_cast<const u8*>(data);
Report rpt(report_data, report_data + size);
WiimoteEmu::Wiimote* const wm =
static_cast<WiimoteEmu::Wiimote*>(::Wiimote::GetConfig()->GetController(m_index));
// Convert output DATA packets to SET_REPORT packets.
// Nintendo Wiimotes work without this translation, but 3rd
@ -187,10 +199,9 @@ void Wiimote::InterruptChannel(const u16 channel, const void* const data, const
}
}
else if (rpt[1] == u8(OutputReportID::SpeakerData) &&
(!SConfig::GetInstance().m_WiimoteEnableSpeaker ||
(!wm->m_status.speaker || wm->m_speaker_mute)))
(!SConfig::GetInstance().m_WiimoteEnableSpeaker || !m_speaker_enable || m_speaker_mute))
{
// Translate speaker data reports into rumble reports.
// Translate undesired speaker data reports into rumble reports.
rpt[1] = u8(OutputReportID::Rumble);
// Keep only the rumble bit.
rpt[2] &= 0x1;
@ -379,6 +390,8 @@ bool Wiimote::CheckForButtonPress()
if (rpt.size() >= 4)
{
const auto mode = InputReportID(rpt[1]);
// TODO: Button data could also be pulled out of non-data reports if really wanted.
if (DataReportBuilder::IsValidMode(mode))
{
auto builder = MakeDataReportManipulator(mode, rpt.data() + 2);
@ -423,49 +436,26 @@ bool Wiimote::PrepareOnThread()
void Wiimote::EmuStart()
{
DisableDataReporting();
ResetDataReporting();
EnablePowerAssertionInternal();
}
void Wiimote::EmuStop()
{
m_channel = 0;
DisableDataReporting();
NOTICE_LOG(WIIMOTE, "Stopping Wiimote data reporting.");
ResetDataReporting();
DisablePowerAssertionInternal();
}
void Wiimote::EmuResume()
{
WiimoteEmu::Wiimote* const wm =
static_cast<WiimoteEmu::Wiimote*>(::Wiimote::GetConfig()->GetController(m_index));
m_last_input_report.clear();
OutputReportMode rpt = {};
rpt.mode = wm->m_reporting_mode;
rpt.continuous = 1;
QueueReport(u8(OutputReportID::ReportMode), &rpt, sizeof(rpt));
NOTICE_LOG(WIIMOTE, "Resuming Wiimote data reporting.");
EnablePowerAssertionInternal();
}
void Wiimote::EmuPause()
{
m_last_input_report.clear();
OutputReportMode rpt = {};
rpt.mode = InputReportID::ReportCore;
rpt.continuous = 0;
QueueReport(u8(OutputReportID::ReportMode), &rpt, sizeof(rpt));
NOTICE_LOG(WIIMOTE, "Pausing Wiimote data reporting.");
DisablePowerAssertionInternal();
}
@ -569,25 +559,43 @@ void WiimoteScanner::ThreadFunc()
if (m_scan_mode.load() == WiimoteScanMode::DO_NOT_SCAN)
continue;
if (!g_real_wiimotes_initialized)
continue;
// Does stuff needed to detect disconnects on Windows
for (const auto& backend : m_backends)
backend->Update();
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
continue;
for (const auto& backend : m_backends)
{
if (CalculateWantedWiimotes() != 0 || CalculateWantedBB() != 0)
std::vector<Wiimote*> found_wiimotes;
Wiimote* found_board = nullptr;
backend->FindWiimotes(found_wiimotes, found_board);
{
std::vector<Wiimote*> found_wiimotes;
Wiimote* found_board = nullptr;
backend->FindWiimotes(found_wiimotes, found_board);
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
for (auto* wiimote : found_wiimotes)
{
if (!g_real_wiimotes_initialized)
continue;
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::for_each(found_wiimotes.begin(), found_wiimotes.end(), TryToConnectWiimote);
if (found_board)
TryToConnectBalanceBoard(found_board);
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.insert(wiimote->GetId());
}
TryToConnectWiimote(std::unique_ptr<Wiimote>(wiimote));
}
if (found_board)
{
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.insert(found_board->GetId());
}
TryToConnectBalanceBoard(std::unique_ptr<Wiimote>(found_board));
}
}
else
{
backend->Update(); // Does stuff needed to detect disconnects on Windows
}
}
@ -687,12 +695,17 @@ void LoadSettings()
secname += static_cast<char>('1' + i);
IniFile::Section& sec = *inifile.GetOrCreateSection(secname);
sec.Get("Source", &g_wiimote_sources[i], i ? WIIMOTE_SRC_NONE : WIIMOTE_SRC_EMU);
unsigned int source = 0;
sec.Get("Source", &source, i ? WIIMOTE_SRC_NONE : WIIMOTE_SRC_EMU);
g_wiimote_sources[i] = source;
}
std::string secname("BalanceBoard");
IniFile::Section& sec = *inifile.GetOrCreateSection(secname);
sec.Get("Source", &g_wiimote_sources[WIIMOTE_BALANCE_BOARD], WIIMOTE_SRC_NONE);
unsigned int bb_source = 0;
sec.Get("Source", &bb_source, WIIMOTE_SRC_NONE);
g_wiimote_sources[WIIMOTE_BALANCE_BOARD] = bb_source;
}
// config dialog calls this when some settings change
@ -700,7 +713,6 @@ void Initialize(::Wiimote::InitializeMode init_mode)
{
if (!g_real_wiimotes_initialized)
{
s_known_ids.clear();
g_wiimote_scanner.StartThread();
}
@ -776,83 +788,70 @@ void ChangeWiimoteSource(unsigned int index, int source)
}
g_wiimote_sources[index] = source;
{
// kill real connection (or swap to different slot)
// Kill real wiimote connection (if any) (or swap to different slot)
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
Wiimote* wm = g_wiimotes[index];
if (wm)
if (auto removed_wiimote = std::move(g_wiimotes[index]))
{
g_wiimotes[index] = nullptr;
// First see if we can use this real Wiimote in another slot.
TryToConnectWiimote(wm);
// See if we can use this real Wiimote in another slot.
// Otherwise it will be disconnected.
TryToConnectWiimote(std::move(removed_wiimote));
}
// else, just disconnect the Wiimote
HandleWiimoteDisconnect(index);
}
// reconnect to the emulator
// Reconnect to the emulator.
Core::RunAsCPUThread([index, previous_source, source] {
if (previous_source != WIIMOTE_SRC_NONE)
::Wiimote::Connect(index, false);
if (source & WIIMOTE_SRC_EMU)
if (source == WIIMOTE_SRC_EMU)
::Wiimote::Connect(index, true);
});
}
// Called from the Wiimote scanner thread
static bool TryToConnectWiimoteToSlot(Wiimote* wm, unsigned int i)
// Called from the Wiimote scanner thread (or UI thread on source change)
static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int i)
{
if (WIIMOTE_SRC_REAL & g_wiimote_sources[i] && !g_wiimotes[i])
if (WIIMOTE_SRC_REAL != g_wiimote_sources[i] || g_wiimotes[i])
return false;
if (!wm->Connect(i))
{
if (wm->Connect(i))
{
NOTICE_LOG(WIIMOTE, "Connected to Wiimote %i.", i + 1);
g_wiimotes[i] = wm;
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.insert(wm->GetId());
}
return true;
ERROR_LOG(WIIMOTE, "Failed to connect real wiimote.");
return false;
}
return false;
g_wiimotes[i] = std::move(wm);
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
NOTICE_LOG(WIIMOTE, "Connected real wiimote to slot %i.", i + 1);
return true;
}
static void TryToConnectWiimote(Wiimote* wm)
static void TryToConnectWiimote(std::unique_ptr<Wiimote> wm)
{
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
{
if (TryToConnectWiimoteToSlot(wm, i))
{
wm = nullptr;
break;
}
return;
}
delete wm;
NOTICE_LOG(WIIMOTE, "No open slot for real wiimote.");
}
static void TryToConnectBalanceBoard(Wiimote* wm)
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote> wm)
{
if (TryToConnectWiimoteToSlot(wm, WIIMOTE_BALANCE_BOARD))
{
wm = nullptr;
}
delete wm;
return;
NOTICE_LOG(WIIMOTE, "No open slot for real balance board.");
}
static void HandleWiimoteDisconnect(int index)
{
Wiimote* wm = nullptr;
std::swap(wm, g_wiimotes[index]);
if (wm)
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.erase(wm->GetId());
delete wm;
NOTICE_LOG(WIIMOTE, "Disconnected Wiimote %i.", index + 1);
}
g_wiimotes[index] = nullptr;
}
// This is called from the GUI thread

View File

@ -5,6 +5,7 @@
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@ -105,19 +106,19 @@ public:
void Prepare();
bool PrepareOnThread();
void DisableDataReporting();
void EnableDataReporting(u8 mode);
void SetChannel(u16 channel);
void ResetDataReporting();
void QueueReport(u8 rpt_id, const void* data, unsigned int size);
void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size);
int GetIndex() const;
protected:
Wiimote();
int m_index;
Report m_last_input_report;
u16 m_channel;
Wiimote() = default;
int m_index = 0;
Report m_last_input_report = {};
u16 m_channel = 0;
// If true, the Wiimote will be really disconnected when it is disconnected by Dolphin.
// In any other case, data reporting is not paused to allow reconnecting on any button press.
// This is not enabled on all platforms as connecting a Wiimote can be a pain on some platforms.
@ -133,7 +134,12 @@ private:
void ThreadFunc();
bool m_rumble_state;
// We track the speaker state to convert unnecessary speaker data into rumble reports.
bool m_speaker_enable = false;
bool m_speaker_mute = false;
// And we track the rumble state to drop unnecessary rumble reports.
bool m_rumble_state = false;
std::thread m_wiimote_thread;
// Whether to keep running the thread.
@ -188,7 +194,7 @@ private:
extern std::mutex g_wiimotes_mutex;
extern WiimoteScanner g_wiimote_scanner;
extern Wiimote* g_wiimotes[MAX_BBMOTES];
extern std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
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);

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 104; // Last changed in PR 7806
static const u32 STATE_VERSION = 105; // Last changed in PR 7871
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,
@ -177,10 +177,6 @@ static void DoState(PointerWrap& p)
g_video_backend->DoState(p);
p.DoMarker("video_backend");
if (SConfig::GetInstance().bWii)
Wiimote::DoState(p);
p.DoMarker("Wiimote");
PowerPC::DoState(p);
p.DoMarker("PowerPC");
// CoreTiming needs to be restored before restoring Hardware because
@ -189,6 +185,9 @@ static void DoState(PointerWrap& p)
p.DoMarker("CoreTiming");
HW::DoState(p);
p.DoMarker("HW");
if (SConfig::GetInstance().bWii)
Wiimote::DoState(p);
p.DoMarker("Wiimote");
Movie::DoState(p);
p.DoMarker("Movie");
Gecko::DoState(p);

View File

@ -496,11 +496,8 @@ void ControllersWindow::SaveSettings()
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const int index = m_wiimote_boxes[i]->currentIndex();
g_wiimote_sources[i] = index;
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
if (Core::IsRunning())
WiimoteReal::ChangeWiimoteSource(static_cast<u32>(i), index);
WiimoteReal::ChangeWiimoteSource(static_cast<u32>(i), index);
}
UICommon::SaveWiimoteSources();