391 lines
14 KiB
C++
391 lines
14 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <forward_list>
|
|
#include <map>
|
|
#include <tuple>
|
|
|
|
#include "third_party/fmt/include/fmt/format.h"
|
|
#include "third_party/imgui/imgui.h"
|
|
#include "xenia/base/clock.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/main.h"
|
|
#include "xenia/base/threading.h"
|
|
#include "xenia/hid/input_system.h"
|
|
#include "xenia/ui/imgui_drawer.h"
|
|
#include "xenia/ui/vulkan/vulkan_provider.h"
|
|
#include "xenia/ui/window.h"
|
|
|
|
// Available input drivers:
|
|
#include "xenia/hid/nop/nop_hid.h"
|
|
#include "xenia/hid/sdl/sdl_hid.h"
|
|
#if XE_PLATFORM_WIN32
|
|
#include "xenia/hid/winkey/winkey_hid.h"
|
|
#include "xenia/hid/xinput/xinput_hid.h"
|
|
#endif // XE_PLATFORM_WIN32
|
|
|
|
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
|
"General");
|
|
|
|
#define MAX_USERS 4
|
|
#define COL_WIDTH_STATE 320
|
|
#define COL_WIDTH_STROKE 416
|
|
|
|
namespace xe {
|
|
namespace hid {
|
|
|
|
std::unique_ptr<xe::hid::InputSystem> input_system_;
|
|
|
|
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
|
ui::Window* window) {
|
|
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
|
if (cvars::hid.compare("nop") == 0) {
|
|
drivers.emplace_back(xe::hid::nop::Create(window));
|
|
} else if (cvars::hid.compare("sdl") == 0) {
|
|
auto driver = xe::hid::sdl::Create(window);
|
|
if (XSUCCEEDED(driver->Setup())) {
|
|
drivers.emplace_back(std::move(driver));
|
|
}
|
|
#if XE_PLATFORM_WIN32
|
|
} else if (cvars::hid.compare("winkey") == 0) {
|
|
auto driver = xe::hid::winkey::Create(window);
|
|
if (XSUCCEEDED(driver->Setup())) {
|
|
drivers.emplace_back(std::move(driver));
|
|
}
|
|
} else if (cvars::hid.compare("xinput") == 0) {
|
|
auto driver = xe::hid::xinput::Create(window);
|
|
if (XSUCCEEDED(driver->Setup())) {
|
|
drivers.emplace_back(std::move(driver));
|
|
}
|
|
#endif // XE_PLATFORM_WIN32
|
|
} else {
|
|
auto sdl_driver = xe::hid::sdl::Create(window);
|
|
if (sdl_driver && XSUCCEEDED(sdl_driver->Setup())) {
|
|
drivers.emplace_back(std::move(sdl_driver));
|
|
}
|
|
#if XE_PLATFORM_WIN32
|
|
auto xinput_driver = xe::hid::xinput::Create(window);
|
|
if (xinput_driver && XSUCCEEDED(xinput_driver->Setup())) {
|
|
drivers.emplace_back(std::move(xinput_driver));
|
|
}
|
|
auto winkey_driver = xe::hid::winkey::Create(window);
|
|
if (winkey_driver && XSUCCEEDED(winkey_driver->Setup())) {
|
|
drivers.emplace_back(std::move(winkey_driver));
|
|
}
|
|
#endif // XE_PLATFORM_WIN32
|
|
if (drivers.empty()) {
|
|
// Fallback to nop if none created.
|
|
drivers.emplace_back(xe::hid::nop::Create(window));
|
|
}
|
|
}
|
|
return drivers;
|
|
}
|
|
|
|
std::unique_ptr<xe::ui::GraphicsProvider> CreateDemoGraphicsProvider(
|
|
xe::ui::Window* window) {
|
|
return xe::ui::vulkan::VulkanProvider::Create(window);
|
|
}
|
|
|
|
void DrawInputGetState();
|
|
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log);
|
|
|
|
int hid_demo_main(const std::vector<std::string>& args) {
|
|
// Create run loop and the window.
|
|
auto loop = ui::Loop::Create();
|
|
auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name);
|
|
loop->PostSynchronous([&window]() {
|
|
xe::threading::set_name("Win32 Loop");
|
|
if (!window->Initialize()) {
|
|
FatalError("Failed to initialize main window");
|
|
return;
|
|
}
|
|
});
|
|
window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) {
|
|
loop->Quit();
|
|
XELOGI("User-initiated death!");
|
|
exit(1);
|
|
});
|
|
loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); });
|
|
|
|
// Initial size setting, done here so that it knows the menu exists.
|
|
window->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, 500);
|
|
|
|
// Create the graphics context used for drawing and setup the window.
|
|
std::unique_ptr<xe::ui::GraphicsProvider> graphics_provider;
|
|
loop->PostSynchronous([&window, &graphics_provider]() {
|
|
// Create context and give it to the window.
|
|
// The window will finish initialization wtih the context (loading
|
|
// resources, etc).
|
|
graphics_provider = CreateDemoGraphicsProvider(window.get());
|
|
window->set_context(graphics_provider->CreateContext(window.get()));
|
|
|
|
// Initialize input system and all drivers.
|
|
input_system_ = std::make_unique<xe::hid::InputSystem>(window.get());
|
|
auto drivers = CreateInputDrivers(window.get());
|
|
for (size_t i = 0; i < drivers.size(); ++i) {
|
|
input_system_->AddDriver(std::move(drivers[i]));
|
|
}
|
|
|
|
window->Invalidate();
|
|
});
|
|
|
|
window->set_imgui_input_enabled(true);
|
|
|
|
window->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
|
auto& io = window->imgui_drawer()->GetIO();
|
|
|
|
const ImGuiWindowFlags wflags =
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoScrollbar;
|
|
|
|
ImGui::Begin("GetState()", nullptr, wflags);
|
|
{
|
|
ImGui::SetWindowPos(ImVec2(0, 0));
|
|
ImGui::SetWindowSize(ImVec2(COL_WIDTH_STATE, io.DisplaySize.y));
|
|
|
|
static bool enable_GetState = false;
|
|
ImGui::Checkbox("Active", &enable_GetState);
|
|
if (enable_GetState) {
|
|
ImGui::Spacing();
|
|
DrawInputGetState();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
|
|
ImGui::Begin("GetKeystroke()", nullptr, wflags);
|
|
{
|
|
ImGui::SetWindowPos(ImVec2(COL_WIDTH_STATE, 0));
|
|
ImGui::SetWindowSize(ImVec2(COL_WIDTH_STROKE, io.DisplaySize.y));
|
|
|
|
static bool enable_GetKeystroke = false;
|
|
static bool hide_repeats = false;
|
|
ImGui::Checkbox("Active", &enable_GetKeystroke);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Hide repeats", &hide_repeats);
|
|
ImGui::SameLine();
|
|
const bool clear_log = ImGui::Button("Clear");
|
|
ImGui::Spacing();
|
|
DrawInputGetKeystroke(enable_GetKeystroke, hide_repeats, clear_log);
|
|
}
|
|
ImGui::End();
|
|
|
|
// Continuous paint.
|
|
window->Invalidate();
|
|
});
|
|
|
|
// Wait until we are exited.
|
|
loop->AwaitQuit();
|
|
|
|
input_system_.reset();
|
|
|
|
loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); });
|
|
window.reset();
|
|
loop.reset();
|
|
return 0;
|
|
}
|
|
|
|
void DrawUserInputGetState(uint32_t user_index) {
|
|
ImGui::Text("User %u:", user_index);
|
|
|
|
X_INPUT_STATE state;
|
|
if (input_system_->GetState(user_index, &state) != X_ERROR_SUCCESS) {
|
|
ImGui::Text(" No controller detected");
|
|
return;
|
|
}
|
|
|
|
ImGui::Text(" Packet Number: %u", static_cast<uint32_t>(state.packet_number));
|
|
|
|
auto& gamepad = state.gamepad;
|
|
ImGui::Text(" Buttons: [%c][%c][%c][%c] [%s][%s]",
|
|
gamepad.buttons & X_INPUT_GAMEPAD_A ? 'A' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_B ? 'B' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_X ? 'X' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_Y ? 'Y' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_START ? "start" : " ",
|
|
gamepad.buttons & X_INPUT_GAMEPAD_BACK ? "back" : " ");
|
|
ImGui::Text(" D-pad: [%c][%c][%c][%c]",
|
|
gamepad.buttons & X_INPUT_GAMEPAD_DPAD_UP ? 'U' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_DPAD_DOWN ? 'D' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_DPAD_LEFT ? 'L' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_DPAD_RIGHT ? 'R' : ' ');
|
|
ImGui::Text(" Thumbs: [%c][%c]",
|
|
gamepad.buttons & X_INPUT_GAMEPAD_LEFT_THUMB ? 'L' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_RIGHT_THUMB ? 'R' : ' ');
|
|
ImGui::Text(" Shoulders: [%c][%c]",
|
|
gamepad.buttons & X_INPUT_GAMEPAD_LEFT_SHOULDER ? 'L' : ' ',
|
|
gamepad.buttons & X_INPUT_GAMEPAD_RIGHT_SHOULDER ? 'R' : ' ');
|
|
ImGui::Text(" Left Trigger: %3u",
|
|
static_cast<uint16_t>(gamepad.left_trigger));
|
|
ImGui::Text(" Right Trigger: %3u",
|
|
static_cast<uint16_t>(gamepad.right_trigger));
|
|
ImGui::Text(" Left Thumb: %6d, %6d",
|
|
static_cast<int32_t>(gamepad.thumb_lx),
|
|
static_cast<int32_t>(gamepad.thumb_ly));
|
|
ImGui::Text(" Right Thumb: %6d, %6d",
|
|
static_cast<int32_t>(gamepad.thumb_rx),
|
|
static_cast<int32_t>(gamepad.thumb_ry));
|
|
|
|
X_INPUT_VIBRATION vibration;
|
|
vibration.left_motor_speed = static_cast<uint16_t>(
|
|
float(static_cast<uint16_t>(gamepad.left_trigger)) / 255.0f * UINT16_MAX);
|
|
vibration.right_motor_speed = static_cast<uint16_t>(
|
|
float(static_cast<uint16_t>(gamepad.right_trigger)) / 255.0f *
|
|
UINT16_MAX);
|
|
input_system_->SetState(user_index, &vibration);
|
|
|
|
ImGui::Text(" Motor Speeds: L %5u, R %5u",
|
|
static_cast<uint32_t>(vibration.left_motor_speed),
|
|
static_cast<uint32_t>(vibration.right_motor_speed));
|
|
|
|
ImGui::Text(" ");
|
|
}
|
|
|
|
void DrawInputGetState() {
|
|
ImGui::BeginChild("##input_get_state_scroll");
|
|
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
|
DrawUserInputGetState(user_index);
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
static const std::map<std::underlying_type<X_INPUT_GAMEPAD_VK>::type,
|
|
const std::string>
|
|
vk_pretty = {
|
|
{X_INPUT_GAMEPAD_VK_A, "A"},
|
|
{X_INPUT_GAMEPAD_VK_B, "B"},
|
|
{X_INPUT_GAMEPAD_VK_X, "X"},
|
|
{X_INPUT_GAMEPAD_VK_Y, "Y"},
|
|
{X_INPUT_GAMEPAD_VK_RSHOULDER, "R Shoulder"},
|
|
{X_INPUT_GAMEPAD_VK_LSHOULDER, "L Shoulder"},
|
|
{X_INPUT_GAMEPAD_VK_LTRIGGER, "L Trigger"},
|
|
{X_INPUT_GAMEPAD_VK_RTRIGGER, "R Trigger"},
|
|
|
|
{X_INPUT_GAMEPAD_VK_DPAD_UP, "DPad up"},
|
|
{X_INPUT_GAMEPAD_VK_DPAD_DOWN, "DPad down"},
|
|
{X_INPUT_GAMEPAD_VK_DPAD_LEFT, "DPad left"},
|
|
{X_INPUT_GAMEPAD_VK_DPAD_RIGHT, "DPad right"},
|
|
{X_INPUT_GAMEPAD_VK_START, "Start"},
|
|
{X_INPUT_GAMEPAD_VK_BACK, "Back"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_PRESS, "L Thumb press"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_PRESS, "R Thumb press"},
|
|
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_UP, "L Thumb up"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_DOWN, "L Thumb down"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_RIGHT, "L Thumb right"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_LEFT, "L Thumb left"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_UPLEFT, "L Thumb up & left"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_UPRIGHT, "L Thumb up & right"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_DOWNRIGHT, "L Thumb down & right"},
|
|
{X_INPUT_GAMEPAD_VK_LTHUMB_DOWNLEFT, "L Thumb down & left"},
|
|
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_UP, "R Thumb up"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_DOWN, "R Thumb down"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_RIGHT, "R Thumb right"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_LEFT, "R Thumb left"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_UPLEFT, "R Thumb up & left"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_UPRIGHT, "R Thumb up & right"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_DOWNRIGHT, "R Thumb down & right"},
|
|
{X_INPUT_GAMEPAD_VK_RTHUMB_DOWNLEFT, "R Thumb down & left"},
|
|
};
|
|
|
|
void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
|
bool hide_repeats, bool clear_log) {
|
|
const size_t maxLog = 128;
|
|
static std::array<std::forward_list<std::string>, MAX_USERS> event_logs;
|
|
static std::array<uint64_t, MAX_USERS> last_event_times = {};
|
|
|
|
auto& event_log = event_logs.at(user_index);
|
|
if (clear_log) {
|
|
event_log.clear();
|
|
}
|
|
|
|
if (poll) {
|
|
X_INPUT_KEYSTROKE stroke;
|
|
bool continue_poll;
|
|
do {
|
|
continue_poll = false;
|
|
auto status = input_system_->GetKeystroke(user_index, 0, &stroke);
|
|
switch (status) {
|
|
case X_ERROR_SUCCESS: {
|
|
if (!stroke.virtual_key) {
|
|
event_log.emplace_front("Error: Empty KEYSTROKE on SUCCESS.");
|
|
break;
|
|
}
|
|
continue_poll = true;
|
|
|
|
const auto now = Clock::QueryHostUptimeMillis();
|
|
const auto dur = now - last_event_times[user_index];
|
|
last_event_times[user_index] = now;
|
|
|
|
if (hide_repeats && (stroke.flags & X_INPUT_KEYSTROKE_REPEAT)) {
|
|
break;
|
|
}
|
|
|
|
const auto key_search = vk_pretty.find(stroke.virtual_key);
|
|
const auto key = key_search != vk_pretty.end()
|
|
? key_search->second
|
|
: fmt::format("0x{:04x}", stroke.virtual_key);
|
|
event_log.emplace_front(fmt::format(
|
|
"{:>6} {:>9}ms {:<20} {} {} {}", ImGui::GetFrameCount(),
|
|
dur, key,
|
|
((stroke.flags & X_INPUT_KEYSTROKE_KEYDOWN) ? "down" : " "),
|
|
((stroke.flags & X_INPUT_KEYSTROKE_KEYUP) ? "up" : " "),
|
|
((stroke.flags & X_INPUT_KEYSTROKE_REPEAT) ? "repeat" : "")));
|
|
|
|
break;
|
|
}
|
|
case X_ERROR_DEVICE_NOT_CONNECTED:
|
|
case X_ERROR_EMPTY:
|
|
break;
|
|
default:
|
|
event_log.emplace_front(
|
|
fmt::format("Error: Unknown result code: 0x{:08X}", status));
|
|
break;
|
|
}
|
|
} while (continue_poll);
|
|
}
|
|
|
|
if (ImGui::BeginTabItem(fmt::format("User {}", user_index).c_str())) {
|
|
ImGui::BeginChild(
|
|
fmt::format("##input_get_keystroke_scroll_{}", user_index).c_str());
|
|
ImGui::Text(" frame since last button flags");
|
|
for (auto [it, count] = std::tuple{event_log.begin(), 1};
|
|
it != event_log.end(); ++it, ++count) {
|
|
ImGui::Text("%s", it->c_str());
|
|
|
|
if (count >= maxLog) {
|
|
auto last = it;
|
|
if (++it != event_log.end()) {
|
|
event_log.erase_after(last);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ImGui::EndChild();
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) {
|
|
bool tab_bar = ImGui::BeginTabBar("DrawInputGetKeystroke");
|
|
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
|
DrawUserInputGetKeystroke(user_index, poll, hide_repeats, clear_log);
|
|
}
|
|
if (tab_bar) ImGui::EndTabBar();
|
|
}
|
|
|
|
} // namespace hid
|
|
} // namespace xe
|
|
|
|
DEFINE_ENTRY_POINT("xenia-hid-demo", xe::hid::hid_demo_main, "");
|