diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 017cc4165..e1fb88214 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -140,15 +140,6 @@ DEFINE_int32(recent_titles_entry_amount, 10, "Allows user to define how many titles is saved in list of " "recently played titles.", "General"); - -namespace xe { -namespace kernel { -namespace xam { -extern std::atomic xam_dialogs_shown_; -} -} // namespace kernel -} // namespace xe - namespace xe { namespace app { @@ -275,9 +266,11 @@ void EmulatorWindow::OnEmulatorInitialized() { } // Create a thread to listen for controller hotkeys. - Gamepad_HotKeys_Listener = - threading::Thread::Create({}, [&] { GamepadHotKeys(); }); - Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener"); + if (cvars::controller_hotkeys) { + Gamepad_HotKeys_Listener = + threading::Thread::Create({}, [&] { GamepadHotKeys(); }); + Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener"); + } } void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) { @@ -1462,12 +1455,12 @@ void EmulatorWindow::ToggleProfilesConfigDialog() { emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 1); profile_config_dialog_ = std::make_unique(imgui_drawer_.get(), this); - kernel::xam::xam_dialogs_shown_++; + emulator_->kernel_state()->xam_state()->xam_dialogs_shown_++; } else { disable_hotkeys_ = false; emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 0); profile_config_dialog_.reset(); - kernel::xam::xam_dialogs_shown_--; + emulator_->kernel_state()->xam_state()->xam_dialogs_shown_--; } } @@ -2065,7 +2058,7 @@ xe::X_STATUS EmulatorWindow::RunTitle( if (profile_config_dialog_) { profile_config_dialog_.reset(); - kernel::xam::xam_dialogs_shown_--; + emulator_->kernel_state()->xam_state()->xam_dialogs_shown_--; } if (display_config_dialog_) { @@ -2210,7 +2203,7 @@ void EmulatorWindow::ClearDialogs() { } imgui_drawer_.get()->ClearDialogs(); - kernel::xam::xam_dialogs_shown_ = 0; + emulator_->kernel_state()->xam_state()->xam_dialogs_shown_ = 0; } } // namespace app diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 3ad0c6958..3bd34bbc6 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -279,10 +279,7 @@ X_STATUS Emulator::Setup( if (input_driver_factory) { auto input_drivers = input_driver_factory(display_window_); for (size_t i = 0; i < input_drivers.size(); ++i) { - auto& input_driver = input_drivers[i]; - input_driver->set_is_active_callback( - []() -> bool { return !xe::kernel::xam::xeXamIsUIActive(); }); - input_system_->AddDriver(std::move(input_driver)); + input_system_->AddDriver(std::move(input_drivers[i])); } } @@ -291,6 +288,9 @@ X_STATUS Emulator::Setup( return result; } + // Add inputSystem to UI + imgui_drawer_->LoadInputSystem(input_system_.get()); + XELOGI("{}: Initializing VFS...", __func__); // Bring up the virtual filesystem used by the kernel. file_system_ = std::make_unique(); diff --git a/src/xenia/hid/input.h b/src/xenia/hid/input.h index f142b8990..b13104344 100644 --- a/src/xenia/hid/input.h +++ b/src/xenia/hid/input.h @@ -16,6 +16,10 @@ namespace xe { namespace hid { +constexpr int32_t X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849; +constexpr int32_t X_INPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689; +constexpr uint8_t X_INPUT_GAMEPAD_TRIGGER_THRESHOLD = 30; + enum X_INPUT_CAPS { X_INPUT_CAPS_FFB_SUPPORTED = 0x0001, X_INPUT_CAPS_WIRELESS = 0x0002, diff --git a/src/xenia/hid/input_driver.h b/src/xenia/hid/input_driver.h index a5c05af03..ee8b00cc4 100644 --- a/src/xenia/hid/input_driver.h +++ b/src/xenia/hid/input_driver.h @@ -46,10 +46,6 @@ class InputDriver { virtual InputType GetInputType() const = 0; - void set_is_active_callback(std::function is_active_callback) { - is_active_callback_ = is_active_callback; - } - protected: explicit InputDriver(xe::ui::Window* window, size_t window_z_order) : window_(window), window_z_order_(window_z_order) {} @@ -57,14 +53,9 @@ class InputDriver { xe::ui::Window* window() const { return window_; } size_t window_z_order() const { return window_z_order_; } - bool is_active() const { - return !is_active_callback_ || is_active_callback_(); - } - private: xe::ui::Window* window_; size_t window_z_order_; - std::function is_active_callback_ = nullptr; }; } // namespace hid diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index ef49106ba..051dc7210 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -219,11 +219,7 @@ X_RESULT SDLInputDriver::GetState(uint32_t user_index, return X_ERROR_BAD_ARGUMENTS; } - auto is_active = this->is_active(); - - if (is_active) { - QueueControllerUpdate(); - } + QueueControllerUpdate(); auto controller = GetControllerState(user_index); if (!controller) { @@ -233,18 +229,10 @@ X_RESULT SDLInputDriver::GetState(uint32_t user_index, // Make sure packet_number is only incremented by 1, even if there have been // multiple updates between GetState calls. Also track `is_active` to // increment the packet number if it changed. - if ((is_active != controller->is_active) || - (is_active && controller->state_changed)) { + if (controller->is_active || controller->state_changed) { controller->state.packet_number++; - controller->is_active = is_active; - controller->state_changed = false; } std::memcpy(out_state, &controller->state, sizeof(*out_state)); - if (!is_active) { - // Simulate an "untouched" controller. When we become active again the - // pressed buttons aren't lost and will be visible again. - std::memset(&out_state->gamepad, 0, sizeof(out_state->gamepad)); - } return X_ERROR_SUCCESS; } @@ -332,11 +320,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, ui::VirtualKey::kXInputPadRThumbDownLeft, }; - auto is_active = this->is_active(); - - if (is_active) { - QueueControllerUpdate(); - } + QueueControllerUpdate(); for (uint32_t user_index = (user_any ? 0 : users); user_index < (user_any ? HID_SDL_USER_COUNT : users + 1); user_index++) { @@ -352,10 +336,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, // If input is not active (e.g. due to a dialog overlay), force buttons to // "unpressed". The algorithm will automatically send UP events when // `is_active()` goes low and DOWN events when it goes high again. - const uint64_t curr_butts = - is_active ? (controller->state.gamepad.buttons | - AnalogToKeyfield(controller->state.gamepad)) - : uint64_t(0); + const uint64_t curr_butts = controller->state.gamepad.buttons | + AnalogToKeyfield(controller->state.gamepad); KeystrokeState& last = keystroke_states_.at(user_index); // Handle repeating diff --git a/src/xenia/hid/winkey/winkey_input_driver.cc b/src/xenia/hid/winkey/winkey_input_driver.cc index 762a6593e..3ff5beaff 100644 --- a/src/xenia/hid/winkey/winkey_input_driver.cc +++ b/src/xenia/hid/winkey/winkey_input_driver.cc @@ -169,7 +169,7 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index, int16_t thumb_rx = 0; int16_t thumb_ry = 0; - if (window()->HasFocus() && is_active()) { + if (window()->HasFocus()) { bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT); for (const KeyBinding& b : key_bindings_) { if (((b.lowercase == b.uppercase) || (b.lowercase && !capital) || @@ -283,10 +283,6 @@ X_RESULT WinKeyInputDriver::SetState(uint32_t user_index, X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, X_INPUT_KEYSTROKE* out_keystroke) { - if (!is_active()) { - return X_ERROR_DEVICE_NOT_CONNECTED; - } - if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) { return X_ERROR_DEVICE_NOT_CONNECTED; } @@ -382,8 +378,8 @@ void WinKeyInputDriver::WinKeyWindowInputListener::OnKeyUp(ui::KeyEvent& e) { } void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) { - if (!is_active() || static_cast(cvars::keyboard_mode) == - KeyboardMode::Disabled) { + if (static_cast(cvars::keyboard_mode) == + KeyboardMode::Disabled) { return; } diff --git a/src/xenia/hid/xinput/xinput_input_driver.cc b/src/xenia/hid/xinput/xinput_input_driver.cc index c745990f2..c921d4390 100644 --- a/src/xenia/hid/xinput/xinput_input_driver.cc +++ b/src/xenia/hid/xinput/xinput_input_driver.cc @@ -164,17 +164,13 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index, } out_state->packet_number = native_state.state.dwPacketNumber; - if (is_active()) { - out_state->gamepad.buttons = native_state.state.Gamepad.wButtons; - out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger; - out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger; - out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX; - out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY; - out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX; - out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY; - } else { - std::memset(&out_state->gamepad, 0, sizeof(out_state->gamepad)); - } + out_state->gamepad.buttons = native_state.state.Gamepad.wButtons; + out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger; + out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger; + out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX; + out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY; + out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX; + out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY; return result; } @@ -226,10 +222,6 @@ X_RESULT XInputInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, return result; } - if (!is_active()) { - return X_ERROR_EMPTY; - } - out_keystroke->virtual_key = native_keystroke.VirtualKey; out_keystroke->unicode = native_keystroke.Unicode; out_keystroke->flags = native_keystroke.Flags; diff --git a/src/xenia/kernel/xam/xam_input.cc b/src/xenia/kernel/xam/xam_input.cc index 0ce6eed6e..2b203e29d 100644 --- a/src/xenia/kernel/xam/xam_input.cc +++ b/src/xenia/kernel/xam/xam_input.cc @@ -107,6 +107,10 @@ dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags, return X_ERROR_DEVICE_NOT_CONNECTED; } + if (kernel_state()->xam_state()->xam_dialogs_shown_ > 0) { + return X_ERROR_SUCCESS; + } + // Games call this with a NULL state ptr, probably as a query. uint32_t actual_user_index = user_index; diff --git a/src/xenia/kernel/xam/xam_module.cc b/src/xenia/kernel/xam/xam_module.cc index 0be810ab9..9a85f544c 100644 --- a/src/xenia/kernel/xam/xam_module.cc +++ b/src/xenia/kernel/xam/xam_module.cc @@ -17,23 +17,6 @@ namespace xe { namespace kernel { namespace xam { -std::atomic xam_dialogs_shown_ = {0}; -std::atomic xam_nui_dialogs_shown_ = {0}; - -// FixMe(RodoMa92): Same hack as main_init_posix.cc:40 -// Force initialization before constructor calling, mimicking -// Windows. -// Ref: -// https://reviews.llvm.org/D12689#243295 -#ifdef XE_PLATFORM_LINUX -__attribute__((init_priority(101))) -#endif -static std::vector - xam_exports(4096); - -bool xeXamIsUIActive() { return xam_dialogs_shown_ > 0; } -bool xeXamIsNuiUIActive() { return xam_nui_dialogs_shown_ > 0; } - XamModule::XamModule(Emulator* emulator, KernelState* kernel_state) : KernelModule(kernel_state, "xe:\\xam.xex"), loader_data_() { RegisterExportTable(export_resolver_); @@ -45,7 +28,13 @@ XamModule::XamModule(Emulator* emulator, KernelState* kernel_state) #undef XE_MODULE_EXPORT_GROUP } +static auto& get_xam_exports() { + static std::vector xam_exports(4096); + return xam_exports; +} + xe::cpu::Export* RegisterExport_xam(xe::cpu::Export* export_entry) { + auto& xam_exports = get_xam_exports(); assert_true(export_entry->ordinal < xam_exports.size()); xam_exports[export_entry->ordinal] = export_entry; return export_entry; @@ -58,6 +47,7 @@ static constexpr xe::cpu::Export xam_export_table[] = { #include "xenia/kernel/util/export_table_post.inc" void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) { assert_not_null(export_resolver); + auto& xam_exports = get_xam_exports(); for (size_t i = 0; i < xe::countof(xam_export_table); ++i) { auto& export_entry = xam_export_table[i]; @@ -67,7 +57,7 @@ void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) { const_cast(&export_entry); } } - export_resolver->RegisterTable("xam.xex", &xam_exports); + export_resolver->RegisterTable("xam.xex", &get_xam_exports()); } XamModule::~XamModule() {} diff --git a/src/xenia/kernel/xam/xam_module.h b/src/xenia/kernel/xam/xam_module.h index bf3fdbd9f..bfc106ec8 100644 --- a/src/xenia/kernel/xam/xam_module.h +++ b/src/xenia/kernel/xam/xam_module.h @@ -20,9 +20,6 @@ namespace xe { namespace kernel { namespace xam { -bool xeXamIsUIActive(); -bool xeXamIsNuiUIActive(); - static constexpr std::string_view kXamModuleLoaderDataFileName = "launch_data.bin"; diff --git a/src/xenia/kernel/xam/xam_nui.cc b/src/xenia/kernel/xam/xam_nui.cc index 75a81a389..aa2417e84 100644 --- a/src/xenia/kernel/xam/xam_nui.cc +++ b/src/xenia/kernel/xam/xam_nui.cc @@ -28,10 +28,6 @@ DEFINE_bool(allow_nui_initialization, false, namespace xe { namespace kernel { namespace xam { - -extern std::atomic xam_dialogs_shown_; -extern std::atomic xam_nui_dialogs_shown_; - // https://web.cs.ucdavis.edu/~okreylos/ResDev/Kinect/MainPage.html struct X_NUI_DEVICE_STATUS { @@ -185,7 +181,9 @@ dword_result_t XamNuiCameraSetFlags_entry(qword_t unk1, dword_t unk2) { } DECLARE_XAM_EXPORT1(XamNuiCameraSetFlags, kNone, kStub); -dword_result_t XamIsNuiUIActive_entry() { return xeXamIsNuiUIActive(); } +dword_result_t XamIsNuiUIActive_entry() { + return kernel_state()->xam_state()->xam_nui_dialogs_shown_ > 0; +} DECLARE_XAM_EXPORT1(XamIsNuiUIActive, kNone, kImplemented); dword_result_t XamNuiIsDeviceReady_entry() { @@ -367,9 +365,9 @@ dword_result_t XamShowNuiTroubleshooterUI_entry(dword_t user_index, "The game has indicated there is a problem with NUI (Kinect).") ->Then(&fence); })) { - ++xam_dialogs_shown_; + kernel_state()->xam_state()->xam_dialogs_shown_++; fence.Wait(); - --xam_dialogs_shown_; + kernel_state()->xam_state()->xam_dialogs_shown_--; } } diff --git a/src/xenia/kernel/xam/xam_private.h b/src/xenia/kernel/xam/xam_private.h index 35eec0692..029d02c68 100644 --- a/src/xenia/kernel/xam/xam_private.h +++ b/src/xenia/kernel/xam/xam_private.h @@ -17,9 +17,6 @@ namespace xe { namespace kernel { namespace xam { -bool xeXamIsUIActive(); -bool xeXamIsNuiUIActive(); - xe::cpu::Export* RegisterExport_xam(xe::cpu::Export* export_entry); // Registration functions, one per file. diff --git a/src/xenia/kernel/xam/xam_state.h b/src/xenia/kernel/xam/xam_state.h index 2c9364748..caf450f9d 100644 --- a/src/xenia/kernel/xam/xam_state.h +++ b/src/xenia/kernel/xam/xam_state.h @@ -59,6 +59,9 @@ class XamState { X_DASH_APP_INFO dash_app_info_ = {}; + std::atomic xam_dialogs_shown_ = {0}; + std::atomic xam_nui_dialogs_shown_ = {0}; + private: KernelState* kernel_state_; diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index 0ce27d642..3f64c1ad8 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -54,16 +54,24 @@ namespace xam { // // We deliberately delay the XN_SYS_UI = false notification to give games time // to create a listener (if they're insane enough do this). +XamDialog::XamDialog(xe::ui::ImGuiDrawer* imgui_drawer) + : xe::ui::ImGuiDialog(imgui_drawer) { + kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); + kernel_state()->xam_state()->xam_dialogs_shown_++; +} -extern std::atomic xam_dialogs_shown_; +XamDialog::~XamDialog() { + kernel_state()->xam_state()->xam_dialogs_shown_--; + if (kernel_state()->xam_state()->xam_dialogs_shown_ == 0) { + kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); + } +} template X_RESULT xeXamDispatchDialog(T* dialog, std::function close_callback, uint32_t overlapped) { - auto pre = []() { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - }; + auto pre = []() {}; auto run = [dialog, close_callback]() -> X_RESULT { X_RESULT result; dialog->set_close_callback([&dialog, &result, &close_callback]() { @@ -74,19 +82,14 @@ X_RESULT xeXamDispatchDialog(T* dialog, kernel_state()->emulator()->display_window()->app_context(); if (app_context.CallInUIThreadSynchronous( [&dialog, &fence]() { dialog->Then(&fence); })) { - ++xam_dialogs_shown_; fence.Wait(); - --xam_dialogs_shown_; } else { delete dialog; } // dialog should be deleted at this point! return result; }; - auto post = []() { - xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); - }; + auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); }; if (!overlapped) { pre(); auto result = run(); @@ -102,9 +105,7 @@ template X_RESULT xeXamDispatchDialogEx( T* dialog, std::function close_callback, uint32_t overlapped) { - auto pre = []() { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - }; + auto pre = []() {}; auto run = [dialog, close_callback](uint32_t& extended_error, uint32_t& length) -> X_RESULT { auto display_window = kernel_state()->emulator()->display_window(); @@ -116,19 +117,14 @@ X_RESULT xeXamDispatchDialogEx( xe::threading::Fence fence; if (display_window->app_context().CallInUIThreadSynchronous( [&dialog, &fence]() { dialog->Then(&fence); })) { - ++xam_dialogs_shown_; fence.Wait(); - --xam_dialogs_shown_; } else { delete dialog; } // dialog should be deleted at this point! return result; }; - auto post = []() { - xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); - }; + auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); }; if (!overlapped) { pre(); uint32_t extended_error, length; @@ -144,13 +140,8 @@ X_RESULT xeXamDispatchDialogEx( X_RESULT xeXamDispatchHeadless(std::function run_callback, uint32_t overlapped) { - auto pre = []() { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - }; - auto post = []() { - xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); - }; + auto pre = []() {}; + auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); }; if (!overlapped) { pre(); auto result = run_callback(); @@ -166,13 +157,8 @@ X_RESULT xeXamDispatchHeadless(std::function run_callback, X_RESULT xeXamDispatchHeadlessEx( std::function run_callback, uint32_t overlapped) { - auto pre = []() { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - }; - auto post = []() { - xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); - }; + auto pre = []() {}; + auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); }; if (!overlapped) { pre(); uint32_t extended_error, length; @@ -190,20 +176,14 @@ X_RESULT xeXamDispatchHeadlessEx( template X_RESULT xeXamDispatchDialogAsync(T* dialog, std::function close_callback) { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - ++xam_dialogs_shown_; - // Important to pass captured vars by value here since we return from this // without waiting for the dialog to close so the original local vars will be // destroyed. dialog->set_close_callback([dialog, close_callback]() { close_callback(dialog); - --xam_dialogs_shown_; - auto run = []() -> void { xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); }; std::thread thread(run); @@ -214,18 +194,12 @@ X_RESULT xeXamDispatchDialogAsync(T* dialog, } X_RESULT xeXamDispatchHeadlessAsync(std::function run_callback) { - kernel_state()->BroadcastNotification(kXNotificationSystemUI, true); - ++xam_dialogs_shown_; - auto display_window = kernel_state()->emulator()->display_window(); display_window->app_context().CallInUIThread([run_callback]() { run_callback(); - --xam_dialogs_shown_; - auto run = []() -> void { xe::threading::Sleep(std::chrono::milliseconds(100)); - kernel_state()->BroadcastNotification(kXNotificationSystemUI, false); }; std::thread thread(run); @@ -393,7 +367,9 @@ static dword_result_t XamShowMessageBoxUi( return result; } -dword_result_t XamIsUIActive_entry() { return xeXamIsUIActive(); } +dword_result_t XamIsUIActive_entry() { + return kernel_state()->xam_state()->xam_dialogs_shown_ > 0; +} DECLARE_XAM_EXPORT2(XamIsUIActive, kUI, kImplemented, kHighFrequency); // https://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/ diff --git a/src/xenia/kernel/xam/xam_ui.h b/src/xenia/kernel/xam/xam_ui.h index 5989a2521..518d67ce1 100644 --- a/src/xenia/kernel/xam/xam_ui.h +++ b/src/xenia/kernel/xam/xam_ui.h @@ -25,10 +25,9 @@ class XamDialog : public xe::ui::ImGuiDialog { } protected: - XamDialog(xe::ui::ImGuiDrawer* imgui_drawer) - : xe::ui::ImGuiDialog(imgui_drawer) {} + XamDialog(xe::ui::ImGuiDrawer* imgui_drawer); - virtual ~XamDialog() {} + ~XamDialog(); void OnClose() override { if (close_callback_) { close_callback_(); diff --git a/src/xenia/ui/imgui_drawer.cc b/src/xenia/ui/imgui_drawer.cc index 65b929cf4..82407ab21 100644 --- a/src/xenia/ui/imgui_drawer.cc +++ b/src/xenia/ui/imgui_drawer.cc @@ -143,6 +143,9 @@ void ImGuiDrawer::Initialize() { internal_state_ = ImGui::CreateContext(); ImGui::SetCurrentContext(internal_state_); + auto& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + const float font_size = std::max((float)cvars::font_size, 8.f); const float title_font_size = font_size + 6.f; @@ -211,6 +214,18 @@ void ImGuiDrawer::Initialize() { reset_mouse_position_after_next_frame_ = false; } +void ImGuiDrawer::LoadInputSystem(hid::InputSystem* input_system) { + if (input_system_) { + return; + } + + input_system_ = input_system; +} + +void ImGuiDrawer::SetGuideButtonAction(std::function func) { + onGuidePressFunction_ = func; +} + std::optional ImGuiDrawer::VirtualKeyToImGuiKey(VirtualKey vkey) { static const std::map map = { {ui::VirtualKey::kTab, ImGuiKey_Tab}, @@ -568,6 +583,10 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) { io.DisplaySize.x = window_->GetActualPhysicalWidth() * physical_to_logical; io.DisplaySize.y = window_->GetActualPhysicalHeight() * physical_to_logical; + if (!dialogs_.empty()) { + UpdateGamepads(); + } + ImGui::NewFrame(); assert_true(!IsDrawingDialogs()); @@ -874,5 +893,124 @@ void ImGuiDrawer::DetachIfLastWindowRemoved() { ClearInput(); } +void ImGuiDrawer::UpdateGamepads() { + if (!input_system_) { + return; + } + + hid::X_INPUT_CAPABILITIES caps = {}; + + bool is_gamepad_connected = false; + + for (uint8_t i = 0; i < XUserMaxUserCount; i++) { + if (input_system_->GetCapabilities(i, 1, &caps) == X_ERROR_SUCCESS) { + // Special case to skip keyboard being set in gamepad mode. + if (caps.gamepad.buttons == 0xFFFF && + caps.vibration.left_motor_speed == 0 && + caps.vibration.right_motor_speed == 0) { + continue; + } + + is_gamepad_connected = true; + break; + } + } + + auto& io = GetIO(); + + if (!is_gamepad_connected) { + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + return; + } + + uint8_t controller_to_poke = XUserIndexNone; + hid::X_INPUT_STATE gamepad_state; + for (uint8_t i = 0; i < XUserMaxUserCount; i++) { + if (input_system_->GetState(i, 1, &gamepad_state) == X_ERROR_SUCCESS) { + if (gamepad_state.gamepad.buttons != 0) { + controller_to_poke = i; + break; + } + } + } + if (controller_to_poke == XUserIndexNone) { + io.ClearInputKeys(); + return; + } + + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + hid::X_INPUT_GAMEPAD& gamepad = gamepad_state.gamepad; + + // GUIDE BUTTON - More info needed + if (gamepad_state.gamepad.buttons == + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_GUIDE) { + if (onGuidePressFunction_) { + onGuidePressFunction_(controller_to_poke); + } + } + +#define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V) +#define MAP_BUTTON(KEY_NO, BUTTON_ENUM) \ + { \ + io.AddKeyEvent(KEY_NO, (gamepad.buttons & BUTTON_ENUM) != 0); \ + } +#define MAP_ANALOG(KEY_NO, VALUE, V0, V1) \ + { \ + float vn = (float)(VALUE - V0) / (float)(V1 - V0); \ + io.AddKeyAnalogEvent(KEY_NO, vn > 0.10f, IM_SATURATE(vn)); \ + } + + MAP_BUTTON(ImGuiKey_GamepadStart, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_START); + MAP_BUTTON(ImGuiKey_GamepadBack, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_BACK); + MAP_BUTTON(ImGuiKey_GamepadFaceLeft, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_X); + MAP_BUTTON(ImGuiKey_GamepadFaceRight, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_B); + MAP_BUTTON(ImGuiKey_GamepadFaceUp, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_Y); + MAP_BUTTON(ImGuiKey_GamepadFaceDown, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_A); + MAP_BUTTON(ImGuiKey_GamepadDpadLeft, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_LEFT); + MAP_BUTTON(ImGuiKey_GamepadDpadRight, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_RIGHT); + MAP_BUTTON(ImGuiKey_GamepadDpadUp, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_UP); + MAP_BUTTON(ImGuiKey_GamepadDpadDown, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_DOWN); + MAP_BUTTON(ImGuiKey_GamepadL1, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_LEFT_SHOULDER); + MAP_BUTTON(ImGuiKey_GamepadR1, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_RIGHT_SHOULDER); + MAP_ANALOG(ImGuiKey_GamepadL2, gamepad.left_trigger, + hid::X_INPUT_GAMEPAD_TRIGGER_THRESHOLD, 255); + MAP_ANALOG(ImGuiKey_GamepadR2, gamepad.right_trigger, + hid::X_INPUT_GAMEPAD_TRIGGER_THRESHOLD, 255); + MAP_BUTTON(ImGuiKey_GamepadL3, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_LEFT_THUMB); + MAP_BUTTON(ImGuiKey_GamepadR3, + hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_RIGHT_THUMB); + MAP_ANALOG(ImGuiKey_GamepadLStickLeft, gamepad.thumb_lx, + -hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); + MAP_ANALOG(ImGuiKey_GamepadLStickRight, gamepad.thumb_lx, + +hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiKey_GamepadLStickUp, gamepad.thumb_ly, + +hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiKey_GamepadLStickDown, gamepad.thumb_ly, + -hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); + MAP_ANALOG(ImGuiKey_GamepadRStickLeft, gamepad.thumb_rx, + -hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); + MAP_ANALOG(ImGuiKey_GamepadRStickRight, gamepad.thumb_rx, + +hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiKey_GamepadRStickUp, gamepad.thumb_ry, + +hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.thumb_ry, + -hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); +#undef MAP_BUTTON +#undef MAP_ANALOG +} + } // namespace ui } // namespace xe diff --git a/src/xenia/ui/imgui_drawer.h b/src/xenia/ui/imgui_drawer.h index 541d77252..5fec31009 100644 --- a/src/xenia/ui/imgui_drawer.h +++ b/src/xenia/ui/imgui_drawer.h @@ -18,6 +18,7 @@ #include #include "third_party/imgui/imgui.h" +#include "xenia/hid/input_system.h" #include "xenia/ui/immediate_drawer.h" #include "xenia/ui/presenter.h" #include "xenia/ui/window.h" @@ -89,6 +90,9 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { return GetIO().Fonts->Fonts[1]; } + void LoadInputSystem(hid::InputSystem* input_system); + void SetGuideButtonAction(std::function func); + protected: void OnKeyDown(KeyEvent& e) override; void OnKeyUp(KeyEvent& e) override; @@ -119,6 +123,7 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { bool IsDrawingDialogs() const { return dialog_loop_next_index_ != SIZE_MAX; } void DetachIfLastWindowRemoved(); + void UpdateGamepads(); std::optional VirtualKeyToImGuiKey(VirtualKey vkey); @@ -126,7 +131,9 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { size_t z_order_; ImGuiContext* internal_state_ = nullptr; + hid::InputSystem* input_system_ = nullptr; + std::function onGuidePressFunction_; // All currently-attached dialogs that get drawn. std::vector dialogs_;