pass real wii remotes through emulated bluetooth

This commit is contained in:
patchzyy
2026-10-20 02:05:29 +02:00
parent e3f5e3a3ec
commit 209fba7f64
43 changed files with 5542 additions and 1964 deletions
+58 -81
View File
@@ -57,26 +57,16 @@ struct RuntimeUserConfig {
std::optional<bool> audioMuted;
std::optional<bool> audioMixWorker;
std::optional<bool> attenuateMusicWhenMediaPlays;
// Real Wii Remotes (with or without Nunchuk / Classic Controller) and Wii U Pro
// Controllers paired over Bluetooth, driven by SDL's HIDAPI Wii driver. The driver
// is opt-in on SDL's side, so this decides whether the runtime turns it on.
// Real Wii Remotes passed through to the game's own Bluetooth stack. Off hands
// Nintendo controllers (e.g. the Wii U Pro Controller) to SDL as plain gamepads.
std::optional<bool> wiiRemotes;
// Keep re-enumerating Bluetooth HID devices while no Wii controller is connected
// (Dolphin's "continuous scanning"), so a remote that dropped or was switched on
// after launch shows up without restarting.
std::optional<bool> wiiContinuousScan;
// Accelerometer zero-point correction for the Bluetooth Wii Remote, in g and in
// SDL's sensor frame (x right, y out of the button face, z towards the user).
// SDL's Wii driver falls back to a nominal zero point when its read of the
// remote's calibration block times out (common over Bluetooth), so this is
// measured in the overlay with the remote at rest. Stored per remote under
// [controller.wii_accel_offsets]; the old shared
// wii_accel_offset_* keys are only detected, to report that they are ignored.
std::map<std::string, std::array<double, 3>> wiiAccelOffsets;
bool hasLegacyWiiAccelOffset = false;
// Debugging aid: append every KPAD sample of the Bluetooth remote (raw and
// corrected accelerometer, buttons) to wii_accel_trace.csv next to Config.toml.
std::optional<bool> wiiAccelTrace;
// Look for remotes in pairing mode (1+2 / SYNC) while none is connected.
std::optional<bool> wiiAutoPair;
// The console's Wii Remote settings (SYSCONF BT.BAR, BT.SENS, BT.SPKV, BT.MOT).
std::optional<bool> wiiSensorBarAbove;
std::optional<int32_t> wiiIrSensitivity;
std::optional<int32_t> wiiSpeakerVolume;
std::optional<bool> wiiRumble;
std::optional<bool> networkEnabled;
std::optional<bool> discordPresenceEnabled;
// The application ID of the WiiCompiled Discord application. This is only
@@ -382,28 +372,6 @@ inline void AppendOverlayRoots(RuntimeUserConfig& config, const std::string& roo
}
// Reads every supported setting out of a parsed Config.toml document.
// [x, y, z] of finite numbers (integers allowed), else nullopt.
inline std::optional<std::array<double, 3>> ParseAccelOffset(const toml::value& value) {
if (!value.is_array() || value.as_array().size() != 3) {
return std::nullopt;
}
std::array<double, 3> offset{};
for (size_t i = 0; i < 3; ++i) {
const toml::value& element = value.as_array()[i];
if (element.is_floating()) {
offset[i] = element.as_floating();
} else if (element.is_integer()) {
offset[i] = static_cast<double>(element.as_integer());
} else {
return std::nullopt;
}
if (!std::isfinite(offset[i])) {
return std::nullopt;
}
}
return offset;
}
inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
RuntimeUserConfig config;
@@ -427,13 +395,6 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.controllerExpressions[key] = value.as_string();
}
}
if (section->contains("wii_accel_offsets") && section->at("wii_accel_offsets").is_table()) {
for (const auto& [key, value] : section->at("wii_accel_offsets").as_table()) {
if (const auto offset = ParseAccelOffset(value)) {
config.wiiAccelOffsets[key] = *offset;
}
}
}
}
config.widescreen = FindConfigValue<bool>(document, "video", "widescreen");
@@ -494,11 +455,17 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.attenuateMusicWhenMediaPlays =
FindConfigValue<bool>(document, "audio", "attenuate_music_when_media_plays");
config.wiiRemotes = FindConfigValue<bool>(document, "controller", "wii_remotes");
config.wiiContinuousScan = FindConfigValue<bool>(document, "controller", "wii_continuous_scan");
for (const char* key : {"wii_accel_offset_x", "wii_accel_offset_y", "wii_accel_offset_z"}) {
config.hasLegacyWiiAccelOffset |= FindConfigValue<double>(document, "controller", key).value_or(0.0) != 0.0;
config.wiiAutoPair = FindConfigValue<bool>(document, "controller", "wii_auto_pair");
if (const auto bar = FindConfigValue<std::string>(document, "controller", "wii_sensor_bar")) {
if (*bar == "above" || *bar == "below") config.wiiSensorBarAbove = *bar == "above";
}
config.wiiAccelTrace = FindConfigValue<bool>(document, "controller", "wii_accel_trace");
if (const auto value = FindConfigInt(document, "controller", "wii_ir_sensitivity"); value && *value >= 1 && *value <= 5) {
config.wiiIrSensitivity = *value;
}
if (const auto value = FindConfigInt(document, "controller", "wii_speaker_volume"); value && *value >= 0 && *value <= 127) {
config.wiiSpeakerVolume = *value;
}
config.wiiRumble = FindConfigValue<bool>(document, "controller", "wii_rumble");
config.networkEnabled = FindConfigValue<bool>(document, "network", "enabled");
config.discordPresenceEnabled = FindConfigValue<bool>(document, "discord", "enabled");
config.discordClientId = FindConfigValue<std::string>(document, "discord", "client_id");
@@ -849,51 +816,61 @@ inline bool AttenuateMusicWhenMediaPlays(bool fallback = false) {
return Get().attenuateMusicWhenMediaPlays.value_or(fallback);
}
// Bluetooth Wii Remotes / Wii U Pro Controllers. Read once before SDL's joystick
// subsystem comes up, so a change only takes effect on the next launch.
inline bool WiiRemotesEnabled(bool fallback = true) {
// Bluetooth Wii Remotes. Read once before SDL's joystick subsystem comes up, so a
// change only takes effect on the next launch.
inline bool WiiRemotesEnabled(bool fallback = false) {
return Get().wiiRemotes.value_or(fallback);
}
// Persists the Bluetooth Wii Remote driver switch.
inline bool SetWiiRemotesEnabled(bool value) {
Mutable().wiiRemotes = value;
return WriteSetting("controller", "wii_remotes", value ? "true" : "false");
}
// Whether to keep rescanning Bluetooth while no Wii controller is connected.
inline bool WiiContinuousScanEnabled(bool fallback = false) {
return Get().wiiContinuousScan.value_or(fallback);
inline bool WiiAutoPairEnabled(bool fallback = true) {
return Get().wiiAutoPair.value_or(fallback);
}
// Persists the continuous scanning switch.
inline bool SetWiiContinuousScanEnabled(bool value) {
Mutable().wiiContinuousScan = value;
return WriteSetting("controller", "wii_continuous_scan", value ? "true" : "false");
inline bool SetWiiAutoPairEnabled(bool value) {
Mutable().wiiAutoPair = value;
return WriteSetting("controller", "wii_auto_pair", value ? "true" : "false");
}
// Stored correction of the remote identified by `remoteKey`, if it has one.
inline std::optional<std::array<double, 3>> WiiAccelOffsetFor(const std::string& remoteKey) {
const auto it = Get().wiiAccelOffsets.find(remoteKey);
if (it == Get().wiiAccelOffsets.end()) {
return std::nullopt;
}
return it->second;
// Defaults are the console's.
inline bool WiiSensorBarAbove(bool fallback = false) {
return Get().wiiSensorBarAbove.value_or(fallback);
}
// Whether to write the per-frame accelerometer trace (off unless asked for).
inline bool WiiAccelTraceEnabled(bool fallback = false) {
return Get().wiiAccelTrace.value_or(fallback);
inline bool SetWiiSensorBarAbove(bool value) {
Mutable().wiiSensorBarAbove = value;
return WriteSetting("controller", "wii_sensor_bar", value ? "\"above\"" : "\"below\"");
}
// Persists the accelerometer correction of one remote. `remoteKey` must be a bare TOML key.
inline bool SetWiiAccelOffsetFor(const std::string& remoteKey, const std::array<double, 3>& offset) {
Mutable().wiiAccelOffsets[remoteKey] = offset;
// Always float literals, so a whole-number offset does not come back as a TOML integer.
std::ostringstream formatted;
formatted << std::fixed << std::setprecision(4) << '[' << offset[0] << ", " << offset[1] << ", " << offset[2]
<< ']';
return WriteSetting("controller.wii_accel_offsets", remoteKey, formatted.str());
inline int32_t WiiIrSensitivity(int32_t fallback = 3) {
return Get().wiiIrSensitivity.value_or(fallback);
}
inline bool SetWiiIrSensitivity(int32_t value) {
Mutable().wiiIrSensitivity = value;
return WriteSetting("controller", "wii_ir_sensitivity", std::to_string(value));
}
inline int32_t WiiSpeakerVolume(int32_t fallback = 0x59) {
return Get().wiiSpeakerVolume.value_or(fallback);
}
inline bool SetWiiSpeakerVolume(int32_t value) {
Mutable().wiiSpeakerVolume = value;
return WriteSetting("controller", "wii_speaker_volume", std::to_string(value));
}
inline bool WiiRumbleEnabled(bool fallback = true) {
return Get().wiiRumble.value_or(fallback);
}
inline bool SetWiiRumbleEnabled(bool value) {
Mutable().wiiRumble = value;
return WriteSetting("controller", "wii_rumble", value ? "true" : "false");
}
// Target frame rate for frame interpolation, or 0 to disable it.