diff --git a/src/xenia/hid/file/file_input_driver.h b/src/xenia/hid/file/file_input_driver.h index bb8cf333b..810a10e19 100644 --- a/src/xenia/hid/file/file_input_driver.h +++ b/src/xenia/hid/file/file_input_driver.h @@ -49,6 +49,7 @@ #include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/hid/input_driver.h" +#include "xenia/ui/virtual_key.h" DECLARE_string(pad_file); @@ -129,8 +130,79 @@ class FileInputDriver final : public InputDriver { return user_index == 0 ? X_ERROR_SUCCESS : X_ERROR_DEVICE_NOT_CONNECTED; } + // Menus do NOT read the pad through GetState. "PRESS (A) BUTTON" and most + // 360 front-ends poll XamInputGetKeystrokeEx, so a driver that only answers + // GetState looks completely dead on a title screen while its own log happily + // shows the button arriving. Returning X_ERROR_EMPTY here is what made the + // first scripted run press A into the void. + // + // One event per call, edge triggered: KEYUPs for everything released, then + // KEYDOWNs for everything pressed, exactly as the SDL driver orders them. + // Deliberately NO auto-repeat — scripted input wants precisely one event per + // press, and repeat is what makes menu steps overshoot. X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags, X_INPUT_KEYSTROKE* out_keystroke) override { + const bool user_any = user_index == 0xFF || user_index == 0xFFFFFFFFu; + if (!user_any && user_index != 0) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + if (!out_keystroke) { + return X_ERROR_BAD_ARGUMENTS; + } + Refresh(); + + // Bit index in X_INPUT_GAMEPAD::buttons -> virtual key. Order matters: it is + // the order multiple simultaneous changes are reported in. + static constexpr uint16_t kVk[16] = { + uint16_t(ui::VirtualKey::kXInputPadDpadUp), + uint16_t(ui::VirtualKey::kXInputPadDpadDown), + uint16_t(ui::VirtualKey::kXInputPadDpadLeft), + uint16_t(ui::VirtualKey::kXInputPadDpadRight), + uint16_t(ui::VirtualKey::kXInputPadStart), + uint16_t(ui::VirtualKey::kXInputPadBack), + uint16_t(ui::VirtualKey::kXInputPadLThumbPress), + uint16_t(ui::VirtualKey::kXInputPadRThumbPress), + uint16_t(ui::VirtualKey::kXInputPadLShoulder), + uint16_t(ui::VirtualKey::kXInputPadRShoulder), + 0, /* guide */ + 0, /* unused */ + uint16_t(ui::VirtualKey::kXInputPadA), + uint16_t(ui::VirtualKey::kXInputPadB), + uint16_t(ui::VirtualKey::kXInputPadX), + uint16_t(ui::VirtualKey::kXInputPadY), + }; + + const uint16_t changed = static_cast(buttons_ ^ reported_); + if (!changed) { + return X_ERROR_EMPTY; + } + for (int pass = 0; pass < 2; ++pass) { + const bool clear_pass = pass == 0; + for (uint8_t i = 0; i < 16; ++i) { + const uint16_t bit = static_cast(1u << i); + if (!(changed & bit) || kVk[i] == 0) { + continue; + } + const bool pressed = (buttons_ & bit) != 0; + if (clear_pass == pressed) { + continue; + } + reported_ = static_cast(pressed ? (reported_ | bit) + : (reported_ & ~bit)); + out_keystroke->virtual_key = kVk[i]; + out_keystroke->unicode = 0; + out_keystroke->flags = + pressed ? X_INPUT_KEYSTROKE_KEYDOWN : X_INPUT_KEYSTROKE_KEYUP; + out_keystroke->user_index = 0; + out_keystroke->hid_code = 0; + XELOGI("[file-pad] keystroke vk={:04X} {}", kVk[i], + pressed ? "down" : "up"); + return X_ERROR_SUCCESS; + } + } + // Only bits without a virtual key changed (guide/unused): swallow them so + // the caller is not asked again forever. + reported_ = buttons_; return X_ERROR_EMPTY; } @@ -249,6 +321,8 @@ class FileInputDriver final : public InputDriver { uint8_t lt_ = 0, rt_ = 0; int16_t lx_ = 0, ly_ = 0, rx_ = 0, ry_ = 0; uint32_t packet_ = 1; + // Buttons already reported through GetKeystroke; the edge detector's memory. + uint16_t reported_ = 0; bool present_ = false; time_t mtime_sec_ = 0; long mtime_nsec_ = -1;