mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 03:00:14 +02:00
Add support for "none" controller mode: unplug virtual joystick and allow alternative controllers
This commit is contained in:
1 parent
171702bef4
commit
c536f0087d
12 files changed
+212
-120
No files matched your search
@@ -246,8 +246,7 @@ Touch controllers. The port is served through KPAD like a Bluetooth remote
|
||||
| Right trigger | B |
|
||||
| Right B | C (look behind) |
|
||||
| Right stick up / down | 1 / 2 |
|
||||
| Left X | − |
|
||||
| Left menu | + |
|
||||
| Left X or left menu | + |
|
||||
| Left stick | Nunchuk stick |
|
||||
| Left trigger | Z |
|
||||
| Left Y | Settings panel (not a Wii button) |
|
||||
@@ -257,7 +256,9 @@ Touch controllers. The port is served through KPAD like a Bluetooth remote
|
||||
| Left controller motion | Nunchuk accelerometer |
|
||||
|
||||
Analog inputs count as pressed past half travel. The grips, right stick left / right and the left
|
||||
stick click press no Wii button, and nothing presses HOME. C sits on right B rather than a grip
|
||||
stick click press no Wii button, and nothing presses − or HOME: Mario Kart Wii never reads −. Left
|
||||
X presses + as well as the left menu because the PlayStation VR2's controllers give no usable left
|
||||
menu, so pause would otherwise be out of reach there. C sits on right B rather than a grip
|
||||
because hand steering holds a grip down for a whole corner, and C is the game's look-behind. The
|
||||
game's Wii Remote rumble vibrates both controllers, subject to the ordinary controller-vibration
|
||||
switch.
|
||||
@@ -312,6 +313,15 @@ A/B → South/East, X/Y → West/North, index triggers → trigger axes, grips
|
||||
applies. Left Y is GameCube Y here, so clicking both thumbsticks together opens the settings panel
|
||||
instead. The right thumbstick click on its own still toggles the first-person camera.
|
||||
|
||||
`"none"` makes the controllers nothing to the game, for playing with another controller (a desktop
|
||||
gamepad, a USB wheel or a Bluetooth Wii Remote). The virtual gamepad is unplugged, so it takes no
|
||||
port and the other controllers keep theirs; the game sees a switch to or from `"none"` as that
|
||||
controller disconnecting or connecting. The plugging follows the mode on the game thread, where the
|
||||
gamepad is already written, so the pacing thread never waits on SDL's joystick lock for it. The
|
||||
controllers still open the settings panel with left Y, point at it and toggle the first-person camera
|
||||
with a right thumbstick click; they press no game button, drive no Wii Remote, cannot take hold of
|
||||
the cockpit's wheel, and the game's rumble does not reach them.
|
||||
|
||||
Bindings are suggested for `oculus/touch_controller` (Quest 2, 3 and Pro) and
|
||||
`khr/simple_controller`. `mkw_vr_wii_remote_tests` checks the accelerometer frame, the pointer
|
||||
raycast and debounce, the picture placement and the button profile without a headset.
|
||||
|
||||
@@ -302,7 +302,7 @@ class SettingsPage(
|
||||
section(R.string.section_controls) {
|
||||
choice(
|
||||
R.string.controls_mode, R.string.controls_mode_helper,
|
||||
listOf(R.string.controls_mode_wii_remote, R.string.controls_mode_gamepad),
|
||||
listOf(R.string.controls_mode_wii_remote, R.string.controls_mode_gamepad, R.string.controls_mode_none),
|
||||
read = { stringIndex(it, "vr", "controller_mode", CONTROLLER_MODES) },
|
||||
write = { c, index -> c.setString("vr", "controller_mode", CONTROLLER_MODES[index]) },
|
||||
)
|
||||
@@ -316,7 +316,7 @@ class SettingsPage(
|
||||
info(R.string.controls_map_a, activity.getString(R.string.controls_map_a_value))
|
||||
info(R.string.controls_map_b, activity.getString(R.string.controls_map_b_value))
|
||||
info(R.string.controls_map_12, activity.getString(R.string.controls_map_12_value))
|
||||
info(R.string.controls_map_minus_plus, activity.getString(R.string.controls_map_minus_plus_value))
|
||||
info(R.string.controls_map_plus, activity.getString(R.string.controls_map_plus_value))
|
||||
info(R.string.controls_map_stick, activity.getString(R.string.controls_map_stick_value))
|
||||
info(R.string.controls_map_z, activity.getString(R.string.controls_map_z_value))
|
||||
info(R.string.controls_map_c, activity.getString(R.string.controls_map_c_value))
|
||||
@@ -759,7 +759,7 @@ class SettingsPage(
|
||||
val PERFORMANCE_LEVELS = listOf("boost", "sustained_high", "sustained_low", "power_savings", "default")
|
||||
// runtime_config.h's kVrFoveationLevels, the default ("off") first.
|
||||
val FOVEATION_LEVELS = listOf("off", "low", "medium", "high")
|
||||
val CONTROLLER_MODES = listOf("wii_remote", "gamepad")
|
||||
val CONTROLLER_MODES = listOf("wii_remote", "gamepad", "none")
|
||||
val INTERPOLATION_FPS = listOf(0L, 1L, 72L, 90L, 120L)
|
||||
val RESOLUTIONS = listOf(1.0, 1.5, 2.0, 3.0, 4.0)
|
||||
val SUPPORTED_RESOLUTIONS = listOf(0.0, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0)
|
||||
|
||||
@@ -588,9 +588,10 @@
|
||||
<!-- Controls tab -->
|
||||
<string name="section_controls">Controllers</string>
|
||||
<string name="controls_mode">VR controllers</string>
|
||||
<string name="controls_mode_helper">What the Touch controllers are to the game.</string>
|
||||
<string name="controls_mode_helper">What the Touch controllers are to the game. None leaves it to another controller, such as a Bluetooth gamepad; left Y still opens the settings panel.</string>
|
||||
<string name="controls_mode_wii_remote">Wii Remote + Nunchuk</string>
|
||||
<string name="controls_mode_gamepad">Gamepad</string>
|
||||
<string name="controls_mode_none">None</string>
|
||||
<string name="controls_rumble">Vibration</string>
|
||||
<string name="controls_rumble_helper">The game\'s rumble vibrates the controllers.</string>
|
||||
<string name="section_controls_mapping">Wii Remote + Nunchuk buttons</string>
|
||||
@@ -600,8 +601,8 @@
|
||||
<string name="controls_map_b_value">Right trigger</string>
|
||||
<string name="controls_map_12">1 / 2</string>
|
||||
<string name="controls_map_12_value">Right stick up / down</string>
|
||||
<string name="controls_map_minus_plus">− / +</string>
|
||||
<string name="controls_map_minus_plus_value">Left X / left menu</string>
|
||||
<string name="controls_map_plus">+</string>
|
||||
<string name="controls_map_plus_value">Left X or left menu</string>
|
||||
<string name="controls_map_stick">Nunchuk stick</string>
|
||||
<string name="controls_map_stick_value">Left stick</string>
|
||||
<string name="controls_map_z">Z</string>
|
||||
|
||||
+3
-1
@@ -233,7 +233,9 @@ aimed at the virtual screen; see "Controllers" in `OPENXR.md` for the mapping
|
||||
and the geometry. With `controller_mode = "gamepad"` it stays an ordinary pad:
|
||||
A/B → South/East, X/Y → West/North, index triggers → trigger axes, grips →
|
||||
shoulders, thumbsticks → sticks (clicks → stick buttons), left menu → Start,
|
||||
and every existing binding, dead zone and overlay setting applies. Bindings are
|
||||
and every existing binding, dead zone and overlay setting applies. With
|
||||
`controller_mode = "none"` the virtual joystick is unplugged, leaving the ports
|
||||
to a Bluetooth gamepad; left Y still opens the settings panel. Bindings are
|
||||
suggested for `oculus/touch_controller` and `khr/simple_controller`.
|
||||
|
||||
The manifest declares hand tracking (`horizonos.permission.HAND_TRACKING`, the
|
||||
|
||||
@@ -285,11 +285,12 @@ inline bool IsSupportedVrMirrorView(std::string_view value) {
|
||||
}
|
||||
// What the tracked VR controllers are to the game: "wii_remote" is a Wii
|
||||
// Remote with a Nunchuk (motion and pointer included), "gamepad" one ordinary
|
||||
// controller read as a GameCube pad. Matches mkw::vr::OpenXRControllerMode.
|
||||
// controller read as a GameCube pad, "none" nothing at all (they only open the
|
||||
// settings panel). Matches mkw::vr::OpenXRControllerMode.
|
||||
inline constexpr const char* kVrControllerModeDefault = "wii_remote";
|
||||
|
||||
inline bool IsSupportedVrControllerMode(std::string_view value) {
|
||||
return value == "wii_remote" || value == "gamepad";
|
||||
return value == "wii_remote" || value == "gamepad" || value == "none";
|
||||
}
|
||||
// SDL scancode name, spelled the way SDL_GetScancodeName produces it. An
|
||||
// empty string leaves the recenter hotkey unbound, menu button only.
|
||||
@@ -510,8 +511,8 @@ inline void EnsureConfigFile() {
|
||||
"# What the headset's controllers are to the game: \"wii_remote\"\n"
|
||||
"# is a Wii Remote (right hand, with motion and a pointer aimed at\n"
|
||||
"# the virtual screen) plus a Nunchuk (left hand); \"gamepad\" is one\n"
|
||||
"# ordinary controller read as a GameCube pad. Changeable live from\n"
|
||||
"# the F10 menu.\n"
|
||||
"# ordinary controller read as a GameCube pad; \"none\" leaves the game\n"
|
||||
"# to other controllers. Changeable live from the F10 menu.\n"
|
||||
"controller_mode = \"wii_remote\"\n"
|
||||
"# VR interpolation: 0 = Off, 1 = Auto, or 72/90/120 FPS. Live.\n"
|
||||
"frame_interpolation_fps = 0\n"
|
||||
|
||||
@@ -57,6 +57,11 @@ struct OpenXRPointerScreen {
|
||||
// left / right thumbstick-> left / right stick axes, clicks -> stick buttons
|
||||
// left menu -> Start
|
||||
//
|
||||
// None: the virtual joystick is unplugged, so the controllers hold no port and
|
||||
// another controller (a desktop gamepad, a Bluetooth remote) plays in their
|
||||
// place. The game sees them idle, the way it does while the settings panel has
|
||||
// them; they still open the panel and toggle the first-person camera.
|
||||
//
|
||||
// Both are bound for the Oculus Touch profile; khr/simple_controller gets
|
||||
// select/menu and the poses so an unknown runtime still offers something.
|
||||
//
|
||||
@@ -90,7 +95,8 @@ struct OpenXRPointerScreen {
|
||||
// run on the XR pacing thread. The Wii Remote bridge is internally locked, so
|
||||
// the game thread may read it concurrently, and the virtual gamepad is only
|
||||
// published here: OpenXRApplyVirtualGamepad() performs the SDL writes on the
|
||||
// game thread, keeping SDL's joystick lock off this thread entirely.
|
||||
// game thread, and plugs the joystick in or out as the controller mode
|
||||
// changes, keeping SDL's joystick lock off this thread during a session.
|
||||
class OpenXRInput final {
|
||||
public:
|
||||
explicit OpenXRInput(OpenXRLogCallback logger = {});
|
||||
@@ -131,7 +137,6 @@ public:
|
||||
void ApplyHaptic(uint32_t hand, float amplitude, XrDuration duration);
|
||||
|
||||
bool IsCreated() const noexcept { return m_created; }
|
||||
bool HasVirtualGamepad() const noexcept { return m_joystick_id != 0; }
|
||||
const std::string& LastError() const noexcept { return m_last_error; }
|
||||
|
||||
private:
|
||||
@@ -143,8 +148,6 @@ private:
|
||||
void DestroyPoseSpaces();
|
||||
void LoadInputClock();
|
||||
XrTime InputSampleTime(XrTime predicted_display_time) const;
|
||||
bool AttachVirtualGamepad();
|
||||
void DetachVirtualGamepad();
|
||||
// `withheld` publishes a remote at rest with nothing held and no pointer,
|
||||
// while still tracking motion so releasing it does not read as a jolt.
|
||||
void PublishWiiRemote(XrTime input_time, const OpenXRPointerScreen& screen,
|
||||
@@ -197,8 +200,6 @@ private:
|
||||
bool m_panel_select_held = false;
|
||||
std::array<float, 2> m_horizon{1.0f, 0.0f};
|
||||
bool m_haptics_active[kHands]{};
|
||||
uint32_t m_joystick_id = 0; // SDL_JoystickID; 0 when detached
|
||||
void* m_joystick = nullptr; // SDL_Joystick*
|
||||
ClickToggle m_first_person_click;
|
||||
SteeringWheel m_wheel;
|
||||
WheelReferenceLatch m_wheel_reference;
|
||||
@@ -262,8 +263,9 @@ private:
|
||||
std::string m_last_error;
|
||||
};
|
||||
|
||||
// Game thread: writes the gamepad the pacing thread last published, if any.
|
||||
// Does nothing when no OpenXR controllers are attached.
|
||||
// Game thread: plugs the virtual gamepad in or out as the controller mode asks,
|
||||
// then writes the gamepad the pacing thread last published, if any. Does
|
||||
// nothing when no OpenXR controllers are attached.
|
||||
void OpenXRApplyVirtualGamepad() noexcept;
|
||||
|
||||
} // namespace mkw::vr
|
||||
|
||||
@@ -77,10 +77,11 @@ struct Frame {
|
||||
};
|
||||
|
||||
// Whether the button that opens and closes the panel is held. As a Wii Remote
|
||||
// that is left Y, which has no Wii button; as a gamepad left Y is GameCube Y, so
|
||||
// both thumbsticks clicked together stand in for it.
|
||||
// that is left Y, which has no Wii button, and with the controllers nothing to
|
||||
// the game it is left Y too; as a gamepad left Y is GameCube Y, so both
|
||||
// thumbsticks clicked together stand in for it.
|
||||
inline bool ToggleHeld(const std::array<HandInputs, 2>& hands, OpenXRControllerMode mode) noexcept {
|
||||
if (mode == OpenXRControllerMode::WiiRemote) {
|
||||
if (mode != OpenXRControllerMode::Gamepad) {
|
||||
return hands[0].secondary;
|
||||
}
|
||||
return hands[0].thumbstick_click && hands[1].thumbstick_click;
|
||||
|
||||
@@ -24,6 +24,9 @@ enum class OpenXRControllerMode : uint8_t {
|
||||
WiiRemote,
|
||||
// One ordinary gamepad, read through PAD as a GameCube controller.
|
||||
Gamepad,
|
||||
// Nothing to the game: the virtual gamepad is unplugged, so the controllers
|
||||
// hold no port. They still open the settings panel.
|
||||
None,
|
||||
};
|
||||
|
||||
struct OpenXRWiiRemoteSample {
|
||||
@@ -40,7 +43,7 @@ struct OpenXRWiiRemoteSample {
|
||||
float distance_meters = 0.0f;
|
||||
};
|
||||
|
||||
// Live switch between the two presentations; the settings bar and the launch
|
||||
// Live switch between the presentations; the settings bar and the launch
|
||||
// configuration both go through it.
|
||||
void OpenXRSetControllerMode(OpenXRControllerMode mode) noexcept;
|
||||
OpenXRControllerMode OpenXRGetControllerMode() noexcept;
|
||||
@@ -343,8 +346,10 @@ struct HandInputs {
|
||||
// Adapted from DolphinXR's default "OpenXR Wii Remote" profile
|
||||
// (Data/Sys/Profiles/Wiimote):
|
||||
// right A -> A, right trigger -> B, right B -> C, right stick up/down -> 1/2,
|
||||
// left X -> -, left menu -> +,
|
||||
// left X and left menu -> +,
|
||||
// left trigger -> Z, left stick -> Nunchuk stick.
|
||||
// + is on left X as well because the PlayStation VR2's controllers give no
|
||||
// usable left menu, and - has no button because Mario Kart Wii never reads it.
|
||||
// HOME has no button; left Y opens the settings panel (openxr_settings_panel.h).
|
||||
// The grips press nothing: they take hold of the wheel (openxr_driving.h), and C
|
||||
// is the game's look-behind, which a hand on the wheel would otherwise hold down.
|
||||
@@ -360,8 +365,7 @@ inline uint32_t RemoteButtons(const HandInputs& left, const HandInputs& right) n
|
||||
press(right.secondary, kButtonC);
|
||||
press(right.stick_y > kPressThreshold, kButtonOne);
|
||||
press(right.stick_y < -kPressThreshold, kButtonTwo);
|
||||
press(left.primary, kButtonMinus);
|
||||
press(left.menu, kButtonPlus);
|
||||
press(left.primary || left.menu, kButtonPlus);
|
||||
press(left.trigger > kPressThreshold, kButtonZ);
|
||||
return hold;
|
||||
}
|
||||
|
||||
@@ -203,10 +203,11 @@ int g_vrMirrorView = [] {
|
||||
}();
|
||||
// Config spellings and menu labels for the VR controllers, index-matched to
|
||||
// mkw::vr::OpenXRControllerMode.
|
||||
constexpr std::array<const char*, 2> kVrControllerModeNames{"wii_remote", "gamepad"};
|
||||
constexpr std::array<const char*, 2> kVrControllerModeLabels{"Wii Remote + Nunchuk", "Gamepad"};
|
||||
constexpr std::array<const char*, 3> kVrControllerModeNames{"wii_remote", "gamepad", "none"};
|
||||
constexpr std::array<const char*, 3> kVrControllerModeLabels{"Wii Remote + Nunchuk", "Gamepad", "None"};
|
||||
static_assert(static_cast<int>(mkw::vr::OpenXRControllerMode::WiiRemote) == 0);
|
||||
static_assert(static_cast<int>(mkw::vr::OpenXRControllerMode::Gamepad) == 1);
|
||||
static_assert(static_cast<int>(mkw::vr::OpenXRControllerMode::None) == 2);
|
||||
int g_vrControllerMode = [] {
|
||||
const std::string mode = RuntimeConfigFile::VrControllerMode();
|
||||
for (size_t i = 0; i < kVrControllerModeNames.size(); ++i) {
|
||||
@@ -878,9 +879,11 @@ void DrawVrControllerSettings() {
|
||||
"Wii Remote + Nunchuk: the right controller is a Wii Remote, with motion and a pointer "
|
||||
"that lands where you aim on the virtual screen; the left one is the Nunchuk.\n"
|
||||
" Right: A = A, trigger = B, B = C (look behind), stick up/down = 1/2\n"
|
||||
" Left: stick = Nunchuk stick, trigger = Z, X = -, menu = +, Y = settings panel\n"
|
||||
" Left: stick = Nunchuk stick, trigger = Z, X or menu = +, Y = settings panel\n"
|
||||
" The grips press nothing; they take hold of the wheel with hand steering.\n"
|
||||
"Gamepad: both controllers are one ordinary controller, read as a GameCube pad.\n"
|
||||
"None: the game does not see the VR controllers at all and they take no controller port,\n"
|
||||
"so a desktop gamepad or a Wii Remote plays instead. Left Y still opens the settings panel.\n"
|
||||
"Applies immediately; the game sees the controller change as a reconnection.");
|
||||
}
|
||||
if (mkw::vr::OpenXRIsRunning() &&
|
||||
@@ -2322,9 +2325,9 @@ void DrawVrSettingsPanelWindow() {
|
||||
}
|
||||
ImGui::TextDisabled("Aim and pull a trigger to change a setting, push a thumbstick to scroll.");
|
||||
ImGui::TextDisabled("%s to close. The game does not see the controllers meanwhile.",
|
||||
mkw::vr::OpenXRGetControllerMode() == mkw::vr::OpenXRControllerMode::WiiRemote
|
||||
? "Press left Y or Menu"
|
||||
: "Click both thumbsticks or press Menu");
|
||||
mkw::vr::OpenXRGetControllerMode() == mkw::vr::OpenXRControllerMode::Gamepad
|
||||
? "Click both thumbsticks or press Menu"
|
||||
: "Press left Y or Menu");
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTabBar("Settings")) {
|
||||
const auto tab = [](const char* label, void (*draw)()) {
|
||||
|
||||
+132
-84
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
@@ -49,8 +50,15 @@ namespace {
|
||||
// slot that passes. So it leaves the gamepad here, and the game thread writes
|
||||
// it to SDL where it already polls controllers.
|
||||
//
|
||||
// m_sdl is the lock the SDL work runs under; the pacing thread only ever takes
|
||||
// m_state, and only for the copy. Both are taken in that order.
|
||||
// The relay also owns the virtual joystick. Attaching and detaching it take the
|
||||
// same lock, so a live switch to or from the None controller mode (which
|
||||
// unplugs it, so the VR controllers hold no port) is followed on the game
|
||||
// thread too; only the input's creation and destruction plug it in or out
|
||||
// where they run.
|
||||
//
|
||||
// m_sdl is the lock the SDL work, and the joystick, are under; the pacing
|
||||
// thread only ever takes m_state, and only for the copy. Both are taken in that
|
||||
// order.
|
||||
class VirtualGamepadRelay {
|
||||
public:
|
||||
struct Pad {
|
||||
@@ -58,18 +66,29 @@ public:
|
||||
std::array<bool, SDL_GAMEPAD_BUTTON_COUNT> buttons{};
|
||||
};
|
||||
|
||||
void Attach(SDL_Joystick* joystick) {
|
||||
// The controllers' input exists: plugs the joystick in unless the mode is
|
||||
// None. `logger` reports a joystick SDL refuses, from whichever thread.
|
||||
void Open(OpenXRLogCallback logger) {
|
||||
std::scoped_lock lock(m_sdl, m_state);
|
||||
m_joystick = joystick;
|
||||
m_logger = std::move(logger);
|
||||
m_open = true;
|
||||
m_attach_failed = false;
|
||||
m_pending = false;
|
||||
FollowControllerMode();
|
||||
}
|
||||
|
||||
void Detach() {
|
||||
// The controllers' input is going away: unplugs the joystick.
|
||||
void Close() {
|
||||
std::scoped_lock lock(m_sdl, m_state);
|
||||
m_joystick = nullptr;
|
||||
m_open = false;
|
||||
Detach();
|
||||
m_pending = false;
|
||||
m_logger = {};
|
||||
}
|
||||
|
||||
// The plugged-in joystick's SDL_JoystickID, 0 while unplugged.
|
||||
uint32_t JoystickId() const noexcept { return m_id.load(std::memory_order_relaxed); }
|
||||
|
||||
// Pacing thread.
|
||||
void Publish(const Pad& pad) {
|
||||
std::lock_guard lock(m_state);
|
||||
@@ -77,35 +96,106 @@ public:
|
||||
m_pending = true;
|
||||
}
|
||||
|
||||
// Game thread. Holding m_sdl here is what keeps Detach from closing the
|
||||
// Game thread. Holding m_sdl here is what keeps Close from closing the
|
||||
// joystick underneath the writes.
|
||||
void Apply() {
|
||||
std::lock_guard sdl(m_sdl);
|
||||
FollowControllerMode();
|
||||
Pad pad;
|
||||
SDL_Joystick* joystick = nullptr;
|
||||
{
|
||||
std::lock_guard lock(m_state);
|
||||
if (!m_pending || m_joystick == nullptr) {
|
||||
return;
|
||||
}
|
||||
pad = m_pad;
|
||||
joystick = m_joystick;
|
||||
m_pending = false;
|
||||
}
|
||||
for (int axis = 0; axis < SDL_GAMEPAD_AXIS_COUNT; ++axis) {
|
||||
SDL_SetJoystickVirtualAxis(joystick, static_cast<SDL_GamepadAxis>(axis),
|
||||
SDL_SetJoystickVirtualAxis(m_joystick, static_cast<SDL_GamepadAxis>(axis),
|
||||
pad.axes[static_cast<size_t>(axis)]);
|
||||
}
|
||||
for (int button = 0; button < SDL_GAMEPAD_BUTTON_COUNT; ++button) {
|
||||
SDL_SetJoystickVirtualButton(joystick, static_cast<SDL_GamepadButton>(button),
|
||||
SDL_SetJoystickVirtualButton(m_joystick, static_cast<SDL_GamepadButton>(button),
|
||||
pad.buttons[static_cast<size_t>(button)]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// m_sdl held. A joystick SDL refused is not asked for again until the mode
|
||||
// unplugs it or the input is created anew.
|
||||
void FollowControllerMode() {
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
if (OpenXRGetControllerMode() == OpenXRControllerMode::None) {
|
||||
m_attach_failed = false;
|
||||
Detach();
|
||||
} else if (m_joystick == nullptr && !m_attach_failed) {
|
||||
m_attach_failed = !Attach();
|
||||
}
|
||||
}
|
||||
|
||||
bool Attach() {
|
||||
SDL_VirtualJoystickDesc desc;
|
||||
SDL_INIT_INTERFACE(&desc);
|
||||
desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
|
||||
desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
|
||||
desc.button_mask = (1u << SDL_GAMEPAD_BUTTON_SOUTH) | (1u << SDL_GAMEPAD_BUTTON_EAST) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_WEST) | (1u << SDL_GAMEPAD_BUTTON_NORTH) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_START) | (1u << SDL_GAMEPAD_BUTTON_LEFT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
|
||||
desc.axis_mask = (1u << SDL_GAMEPAD_AXIS_LEFTX) | (1u << SDL_GAMEPAD_AXIS_LEFTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_RIGHTX) | (1u << SDL_GAMEPAD_AXIS_RIGHTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1u << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
|
||||
desc.name = "OpenXR Touch Controllers";
|
||||
const SDL_JoystickID id = SDL_AttachVirtualJoystick(&desc);
|
||||
if (id == 0) {
|
||||
Refused("SDL_AttachVirtualJoystick");
|
||||
return false;
|
||||
}
|
||||
SDL_Joystick* joystick = SDL_OpenJoystick(id);
|
||||
if (joystick == nullptr) {
|
||||
Refused("SDL_OpenJoystick");
|
||||
SDL_DetachVirtualJoystick(id);
|
||||
return false;
|
||||
}
|
||||
m_joystick = joystick;
|
||||
m_id.store(id, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Detach() {
|
||||
const SDL_JoystickID id = m_id.exchange(0, std::memory_order_relaxed);
|
||||
if (m_joystick != nullptr) {
|
||||
SDL_CloseJoystick(m_joystick);
|
||||
m_joystick = nullptr;
|
||||
}
|
||||
if (id != 0) {
|
||||
SDL_DetachVirtualJoystick(id);
|
||||
}
|
||||
}
|
||||
|
||||
void Refused(const char* operation) const {
|
||||
if (!m_logger) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
m_logger(OpenXRLogLevel::Warning, std::string(operation) + " failed: " + SDL_GetError() +
|
||||
"; OpenXR controllers will not reach the game");
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_sdl;
|
||||
std::mutex m_state;
|
||||
OpenXRLogCallback m_logger;
|
||||
bool m_open = false;
|
||||
bool m_attach_failed = false;
|
||||
SDL_Joystick* m_joystick = nullptr;
|
||||
std::atomic<uint32_t> m_id{0};
|
||||
Pad m_pad;
|
||||
bool m_pending = false;
|
||||
};
|
||||
@@ -273,13 +363,18 @@ bool OpenXRInput::Create(OpenXRRuntime& runtime) {
|
||||
CreatePoseSpaces();
|
||||
LoadInputClock();
|
||||
LoadHandTracking();
|
||||
if (!AttachVirtualGamepad()) {
|
||||
Log(OpenXRLogLevel::Warning,
|
||||
"SDL refused the virtual gamepad; OpenXR controllers will not reach the game");
|
||||
Relay().Open(m_logger);
|
||||
switch (OpenXRGetControllerMode()) {
|
||||
case OpenXRControllerMode::WiiRemote:
|
||||
Log(OpenXRLogLevel::Info, "OpenXR controller actions attached (Wii Remote + Nunchuk)");
|
||||
break;
|
||||
case OpenXRControllerMode::Gamepad:
|
||||
Log(OpenXRLogLevel::Info, "OpenXR controller actions attached (gamepad)");
|
||||
break;
|
||||
case OpenXRControllerMode::None:
|
||||
Log(OpenXRLogLevel::Info, "OpenXR controller actions attached (none: the game does not see them)");
|
||||
break;
|
||||
}
|
||||
Log(OpenXRLogLevel::Info, OpenXRGetControllerMode() == OpenXRControllerMode::WiiRemote
|
||||
? "OpenXR controller actions attached (Wii Remote + Nunchuk)"
|
||||
: "OpenXR controller actions attached (gamepad)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -739,54 +834,6 @@ void OpenXRApplyVirtualGamepad() noexcept {
|
||||
Relay().Apply();
|
||||
}
|
||||
|
||||
bool OpenXRInput::AttachVirtualGamepad() {
|
||||
SDL_VirtualJoystickDesc desc;
|
||||
SDL_INIT_INTERFACE(&desc);
|
||||
desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
|
||||
desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
|
||||
desc.button_mask = (1u << SDL_GAMEPAD_BUTTON_SOUTH) | (1u << SDL_GAMEPAD_BUTTON_EAST) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_WEST) | (1u << SDL_GAMEPAD_BUTTON_NORTH) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_START) | (1u << SDL_GAMEPAD_BUTTON_LEFT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
|
||||
desc.axis_mask = (1u << SDL_GAMEPAD_AXIS_LEFTX) | (1u << SDL_GAMEPAD_AXIS_LEFTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_RIGHTX) | (1u << SDL_GAMEPAD_AXIS_RIGHTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1u << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
|
||||
desc.name = "OpenXR Touch Controllers";
|
||||
const SDL_JoystickID id = SDL_AttachVirtualJoystick(&desc);
|
||||
if (id == 0) {
|
||||
m_last_error = std::string("SDL_AttachVirtualJoystick failed: ") + SDL_GetError();
|
||||
Log(OpenXRLogLevel::Warning, m_last_error);
|
||||
return false;
|
||||
}
|
||||
SDL_Joystick* joystick = SDL_OpenJoystick(id);
|
||||
if (joystick == nullptr) {
|
||||
m_last_error = std::string("SDL_OpenJoystick failed: ") + SDL_GetError();
|
||||
Log(OpenXRLogLevel::Warning, m_last_error);
|
||||
SDL_DetachVirtualJoystick(id);
|
||||
return false;
|
||||
}
|
||||
m_joystick_id = id;
|
||||
m_joystick = joystick;
|
||||
Relay().Attach(joystick);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenXRInput::DetachVirtualGamepad() {
|
||||
// Before the handle goes: the game thread may be writing through it.
|
||||
Relay().Detach();
|
||||
if (m_joystick != nullptr) {
|
||||
SDL_CloseJoystick(static_cast<SDL_Joystick*>(m_joystick));
|
||||
m_joystick = nullptr;
|
||||
}
|
||||
if (m_joystick_id != 0) {
|
||||
SDL_DetachVirtualJoystick(m_joystick_id);
|
||||
m_joystick_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRInput::Destroy() {
|
||||
// The game must stop reading a remote whose controllers are going away.
|
||||
OpenXRWithdrawWiiRemote();
|
||||
@@ -794,7 +841,7 @@ void OpenXRInput::Destroy() {
|
||||
if (m_created) {
|
||||
StopRumble();
|
||||
}
|
||||
DetachVirtualGamepad();
|
||||
Relay().Close();
|
||||
DestroyHandTrackers();
|
||||
m_create_hand_tracker = nullptr;
|
||||
m_destroy_hand_tracker = nullptr;
|
||||
@@ -842,7 +889,7 @@ void OpenXRInput::Idle() {
|
||||
}
|
||||
m_pointer.Reset();
|
||||
m_horizon = {1.0f, 0.0f};
|
||||
OpenXRPublishWiiRemote(m_joystick_id, OpenXRWiiRemoteSample{});
|
||||
OpenXRPublishWiiRemote(Relay().JoystickId(), OpenXRWiiRemoteSample{});
|
||||
// The panel stays as it was; only what the controllers were holding is forgotten.
|
||||
m_panel_controls.Reset();
|
||||
m_last_input_time = 0;
|
||||
@@ -855,9 +902,7 @@ void OpenXRInput::Idle() {
|
||||
ResetDriving();
|
||||
StopRumble();
|
||||
// Nothing stays held on the gamepad either while input is away.
|
||||
if (m_joystick != nullptr) {
|
||||
Relay().Publish({});
|
||||
}
|
||||
Relay().Publish({});
|
||||
}
|
||||
|
||||
void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen& screen,
|
||||
@@ -1015,8 +1060,11 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
// is written back.
|
||||
const bool was_open = OpenXRSettingsPanelOpen();
|
||||
bool open = was_open;
|
||||
const settings_panel::Frame panel =
|
||||
m_panel_controls.Update(panel_hands, open, dt_seconds, OpenXRGetControllerMode());
|
||||
const OpenXRControllerMode mode = OpenXRGetControllerMode();
|
||||
const settings_panel::Frame panel = m_panel_controls.Update(panel_hands, open, dt_seconds, mode);
|
||||
// While the panel has the controllers, and always when they are nothing to
|
||||
// the game, the game sees them idle.
|
||||
const bool withheld = panel.withheld || mode == OpenXRControllerMode::None;
|
||||
// Pointer first: the game thread reads it as soon as it sees the panel open.
|
||||
PublishSettingsPanel(input_time, settings_panel, panel);
|
||||
if (open != was_open) {
|
||||
@@ -1036,7 +1084,7 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
|
||||
// The cockpit's wheel before the game reads the controllers: a held wheel
|
||||
// steers through the left stick and keeps its grips from the game.
|
||||
UpdateDriving(predicted_display_time, seat, hands, panel.withheld);
|
||||
UpdateDriving(predicted_display_time, seat, hands, withheld);
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Bare hands in the cockpit. While one of them holds the wheel it holds the
|
||||
@@ -1044,7 +1092,7 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
// pause menu, the results, coasting) a right pinch stays A. The game's own
|
||||
// pointer cannot tell the two apart: MKW keeps it on while driving.
|
||||
const bool cockpit_hands =
|
||||
hand_tracking_on && m_driving.cockpit_active && m_driving.hand_steering && !panel.withheld;
|
||||
hand_tracking_on && m_driving.cockpit_active && m_driving.hand_steering && !withheld;
|
||||
std::array<bool, kHands> bare_held{};
|
||||
for (uint32_t hand = 0; hand < kHands; ++hand) {
|
||||
bare_held[hand] = cockpit_hands && m_bare_latch[hand].Bare() && m_wheel_held[hand];
|
||||
@@ -1069,7 +1117,7 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
flick[hand] = {m_bare_latch[hand].Tracked(), m_wheel_held[hand],
|
||||
m_joint_frame.seat_from_joint[hand][hand_tracking::kPalm][7]};
|
||||
}
|
||||
if (m_flick.Update(flick, dt_seconds) && OpenXRGetControllerMode() == OpenXRControllerMode::WiiRemote) {
|
||||
if (m_flick.Update(flick, dt_seconds) && mode == OpenXRControllerMode::WiiRemote) {
|
||||
m_flick_start = input_time;
|
||||
}
|
||||
} else {
|
||||
@@ -1084,14 +1132,14 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
m_injected_flick_held = injected_flick;
|
||||
#endif
|
||||
|
||||
// While the panel has the controllers, the game sees them idle.
|
||||
// What the game sees of the controllers: nothing held while they are withheld.
|
||||
static const std::array<wii_remote::HandInputs, kHands> kIdleHands{};
|
||||
const auto& game_hands = panel.withheld ? kIdleHands : hands;
|
||||
const auto& game_hands = withheld ? kIdleHands : hands;
|
||||
const wii_remote::HandInputs& left = game_hands[0];
|
||||
const wii_remote::HandInputs& right = game_hands[1];
|
||||
const auto injected = [&panel](const char* button) { return !panel.withheld && Injected(button); };
|
||||
const auto injected = [withheld](const char* button) { return !withheld && Injected(button); };
|
||||
|
||||
if (m_joystick != nullptr) {
|
||||
if (Relay().JoystickId() != 0) {
|
||||
VirtualGamepadRelay::Pad pad;
|
||||
// OpenXR thumbsticks report +Y up; SDL gamepads report +Y down.
|
||||
pad.axes[SDL_GAMEPAD_AXIS_LEFTX] = ToAxis(left.stick_x);
|
||||
@@ -1113,8 +1161,8 @@ void OpenXRInput::Sync(XrTime predicted_display_time, const OpenXRPointerScreen&
|
||||
Relay().Publish(pad);
|
||||
}
|
||||
|
||||
PublishWiiRemote(input_time, screen, game_hands, panel.withheld ? 0u : InjectedWiiRemoteButtons(),
|
||||
panel.withheld);
|
||||
PublishWiiRemote(input_time, screen, game_hands, withheld ? 0u : InjectedWiiRemoteButtons(),
|
||||
withheld);
|
||||
UpdateRumble();
|
||||
}
|
||||
|
||||
@@ -1215,7 +1263,7 @@ void OpenXRInput::PublishWiiRemote(XrTime input_time, const OpenXRPointerScreen&
|
||||
sample.nunchuk_acc = OpenXRWiiRemoteSample{}.nunchuk_acc;
|
||||
m_pointer.Reset();
|
||||
m_flick_start = 0;
|
||||
OpenXRPublishWiiRemote(m_joystick_id, sample);
|
||||
OpenXRPublishWiiRemote(Relay().JoystickId(), sample);
|
||||
return;
|
||||
}
|
||||
if (m_flick_start != 0) {
|
||||
@@ -1252,7 +1300,7 @@ void OpenXRInput::PublishWiiRemote(XrTime input_time, const OpenXRPointerScreen&
|
||||
Log(OpenXRLogLevel::Info, "OpenXR Wii Remote pointer reached the virtual screen");
|
||||
}
|
||||
}
|
||||
OpenXRPublishWiiRemote(m_joystick_id, sample);
|
||||
OpenXRPublishWiiRemote(Relay().JoystickId(), sample);
|
||||
}
|
||||
|
||||
void OpenXRInput::ResetDriving() {
|
||||
@@ -1427,7 +1475,7 @@ void OpenXRInput::UpdateDriving(XrTime display_time, const driving::SeatFrame& s
|
||||
}
|
||||
|
||||
void OpenXRInput::UpdateRumble() {
|
||||
if (!OpenXRWiiRemoteRumbleRequested() || !OpenXRWiiRemoteOwnsGamepad(m_joystick_id)) {
|
||||
if (!OpenXRWiiRemoteRumbleRequested() || !OpenXRWiiRemoteOwnsGamepad(Relay().JoystickId())) {
|
||||
StopRumble();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ void CheckNear(float actual, float expected, const char* what, float tolerance =
|
||||
constexpr float kDt = 1.0f / 90.0f;
|
||||
constexpr OpenXRControllerMode kWiiRemote = OpenXRControllerMode::WiiRemote;
|
||||
constexpr OpenXRControllerMode kGamepad = OpenXRControllerMode::Gamepad;
|
||||
constexpr OpenXRControllerMode kNone = OpenXRControllerMode::None;
|
||||
|
||||
std::array<HandInputs, 2> Released() {
|
||||
return {};
|
||||
@@ -103,6 +104,20 @@ void ChordOpensAndClosesOnceAsAGamepad() {
|
||||
Check(!frame.withheld, "the game gets the controllers back once everything is released");
|
||||
}
|
||||
|
||||
void LeftYOpensThePanelWithoutAGameController() {
|
||||
Controls controls;
|
||||
bool open = false;
|
||||
|
||||
Frame frame = controls.Update(Chord(), open, kDt, kNone);
|
||||
Check(!open, "with no game controller the thumbstick chord does not open the panel");
|
||||
controls.Update(Released(), open, kDt, kNone);
|
||||
frame = controls.Update(LeftY(), open, kDt, kNone);
|
||||
Check(open && frame.open, "with no game controller left Y opens the panel");
|
||||
controls.Update(Released(), open, kDt, kNone);
|
||||
frame = controls.Update(LeftY(), open, kDt, kNone);
|
||||
Check(!open && !frame.open, "and closes it again");
|
||||
}
|
||||
|
||||
void MenuClosesAndItsPressStaysOutOfTheGame() {
|
||||
Controls controls;
|
||||
bool open = false;
|
||||
@@ -213,6 +228,7 @@ void BridgeAccumulatesWheelUntilTaken() {
|
||||
int main() {
|
||||
LeftYOpensAndClosesOnceAsAWiiRemote();
|
||||
ChordOpensAndClosesOnceAsAGamepad();
|
||||
LeftYOpensThePanelWithoutAGameController();
|
||||
MenuClosesAndItsPressStaysOutOfTheGame();
|
||||
SelectWaitsForAReleaseAndFollowsTheTrigger();
|
||||
ThumbstickScrolls();
|
||||
|
||||
@@ -259,8 +259,12 @@ void TestButtons() {
|
||||
|
||||
right = {};
|
||||
left.primary = true;
|
||||
Check(RemoteButtons(left, right) == kButtonPlus, "left X is +, and nothing presses -");
|
||||
left = {};
|
||||
left.menu = true;
|
||||
Check(RemoteButtons(left, right) == (kButtonMinus | kButtonPlus), "left X and menu are - and +");
|
||||
Check(RemoteButtons(left, right) == kButtonPlus, "left menu is + too");
|
||||
left.primary = true;
|
||||
Check(RemoteButtons(left, right) == kButtonPlus, "left X and menu together are one +");
|
||||
left = {};
|
||||
left.trigger = 0.7f;
|
||||
Check(RemoteButtons(left, right) == kButtonZ, "left trigger is Z");
|
||||
|
||||
Reference in new issue
Block a user