From da55ae917f0dc6004a645c144cda68d71fb0c7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Hamil?= Date: Sat, 7 Feb 2026 18:14:22 +0300 Subject: [PATCH] Wiimote to GunCon3: IR healthchecks to determine connection state --- rpcs3/Emu/Io/WiimoteManager.cpp | 88 +++++++++++++++-------- rpcs3/Emu/Io/WiimoteManager.h | 4 +- rpcs3/rpcs3qt/wiimote_settings_dialog.cpp | 52 +++++++++++--- rpcs3/rpcs3qt/wiimote_settings_dialog.ui | 7 -- 4 files changed, 102 insertions(+), 49 deletions(-) diff --git a/rpcs3/Emu/Io/WiimoteManager.cpp b/rpcs3/Emu/Io/WiimoteManager.cpp index d60e467fc8..937c2b0d1f 100644 --- a/rpcs3/Emu/Io/WiimoteManager.cpp +++ b/rpcs3/Emu/Io/WiimoteManager.cpp @@ -48,7 +48,7 @@ bool wiimote_device::open(hid_device_info* info) if (initialize_ir()) { m_state.connected = true; - m_last_update = std::chrono::steady_clock::now(); + m_last_ir_check = std::chrono::steady_clock::now(); return true; } @@ -69,22 +69,23 @@ void wiimote_device::close() m_serial.clear(); } +bool wiimote_device::write_reg(u32 addr, const std::vector& data) +{ + u8 buf[22] = {0}; + buf[0] = 0x16; // Write register + buf[1] = 0x06; // Register Space + Request Acknowledgement + buf[2] = (addr >> 16) & 0xFF; + buf[3] = (addr >> 8) & 0xFF; + buf[4] = addr & 0xFF; + buf[5] = static_cast(data.size()); + std::copy(data.begin(), data.end(), &buf[6]); + if (hid_write(m_handle, buf, sizeof(buf)) < 0) return false; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + return true; +} + bool wiimote_device::initialize_ir() { - auto write_reg = [&](u32 addr, const std::vector& data) { - u8 buf[22] = {0}; - buf[0] = 0x16; // Write register - buf[1] = 0x06; // Register Space + Request Acknowledgement - buf[2] = (addr >> 16) & 0xFF; - buf[3] = (addr >> 8) & 0xFF; - buf[4] = addr & 0xFF; - buf[5] = static_cast(data.size()); - std::copy(data.begin(), data.end(), &buf[6]); - if (hid_write(m_handle, buf, sizeof(buf)) < 0) return false; - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - return true; - }; - // 1. Enable IR logic / Pixel Clock (Requesting Acknowledgement for stability) u8 ir_on1[] = { 0x13, 0x06 }; hid_write(m_handle, ir_on1, 2); @@ -114,19 +115,53 @@ bool wiimote_device::initialize_ir() return true; } +bool wiimote_device::verify_ir_health() +{ + if (!m_handle) return false; + + // Request device status to verify communication + u8 status_req[] = { 0x15, 0x00 }; + if (hid_write(m_handle, status_req, sizeof(status_req)) < 0) + { + return false; + } + + // Try to read a response within reasonable time + u8 buf[22]; + int res = hid_read_timeout(m_handle, buf, sizeof(buf), 100); + + // If we got a response, device is alive. If timeout or error, it's dead. + return res > 0; +} + bool wiimote_device::update() { if (!m_handle) return false; + // Every 3 seconds, verify IR is still working + auto now = std::chrono::steady_clock::now(); + if (now - m_last_ir_check > std::chrono::seconds(3)) + { + m_last_ir_check = now; + if (!verify_ir_health()) + { + // Device not responding - attempt to reinitialize IR + if (!initialize_ir()) + { + // Failed to reinitialize - close and mark disconnected + close(); + return false; + } + } + } + u8 buf[22]; int res; - bool received = false; // 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) { - received = true; // 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) @@ -156,19 +191,12 @@ bool wiimote_device::update() // hid_read_timeout returns -1 on error (e.g. device disconnected). if (res < 0) { - close(); - return false; - } - - if (received) - { - m_last_update = std::chrono::steady_clock::now(); - } - else if (std::chrono::steady_clock::now() - m_last_update > std::chrono::seconds(2)) - { - // No data for 2 seconds. Likely disconnected or powered off. - close(); - return false; + // Device error - try to recover once before giving up + if (!initialize_ir()) + { + close(); + return false; + } } return true; diff --git a/rpcs3/Emu/Io/WiimoteManager.h b/rpcs3/Emu/Io/WiimoteManager.h index 9af071c442..d12ee28f0f 100644 --- a/rpcs3/Emu/Io/WiimoteManager.h +++ b/rpcs3/Emu/Io/WiimoteManager.h @@ -78,9 +78,11 @@ private: std::string m_path; std::wstring m_serial; wiimote_state m_state; - std::chrono::steady_clock::time_point m_last_update; + std::chrono::steady_clock::time_point m_last_ir_check; bool initialize_ir(); + bool verify_ir_health(); + bool write_reg(u32 addr, const std::vector& data); }; class wiimote_manager diff --git a/rpcs3/rpcs3qt/wiimote_settings_dialog.cpp b/rpcs3/rpcs3qt/wiimote_settings_dialog.cpp index 437ee13a62..5a6e89dbea 100644 --- a/rpcs3/rpcs3qt/wiimote_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/wiimote_settings_dialog.cpp @@ -12,11 +12,12 @@ wiimote_settings_dialog::wiimote_settings_dialog(QWidget* parent) { ui->setupUi(this); update_list(); - connect(ui->scanButton, &QPushButton::clicked, this, [this] { update_list(); }); connect(ui->restoreDefaultsButton, &QPushButton::clicked, this, &wiimote_settings_dialog::restore_defaults); + // Timer updates both state AND device list (auto-refresh) QTimer* timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_state); + connect(timer, &QTimer::timeout, this, &wiimote_settings_dialog::update_list); timer->start(50); populate_mappings(); @@ -229,28 +230,57 @@ void wiimote_settings_dialog::update_state() void wiimote_settings_dialog::update_list() { - ui->wiimoteList->clear(); auto* wm = wiimote_manager::get_instance(); if (!wm) { - ui->wiimoteList->addItem(tr("Wiimote Manager not initialized.")); + if (ui->wiimoteList->count() != 1 || ui->wiimoteList->item(0)->text() != tr("Wiimote Manager not initialized.")) + { + ui->wiimoteList->clear(); + ui->wiimoteList->addItem(tr("Wiimote Manager not initialized.")); + } return; } - size_t count = wm->get_device_count(); - if (count == 0) + auto states = wm->get_states(); + + // Only update if the list content actually changed (avoid flicker) + if (static_cast(ui->wiimoteList->count()) != states.size()) { - ui->wiimoteList->addItem(tr("No Wiimotes found.")); - } - else - { - auto states = wm->get_states(); + int current_row = ui->wiimoteList->currentRow(); + ui->wiimoteList->clear(); + for (size_t i = 0; i < states.size(); i++) { QString label = tr("Wiimote #%1").arg(i + 1); if (!states[i].connected) label += " (" + tr("Disconnected") + ")"; ui->wiimoteList->addItem(label); } - ui->wiimoteList->setCurrentRow(0); + + if (current_row >= 0 && current_row < ui->wiimoteList->count()) + { + ui->wiimoteList->setCurrentRow(current_row); + } + else if (ui->wiimoteList->count() > 0) + { + ui->wiimoteList->setCurrentRow(0); + } + } + else + { + // Just update connection status labels without clearing + for (size_t i = 0; i < states.size(); i++) + { + QString label = tr("Wiimote #%1").arg(i + 1); + if (!states[i].connected) label += " (" + tr("Disconnected") + ")"; + + if (static_cast(i) < ui->wiimoteList->count()) + { + QListWidgetItem* item = ui->wiimoteList->item(static_cast(i)); + if (item && item->text() != label) + { + item->setText(label); + } + } + } } } diff --git a/rpcs3/rpcs3qt/wiimote_settings_dialog.ui b/rpcs3/rpcs3qt/wiimote_settings_dialog.ui index cec29d02a1..1c879fe9fd 100644 --- a/rpcs3/rpcs3qt/wiimote_settings_dialog.ui +++ b/rpcs3/rpcs3qt/wiimote_settings_dialog.ui @@ -206,13 +206,6 @@ - - - - Refresh Scan - - -