From d7f0d679047e9fe87de6b36cdd7f2be8b9800477 Mon Sep 17 00:00:00 2001 From: Gloria <32610623+yeah-its-gloria@users.noreply.github.com> Date: Wed, 6 Sep 2023 04:59:50 +0200 Subject: [PATCH] Add a pairing utility for Wiimotes to Cemu (#941) --- src/gui/CMakeLists.txt | 6 + src/gui/MainWindow.cpp | 1 + src/gui/PairingDialog.cpp | 236 +++++++++++++++++++++ src/gui/PairingDialog.h | 38 ++++ src/gui/input/panels/WiimoteInputPanel.cpp | 24 ++- src/gui/input/panels/WiimoteInputPanel.h | 1 + 6 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 src/gui/PairingDialog.cpp create mode 100644 src/gui/PairingDialog.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 90dc91c..19ce95d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -97,6 +97,8 @@ add_library(CemuGui MemorySearcherTool.h PadViewFrame.cpp PadViewFrame.h + PairingDialog.cpp + PairingDialog.h TitleManager.cpp TitleManager.h windows/PPCThreadsViewer @@ -170,3 +172,7 @@ if (ENABLE_WXWIDGETS) # PUBLIC because wx/app.h is included in CemuApp.h target_link_libraries(CemuGui PUBLIC wx::base wx::core wx::gl wx::propgrid wx::xrc) endif() + +if(WIN32) + target_link_libraries(CemuGui PRIVATE bthprops) +endif() diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 74591c5..6fa7280 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2160,6 +2160,7 @@ void MainWindow::RecreateMenu() m_memorySearcherMenuItem->Enable(false); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); + m_menuBar->Append(toolsMenu, _("&Tools")); // cpu timer speed menu diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp new file mode 100644 index 0000000..f90e6d1 --- /dev/null +++ b/src/gui/PairingDialog.cpp @@ -0,0 +1,236 @@ +#include "gui/wxgui.h" +#include "gui/PairingDialog.h" + +#if BOOST_OS_WINDOWS +#include +#endif + +wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); + +PairingDialog::PairingDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL); + m_gauge->SetValue(0); + sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + + auto* rows = new wxFlexGridSizer(0, 2, 0, 0); + rows->AddGrowableCol(1); + + m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); + rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + { + auto* right_side = new wxBoxSizer(wxHORIZONTAL); + + m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); + right_side->Add(m_cancelButton, 0, wxALL, 5); + + rows->Add(right_side, 1, wxALIGN_RIGHT, 5); + } + + sizer->Add(rows, 0, wxALL | wxEXPAND, 5); + + SetSizerAndFit(sizer); + Centre(wxBOTH); + + Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); + + m_thread = std::thread(&PairingDialog::WorkerThread, this); +} + +PairingDialog::~PairingDialog() +{ + Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); +} + +void PairingDialog::OnClose(wxCloseEvent& event) +{ + event.Skip(); + + m_threadShouldQuit = true; + if (m_thread.joinable()) + m_thread.join(); +} + +void PairingDialog::OnCancelButton(const wxCommandEvent& event) +{ + Close(); +} + +void PairingDialog::OnGaugeUpdate(wxCommandEvent& event) +{ + PairingState state = (PairingState)event.GetInt(); + + switch (state) + { + case PairingState::Pairing: + { + m_text->SetLabel(_("Found controller. Pairing...")); + m_gauge->SetValue(50); + break; + } + + case PairingState::Finished: + { + m_text->SetLabel(_("Successfully paired the controller.")); + m_gauge->SetValue(100); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::NoBluetoothAvailable: + { + m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::BluetoothFailed: + { + m_text->SetLabel(_("Failed to search for controllers.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::PairingFailed: + { + m_text->SetLabel(_("Failed to pair with the found controller.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::BluetoothUnusable: + { + m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + + default: + { + break; + } + } +} + +void PairingDialog::WorkerThread() +{ + const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; + const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; + +#if BOOST_OS_WINDOWS + const GUID bthHidGuid = {0x00001124,0x0000,0x1000,{0x80,0x00,0x00,0x80,0x5F,0x9B,0x34,0xFB}}; + + const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = + { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) + }; + + HANDLE radio = INVALID_HANDLE_VALUE; + HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); + if (radioFind == nullptr) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + BluetoothFindRadioClose(radioFind); + + BLUETOOTH_RADIO_INFO radioInfo = + { + .dwSize = sizeof(BLUETOOTH_RADIO_INFO) + }; + + DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); + if (result != ERROR_SUCCESS) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), + + .fReturnAuthenticated = FALSE, + .fReturnRemembered = FALSE, + .fReturnUnknown = TRUE, + .fReturnConnected = FALSE, + + .fIssueInquiry = TRUE, + .cTimeoutMultiplier = 5, + + .hRadio = radio + }; + + BLUETOOTH_DEVICE_INFO info = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_INFO) + }; + + while (!m_threadShouldQuit) + { + HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); + if (deviceFind == nullptr) + { + UpdateCallback(PairingState::BluetoothFailed); + return; + } + + while (!m_threadShouldQuit) + { + if (info.szName == wiimoteName || info.szName == wiiUProControllerName) + { + BluetoothFindDeviceClose(deviceFind); + + UpdateCallback(PairingState::Pairing); + + wchar_t passwd[6] = { radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5] }; + DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + UpdateCallback(PairingState::Finished); + return; + } + + BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); + if (nextDevResult == FALSE) + { + break; + } + } + + BluetoothFindDeviceClose(deviceFind); + } +#else + UpdateCallback(PairingState::BluetoothUnusable); +#endif +} + +void PairingDialog::UpdateCallback(PairingState state) +{ + auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); + event->SetInt((int)state); + wxQueueEvent(this, event); +} \ No newline at end of file diff --git a/src/gui/PairingDialog.h b/src/gui/PairingDialog.h new file mode 100644 index 0000000..6c7612d --- /dev/null +++ b/src/gui/PairingDialog.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +class PairingDialog : public wxDialog +{ +public: + PairingDialog(wxWindow* parent); + ~PairingDialog(); + +private: + enum class PairingState + { + Pairing, + Finished, + NoBluetoothAvailable, + BluetoothFailed, + PairingFailed, + BluetoothUnusable + }; + + void OnClose(wxCloseEvent& event); + void OnCancelButton(const wxCommandEvent& event); + void OnGaugeUpdate(wxCommandEvent& event); + + void WorkerThread(); + void UpdateCallback(PairingState state); + + wxStaticText* m_text; + wxGauge* m_gauge; + wxButton* m_cancelButton; + + std::thread m_thread; + bool m_threadShouldQuit = false; +}; diff --git a/src/gui/input/panels/WiimoteInputPanel.cpp b/src/gui/input/panels/WiimoteInputPanel.cpp index fbc4e32..050baad 100644 --- a/src/gui/input/panels/WiimoteInputPanel.cpp +++ b/src/gui/input/panels/WiimoteInputPanel.cpp @@ -1,5 +1,6 @@ #include "gui/input/panels/WiimoteInputPanel.h" +#include #include #include #include @@ -11,6 +12,7 @@ #include "input/emulated/WiimoteController.h" #include "gui/helpers/wxHelpers.h" #include "gui/components/wxInputDraw.h" +#include "gui/PairingDialog.h" constexpr WiimoteController::ButtonId g_kFirstColumnItems[] = { @@ -36,10 +38,18 @@ WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) bold_font.MakeBold(); auto* main_sizer = new wxBoxSizer(wxVERTICAL); + auto* horiz_main_sizer = new wxBoxSizer(wxHORIZONTAL); - auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); - extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); - extensions_sizer->AddSpacer(10); + auto* pair_button = new wxButton(this, wxID_ANY, _("Pair a Wii or Wii U controller")); + pair_button->Bind(wxEVT_BUTTON, &WiimoteInputPanel::on_pair_button, this); + horiz_main_sizer->Add(pair_button); + horiz_main_sizer->AddSpacer(10); + + auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); + horiz_main_sizer->Add(extensions_sizer, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); + + extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); + extensions_sizer->AddSpacer(10); m_motion_plus = new wxCheckBox(this, wxID_ANY, _("MotionPlus")); m_motion_plus->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); @@ -54,7 +64,7 @@ WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) m_classic->Hide(); extensions_sizer->Add(m_classic); - main_sizer->Add(extensions_sizer, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(horiz_main_sizer, 0, wxEXPAND | wxALL, 5); main_sizer->Add(new wxStaticLine(this), 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 5); m_item_sizer = new wxGridBagSizer(); @@ -254,3 +264,9 @@ void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_co set_active_device_type(wiimote->get_device_type()); } } + +void WiimoteInputPanel::on_pair_button(wxCommandEvent& event) +{ + PairingDialog pairing_dialog(this); + pairing_dialog.ShowModal(); +} diff --git a/src/gui/input/panels/WiimoteInputPanel.h b/src/gui/input/panels/WiimoteInputPanel.h index a7aed99..0810fbc 100644 --- a/src/gui/input/panels/WiimoteInputPanel.h +++ b/src/gui/input/panels/WiimoteInputPanel.h @@ -25,6 +25,7 @@ private: void on_volume_change(wxCommandEvent& event); void on_extension_change(wxCommandEvent& event); + void on_pair_button(wxCommandEvent& event); wxGridBagSizer* m_item_sizer;