diff --git a/Docs/Switch2Kit.md b/Docs/Switch2Kit.md index 55e11d8bd598..3120dbd54266 100644 --- a/Docs/Switch2Kit.md +++ b/Docs/Switch2Kit.md @@ -10,8 +10,9 @@ Switch2Kit's in-process virtual gamepads. No console emulation code is changed. Use a Dolphin application built with this option (not an ordinary upstream build). Close other applications that are managing the same controller. In Dolphin's -Controller Settings, click **Find Switch 2 Controllers**, allow Bluetooth access, -and hold Sync on the wireless controller. Discovery lasts 60 seconds. +Controller Settings, find the **Switch 2 Controllers** section above **Common**. +Click **Find Controllers**, allow Bluetooth access, and hold Sync on the wireless +controller. Discovery lasts 60 seconds. Choose the connected **Switch2Kit GameCube** or **Switch2Kit Pro Controller 2** in the physical-controller dropdown beside the desired GameCube port. This selects @@ -21,6 +22,10 @@ can be assigned to only one active Standard Controller port through this shortcu set its old port to None before moving it. Other controller types and backends continue to use Configure normally. +Pairing stays separate from the emulated controller type: **Standard Controller** +is what the game sees, while the adjacent dropdown selects the physical input +device. Changing the type does not start Bluetooth discovery. + In **Configure**, **Use Recommended Mapping** applies the same mapping to the selected supported device. It is explicit: selecting a device, refreshing the list, or reconnecting a controller does not overwrite bindings. Replacing custom buttons, @@ -38,7 +43,7 @@ This is not wired GameCube USB-adapter mode. ### Automatic connection and recovery -Enable **Automatically connect Switch 2 controllers** in Controller Settings. +Enable **Automatically connect** in the **Switch 2 Controllers** section. This starts listening now and saves your choice for future Dolphin launches. After initial pairing, turn the controller on again after a long pause: Dolphin can rediscover it without reopening settings or pressing Find. Discovery runs on the @@ -50,7 +55,7 @@ and merely launching Dolphin does not start Bluetooth or request permission. With it on, Dolphin starts once on the main run loop after SDL initialization. The status distinguishes continuous listening from a finite manual search. -**Disconnect Switch 2 Controllers** stops input and discovery for the current +**Disconnect All** in that section stops input and discovery for the current session, even with this option checked. Polling, returning to the app, resuming a game or reopening settings cannot undo that explicit stop. Use Find or re-enable the option to resume; the saved option still applies on the next app launch. diff --git a/Readme.md b/Readme.md index 6dd2c3bc8286..42dc04643c6e 100644 --- a/Readme.md +++ b/Readme.md @@ -18,11 +18,11 @@ These are development builds, not notarized releases. For an unverified-develope ### Connect and play -1. Turn on your Mac's Bluetooth and close other apps managing the controller, including the Switch2Kit dashboard or Cemu. In Dolphin, open **Controllers** (Controller Settings), click **Find Switch 2 Controllers**, allow Bluetooth access, and hold the controller's **Sync** button until its player lights sweep. The search lasts 60 seconds; click Find again to retry. +1. Turn on your Mac's Bluetooth and close other apps managing the controller, including the Switch2Kit dashboard or Cemu. In Dolphin, open **Controllers** (Controller Settings). In the **Switch 2 Controllers** section, click **Find Controllers**, allow Bluetooth access, and hold the controller's **Sync** button until its player lights sweep. The search lasts 60 seconds; click Find again to retry. 2. Beside the desired **GameCube port**, choose **Switch2Kit GameCube** or **Switch2Kit Pro Controller 2** in the physical-controller dropdown. Dolphin selects **Standard Controller** and applies the button, stick, trigger, and rumble mappings automatically. This is not **GameCube Adapter for Wii U** mode. 3. Open that port's **Configure** window to check button presses and releases, sticks, and triggers, then open your GameCube game. On the NSO GameCube controller, partial L/R travel and the full-click buttons are separate inputs. Pro Controller ZL/ZR are on/off and cannot reproduce an analog squeeze. -For automatic reconnection on later launches or after a long pause, enable **Automatically connect Switch 2 controllers** in Controller Settings and turn the controller on when you return. This option is off by default; otherwise use **Find Switch 2 Controllers** each session. **Disconnect Switch 2 Controllers** stops the current session without deleting mappings. +For automatic reconnection on later launches or after a long pause, enable **Automatically connect** in the **Switch 2 Controllers** section and turn the controller on when you return. This option is off by default; otherwise use **Find Controllers** each session. **Disconnect All** in that section stops the current session without deleting mappings. For Wii games, configure an **Emulated Wii Remote** and its SDL device through Dolphin's normal Wii Remote settings; the GameCube-port shortcut above does not configure a Wii Remote or add calibrated Wii motion. Individual Joy-Con 2 halves also need normal manual bindings. diff --git a/Source/Core/DolphinQt/Config/ControllersPane.cpp b/Source/Core/DolphinQt/Config/ControllersPane.cpp index 18e025621ccf..21954a990695 100644 --- a/Source/Core/DolphinQt/Config/ControllersPane.cpp +++ b/Source/Core/DolphinQt/Config/ControllersPane.cpp @@ -7,13 +7,15 @@ #ifdef HAVE_SWITCH2KIT #include -#include +#include +#include #include #include #include #include #include +#include "DolphinQt/QtUtils/NonDefaultQPushButton.h" #include "InputCommon/ControllerInterface/SDL/Switch2Kit.h" #endif @@ -32,56 +34,55 @@ void ControllersPane::CreateMainLayout() auto* const gamecube_controllers = new GamecubeControllersWidget(this); m_wiimote_controllers = new WiimoteControllersWidget(this); - auto* const common = new CommonControllersWidget(this); layout->addWidget(gamecube_controllers); layout->addWidget(m_wiimote_controllers); +#ifdef HAVE_SWITCH2KIT + layout->addWidget(CreateSwitch2ControllersBox()); +#endif + auto* const common = new CommonControllersWidget(this); layout->addWidget(common); + layout->addStretch(1); +} + #ifdef HAVE_SWITCH2KIT - auto* const actions = new QHBoxLayout; - auto* const find = new QPushButton(tr("Find Switch 2 Controllers"), this); - auto* const stop = new QPushButton(tr("Disconnect Switch 2 Controllers"), this); - actions->addWidget(find); - actions->addWidget(stop); - layout->addLayout(actions); - auto* const auto_connect = new QCheckBox(tr("Automatically connect Switch 2 controllers"), this); +QGroupBox* ControllersPane::CreateSwitch2ControllersBox() +{ + // Discovery manages physical input devices, not emulated controller types or port mappings. + auto* const box = new QGroupBox(tr("Switch 2 Controllers"), this); + auto* const layout = new QGridLayout(box); + layout->setVerticalSpacing(7); + layout->setColumnStretch(0, 1); + + auto* const auto_connect = new QCheckBox(tr("Automatically connect"), box); + auto_connect->setAccessibleName(tr("Automatically connect Switch 2 controllers")); auto_connect->setToolTip( - tr("Listen for available supported controllers while Dolphin is open, including after a " - "controller powers off. This uses Bluetooth and starts automatically on future launches. " - "Disconnect stops it until you use Find or restart Dolphin. Mappings are not changed.")); - auto_connect->setChecked(ciface::SDL::GetSwitch2KitStatus().auto_connect); - layout->addWidget(auto_connect); - auto* const status = new QLabel(this); + tr("Listen for supported controllers while Dolphin is open, including on future launches. " + "This uses Bluetooth. Turning this off keeps connected controllers and saved mappings.")); + auto* const find = new NonDefaultQPushButton(tr("Find Controllers"), box); + find->setAccessibleName(tr("Find Switch 2 controllers")); + find->setToolTip(tr("Search for supported Switch 2 controllers. Hold the controller's SYNC " + "button for first-time pairing.")); + auto* const stop = new NonDefaultQPushButton(tr("Disconnect All"), box); + stop->setAccessibleName(tr("Disconnect all Switch 2 controllers")); + stop->setToolTip(tr("Disconnect all Switch 2 controllers and stop searching for this session. " + "Saved mappings and the automatic connection setting are not changed.")); + layout->addWidget(auto_connect, 0, 0); + layout->addWidget(find, 0, 1); + layout->addWidget(stop, 0, 2); + + auto* const status = new QLabel(box); + status->setAccessibleName(tr("Switch 2 connection status")); status->setWordWrap(true); - layout->addWidget(status); + layout->addWidget(status, 1, 0, 1, 3); auto* const help = new QLabel( - tr("Hold Sync to pair, then choose your GameCube or Pro controller next to a port above. " - "Recommended controls and rumble are applied for you. Configure is only needed for " - "custom mappings. No separate controller app is needed."), - this); + tr("Turn on your controller to reconnect, or hold SYNC to pair. " + "Choose it next to a GameCube port above to apply recommended controls and rumble. " + "Use Configure for custom mappings."), + box); help->setWordWrap(true); - layout->addWidget(help); - connect(find, &QPushButton::clicked, this, [this] { - const int result = ciface::SDL::FindSwitch2Controllers(); - if (result != 0) - QMessageBox::warning(this, tr("Switch 2 Controllers"), - tr("Controller discovery could not start (error %1). Check Bluetooth " - "permission and close other controller apps. If disconnecting, " - "wait for it to finish before trying again.") - .arg(result)); - }); - connect(stop, &QPushButton::clicked, this, [] { ciface::SDL::StopSwitch2Controllers(); }); - connect(auto_connect, &QCheckBox::toggled, this, [this, auto_connect](bool enabled) { - const int result = ciface::SDL::SetSwitch2KitAutoConnect(enabled); - const QSignalBlocker blocker(auto_connect); - auto_connect->setChecked(ciface::SDL::GetSwitch2KitStatus().auto_connect); - if (result != 0) - QMessageBox::warning(this, tr("Switch 2 Controllers"), - tr("The automatic connection setting could not be saved or applied " - "(error %1). Check configuration access and Bluetooth permission. " - "The checkbox shows the saved choice; use Find to retry connection.") - .arg(result)); - }); + layout->addWidget(help, 2, 0, 1, 3); + const auto update_status = [status, find, stop, auto_connect] { const auto state = ciface::SDL::GetSwitch2KitStatus(); find->setEnabled(state.available && !state.stopping); @@ -100,27 +101,49 @@ void ControllersPane::CreateMainLayout() else if (state.bluetooth == ciface::SDL::Switch2KitBluetooth::Unsupported) status->setText(tr("Bluetooth is not supported on this Mac.")); else if (state.error != 0) - status->setText(tr("Controller input error %1. Use Find to retry.").arg(state.error)); + status->setText( + tr("Controller input error %1. Use Find Controllers to retry.").arg(state.error)); else if (!state.running) - status->setText(tr("Switch 2 controller support is stopped. Use Find to resume.")); + status->setText(tr("Disconnected. Use Find Controllers to connect.")); else if (state.scanning && state.auto_connect) - status->setText(tr("Listening for Switch 2 controllers. Turn it on to reconnect; " - "hold Sync for initial pairing. Connected: %1.") - .arg(state.controllers)); + status->setText(tr("Listening for controllers. Connected: %1.").arg(state.controllers)); else if (state.scanning) - status->setText( - tr("Searching for 60 seconds: hold Sync. Connected: %1.").arg(state.controllers)); + status->setText(tr("Searching for 60 seconds. Connected: %1.").arg(state.controllers)); else if (state.auto_connect) - status->setText(tr("Automatic connection is enabled. Connected Switch 2 controllers: %1.") - .arg(state.controllers)); + status->setText(tr("Automatic connection enabled. Connected: %1.").arg(state.controllers)); else - status->setText(tr("Connected Switch 2 controllers: %1. Use Find to add another.") - .arg(state.controllers)); + status->setText( + tr("Connected: %1. Use Find Controllers to add another.").arg(state.controllers)); }; - auto* const timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, update_status); + auto* const timer = new QTimer(box); + connect(timer, &QTimer::timeout, box, update_status); timer->start(500); + + connect(find, &QPushButton::clicked, box, [this, update_status] { + const int result = ciface::SDL::FindSwitch2Controllers(); + update_status(); + if (result != 0) + QMessageBox::warning(this, tr("Switch 2 Controllers"), + tr("Controller discovery could not start (error %1). Check Bluetooth " + "permission and close other controller apps. If disconnecting, " + "wait for it to finish before trying again.") + .arg(result)); + }); + connect(stop, &QPushButton::clicked, box, [update_status] { + ciface::SDL::StopSwitch2Controllers(); + update_status(); + }); + connect(auto_connect, &QCheckBox::toggled, box, [this, update_status](bool enabled) { + const int result = ciface::SDL::SetSwitch2KitAutoConnect(enabled); + update_status(); + if (result != 0) + QMessageBox::warning(this, tr("Switch 2 Controllers"), + tr("The automatic connection setting could not be saved or applied " + "(error %1). Check configuration access and Bluetooth permission. " + "The checkbox shows the saved choice; use Find Controllers to retry.") + .arg(result)); + }); update_status(); -#endif - layout->addStretch(1); + return box; } +#endif diff --git a/Source/Core/DolphinQt/Config/ControllersPane.h b/Source/Core/DolphinQt/Config/ControllersPane.h index 4b0d1371c585..6bb1f2352247 100644 --- a/Source/Core/DolphinQt/Config/ControllersPane.h +++ b/Source/Core/DolphinQt/Config/ControllersPane.h @@ -5,6 +5,7 @@ #include +class QGroupBox; class WiimoteControllersWidget; class ControllersPane final : public QWidget @@ -15,6 +16,9 @@ class ControllersPane final : public QWidget private: void CreateMainLayout(); +#ifdef HAVE_SWITCH2KIT + QGroupBox* CreateSwitch2ControllersBox(); +#endif WiimoteControllersWidget* m_wiimote_controllers; }; diff --git a/Tools/test_switch2kit_autoconnect.py b/Tools/test_switch2kit_autoconnect.py index 45e9813977b9..3f060949f6f3 100644 --- a/Tools/test_switch2kit_autoconnect.py +++ b/Tools/test_switch2kit_autoconnect.py @@ -31,9 +31,24 @@ def test_checkbox_updates_do_not_start_or_remap_controllers(self): update = pane.split("const auto update_status =", 1)[1].split("auto* const timer", 1)[0] self.assertIn("QSignalBlocker", update) self.assertIn("state.scanning && state.auto_connect", update) - for action in ("FindSwitch2Controllers", "SetSwitch2KitAutoConnect", "Mapping::Apply"): + for action in ("FindSwitch2Controllers", "StopSwitch2Controllers", + "SetSwitch2KitAutoConnect", "Mapping::Apply"): self.assertNotIn(action, update) + def test_section_initialization_does_not_change_connection_policy(self): + pane = self.read("Source/Core/DolphinQt/Config/ControllersPane.cpp") + initialization = pane.split("connect(find,", 1)[0] + for action in ("FindSwitch2Controllers(", "StopSwitch2Controllers(", + "SetSwitch2KitAutoConnect(", "StartSwitch2KitAutoConnect(", "Mapping::Apply"): + self.assertNotIn(action, initialization) + + def test_user_actions_refresh_the_displayed_backend_state(self): + pane = self.read("Source/Core/DolphinQt/Config/ControllersPane.cpp") + # Reflect explicit stop and saved-setting failures before the next timer tick. + for action in ("FindSwitch2Controllers()", "StopSwitch2Controllers()", + "SetSwitch2KitAutoConnect(enabled)"): + self.assertIn(f"{action};\n update_status();", pane) + def test_polling_and_initialization_never_start_bluetooth(self): native = self.read("Source/Core/InputCommon/ControllerInterface/SDL/Switch2Kit.cpp") initialize = native.split("void InitializeSwitch2Kit()", 1)[1].split("int Find", 1)[0]