From 31ab6b8164755f0759b933647e3d43a166a9ed0b Mon Sep 17 00:00:00 2001 From: Anime <30175255+Anime37@users.noreply.github.com> Date: Sun, 30 Mar 2025 15:20:22 +0300 Subject: [PATCH] input: configurable controller hotkeys --- src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 11 ++- src/gui/input/HotkeySettings.cpp | 155 ++++++++++++++++++++++++++++++- src/gui/input/HotkeySettings.h | 9 ++ src/input/api/Controller.cpp | 2 + 5 files changed, 172 insertions(+), 7 deletions(-) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 35d20073..94f20f2b 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -345,6 +345,7 @@ void CemuConfig::Load(XMLConfigParser& parser) // hotkeys auto xml_hotkeys = parser.get("Hotkeys"); + hotkeys.modifiers = xml_hotkeys.get("modifiers", sHotkeyCfg{}); hotkeys.exitFullscreen = xml_hotkeys.get("ExitFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_ESCAPE}}); hotkeys.toggleFullscreen = xml_hotkeys.get("ToggleFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_F11}}); hotkeys.toggleFullscreenAlt = xml_hotkeys.get("ToggleFullscreenAlt", sHotkeyCfg{uKeyboardHotkey{WXK_CONTROL_M, true}}); // ALT+ENTER @@ -553,6 +554,7 @@ void CemuConfig::Save(XMLConfigParser& parser) // hotkeys auto xml_hotkeys = config.set("Hotkeys"); + xml_hotkeys.set("modifiers", hotkeys.modifiers); xml_hotkeys.set("ExitFullscreen", hotkeys.exitFullscreen); xml_hotkeys.set("ToggleFullscreen", hotkeys.toggleFullscreen); xml_hotkeys.set("ToggleFullscreenAlt", hotkeys.toggleFullscreenAlt); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 62d0dd69..9a5e2f47 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -208,18 +208,20 @@ typedef sint16 ControllerHotkey_t; struct sHotkeyCfg { static constexpr uint8 keyboardNone{WXK_NONE}; + static constexpr sint8 controllerNone{-1}; // no enums to work with, but buttons start from 0 uKeyboardHotkey keyboard{keyboardNone}; + ControllerHotkey_t controller{controllerNone}; /* for defaults */ - sHotkeyCfg(const uKeyboardHotkey& keyboard = {WXK_NONE}) : - keyboard(keyboard){}; + sHotkeyCfg(const uKeyboardHotkey& keyboard = {WXK_NONE}, const ControllerHotkey_t& controller = {-1}) : + keyboard(keyboard), controller(controller) {}; /* for reading from xml */ sHotkeyCfg(const char* xml_values) { std::istringstream iss(xml_values); - iss >> keyboard.raw; + iss >> keyboard.raw >> controller; } }; @@ -228,7 +230,7 @@ struct fmt::formatter : formatter { template auto format(const sHotkeyCfg c, FormatContext &ctx) const { - std::string xml_values = fmt::format("{}", c.keyboard.raw); + std::string xml_values = fmt::format("{} {}", c.keyboard.raw, c.controller); return formatter::format(xml_values, ctx); } }; @@ -544,6 +546,7 @@ struct CemuConfig // hotkeys struct { + sHotkeyCfg modifiers; sHotkeyCfg toggleFullscreen; sHotkeyCfg toggleFullscreenAlt; sHotkeyCfg exitFullscreen; diff --git a/src/gui/input/HotkeySettings.cpp b/src/gui/input/HotkeySettings.cpp index 176e24df..67522eef 100644 --- a/src/gui/input/HotkeySettings.cpp +++ b/src/gui/input/HotkeySettings.cpp @@ -20,12 +20,14 @@ struct HotkeyEntry { std::unique_ptr name; std::unique_ptr keyInput; + std::unique_ptr controllerInput; sHotkeyCfg& hotkey; - HotkeyEntry(wxStaticText* name, wxButton* keyInput, sHotkeyCfg& hotkey) - : name(name), keyInput(keyInput), hotkey(hotkey) + HotkeyEntry(wxStaticText* name, wxButton* keyInput, wxButton* controllerInput, sHotkeyCfg& hotkey) + : name(name), keyInput(keyInput), controllerInput(controllerInput), hotkey(hotkey) { keyInput->SetClientData(&hotkey); + controllerInput->SetClientData(&hotkey); } }; @@ -34,8 +36,9 @@ HotkeySettings::HotkeySettings(wxWindow* parent) { SetIcon(wxICON(X_HOTKEY_SETTINGS)); - m_sizer = new wxFlexGridSizer(0, 2, 5, 5); + m_sizer = new wxFlexGridSizer(0, 3, 5, 5); m_sizer->AddGrowableCol(1); + m_sizer->AddGrowableCol(2); m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE); m_panel->SetSizer(m_sizer); @@ -43,17 +46,27 @@ HotkeySettings::HotkeySettings(wxWindow* parent) Center(); + SetActiveController(); + CreateColumnHeaders(); + /* global modifier */ + CreateHotkeyRow("Hotkey modifier", s_cfgHotkeys.modifiers); + m_hotkeys.at(0).keyInput->Hide(); + /* hotkeys */ CreateHotkeyRow("Toggle fullscreen", s_cfgHotkeys.toggleFullscreen); CreateHotkeyRow("Take screenshot", s_cfgHotkeys.takeScreenshot); + m_controllerTimer = new wxTimer(this); + Bind(wxEVT_TIMER, &HotkeySettings::OnControllerTimer, this); + m_sizer->SetSizeHints(this); } HotkeySettings::~HotkeySettings() { + m_controllerTimer->Stop(); if (m_needToSave) { g_config.Save(); @@ -70,6 +83,11 @@ void HotkeySettings::Init(wxFrame* mainWindowFrame) { s_keyboardHotkeyToFuncMap[keyboardHotkey] = func; } + auto controllerHotkey = cfgHotkey->controller; + if (controllerHotkey > sHotkeyCfg::controllerNone) + { + s_controllerHotkeyToFuncMap[controllerHotkey] = func; + } } s_mainWindow = mainWindowFrame; } @@ -78,26 +96,33 @@ void HotkeySettings::CreateColumnHeaders(void) { auto* emptySpace = new wxStaticText(m_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); auto* keyboard = new wxStaticText(m_panel, wxID_ANY, "Keyboard", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); + auto* controller = new wxStaticText(m_panel, wxID_ANY, "Controller", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); keyboard->SetMinSize(m_minButtonSize); + controller->SetMinSize(m_minButtonSize); auto flags = wxSizerFlags().Expand(); m_sizer->Add(emptySpace, flags); m_sizer->Add(keyboard, flags); + m_sizer->Add(controller, flags); } void HotkeySettings::CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey) { auto* name = new wxStaticText(m_panel, wxID_ANY, label); auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.keyboard), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT); + auto* controllerInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.controller), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT); /* for starting input */ keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnKeyboardHotkeyInputLeftClick, this); + controllerInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnControllerHotkeyInputLeftClick, this); /* for cancelling and clearing input */ keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnKeyboardHotkeyInputRightClick), NULL, this); + controllerInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnControllerHotkeyInputRightClick), NULL, this); keyInput->SetMinSize(m_minButtonSize); + controllerInput->SetMinSize(m_minButtonSize); #if BOOST_OS_WINDOWS const wxColour inputButtonColor = 0xfafafa; @@ -105,14 +130,53 @@ void HotkeySettings::CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotke const wxColour inputButtonColor = GetBackgroundColour(); #endif keyInput->SetBackgroundColour(inputButtonColor); + controllerInput->SetBackgroundColour(inputButtonColor); auto flags = wxSizerFlags(1).Expand().Border(wxALL, 5).CenterVertical(); m_sizer->Add(name, flags); m_sizer->Add(keyInput, flags); + m_sizer->Add(controllerInput, flags); m_hotkeys.emplace_back(name, keyInput, controllerInput, cfgHotkey); } +void HotkeySettings::OnControllerTimer(wxTimerEvent& event) +{ + if (m_activeController.expired()) + { + m_controllerTimer->Stop(); + return; + } + auto& controller = *m_activeController.lock(); + auto buttons = controller.update_state().buttons; + if (!buttons.IsIdle()) + { + for (const auto& newHotkey : buttons.GetButtonList()) + { + m_controllerTimer->Stop(); + auto* inputButton = static_cast(m_controllerTimer->GetClientData()); + auto& cfgHotkey = *static_cast(inputButton->GetClientData()); + const auto oldHotkey = cfgHotkey.controller; + const bool isModifier = (&cfgHotkey == &s_cfgHotkeys.modifiers); + /* ignore same hotkeys and block duplicate hotkeys */ + if ((newHotkey != oldHotkey) && (isModifier || (newHotkey != s_cfgHotkeys.modifiers.controller)) && + (s_controllerHotkeyToFuncMap.find(newHotkey) == s_controllerHotkeyToFuncMap.end())) + { + m_needToSave |= true; + cfgHotkey.controller = newHotkey; + /* don't bind modifier to map */ + if (!isModifier) + { + s_controllerHotkeyToFuncMap.erase(oldHotkey); + s_controllerHotkeyToFuncMap[newHotkey] = s_cfgHotkeyToFuncMap.at(&cfgHotkey); + } + } + FinalizeInput(inputButton); + return; + } + } +} + void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event) { auto* inputButton = static_cast(event.GetEventObject()); @@ -127,6 +191,26 @@ void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event) m_activeInputButton = inputButton; } +void HotkeySettings::OnControllerHotkeyInputLeftClick(wxCommandEvent& event) +{ + auto* inputButton = static_cast(event.GetEventObject()); + if (m_activeInputButton) + { + /* ignore multiple clicks of the same button */ + if (inputButton == m_activeInputButton) return; + RestoreInputButton(); + } + m_controllerTimer->Stop(); + if (!SetActiveController()) + { + return; + } + inputButton->SetLabelText(m_editModeHotkeyText); + m_controllerTimer->SetClientData(inputButton); + m_controllerTimer->Start(25); + m_activeInputButton = inputButton; +} + void HotkeySettings::OnKeyboardHotkeyInputRightClick(wxMouseEvent& event) { if (m_activeInputButton) @@ -146,6 +230,41 @@ void HotkeySettings::OnKeyboardHotkeyInputRightClick(wxMouseEvent& event) } } +void HotkeySettings::OnControllerHotkeyInputRightClick(wxMouseEvent& event) +{ + if (m_activeInputButton) + { + RestoreInputButton(); + return; + } + auto* inputButton = static_cast(event.GetEventObject()); + auto& cfgHotkey = *static_cast(inputButton->GetClientData()); + ControllerHotkey_t newHotkey{ sHotkeyCfg::controllerNone }; + if (cfgHotkey.controller != newHotkey) + { + m_needToSave |= true; + s_controllerHotkeyToFuncMap.erase(cfgHotkey.controller); + cfgHotkey.controller = newHotkey; + FinalizeInput(inputButton); + } +} + +bool HotkeySettings::SetActiveController(void) +{ + auto emulatedController = InputManager::instance().get_controller(0); + if (emulatedController.use_count() <= 1) + { + return false; + } + const auto& controllers = emulatedController->get_controllers(); + if (controllers.empty()) + { + return false; + } + m_activeController = controllers.at(0); + return true; +} + void HotkeySettings::OnKeyUp(wxKeyEvent& event) { auto* inputButton = static_cast(event.GetEventObject()); @@ -178,6 +297,9 @@ void HotkeySettings::FinalizeInput(wxButton* inputButton) { inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); inputButton->SetLabelText(To_wxString(cfgHotkey.keyboard)); + } else if constexpr (std::is_same_v) + { + inputButton->SetLabelText(To_wxString(cfgHotkey.controller)); } m_activeInputButton = nullptr; } @@ -225,6 +347,14 @@ wxString HotkeySettings::To_wxString(uKeyboardHotkey hotkey) return ret; } +wxString HotkeySettings::To_wxString(ControllerHotkey_t hotkey) +{ + if ((hotkey == sHotkeyCfg::controllerNone) || m_activeController.expired()) { + return m_disabledHotkeyText; + } + return m_activeController.lock()->get_button_name(hotkey); +} + void HotkeySettings::CaptureInput(wxKeyEvent& event) { uKeyboardHotkey hotkey{}; @@ -236,3 +366,22 @@ void HotkeySettings::CaptureInput(wxKeyEvent& event) if (it != s_keyboardHotkeyToFuncMap.end()) it->second(); } + +void HotkeySettings::CaptureInput(const ControllerState& currentState, const ControllerState& lastState) +{ + const auto& modifier = s_cfgHotkeys.modifiers.controller; + if ((modifier >= 0) && currentState.buttons.GetButtonState(modifier)) + { + for (const auto& buttonId : currentState.buttons.GetButtonList()) + { + const auto it = s_controllerHotkeyToFuncMap.find(buttonId); + if (it == s_controllerHotkeyToFuncMap.end()) + continue; + /* only capture clicks */ + if (lastState.buttons.GetButtonState(buttonId)) + break; + it->second(); + break; + } + } +} diff --git a/src/gui/input/HotkeySettings.h b/src/gui/input/HotkeySettings.h index 19a6810c..77478ea9 100644 --- a/src/gui/input/HotkeySettings.h +++ b/src/gui/input/HotkeySettings.h @@ -12,6 +12,7 @@ public: static void Init(wxFrame* mainWindowFrame); static void CaptureInput(wxKeyEvent& event); + static void CaptureInput(const ControllerState& currentState, const ControllerState& lastState); HotkeySettings(wxWindow* parent); ~HotkeySettings(); @@ -20,23 +21,28 @@ private: inline static wxFrame* s_mainWindow = nullptr; static const std::unordered_map> s_cfgHotkeyToFuncMap; inline static std::unordered_map> s_keyboardHotkeyToFuncMap{}; + inline static std::unordered_map> s_controllerHotkeyToFuncMap{}; inline static auto& s_cfgHotkeys = GetConfig().hotkeys; wxPanel* m_panel; wxFlexGridSizer* m_sizer; wxButton* m_activeInputButton{ nullptr }; + wxTimer* m_controllerTimer{ nullptr }; const wxSize m_minButtonSize{ 250, 45 }; const wxString m_disabledHotkeyText{ _("----") }; const wxString m_editModeHotkeyText{ _("") }; std::vector m_hotkeys; + std::weak_ptr m_activeController{}; bool m_needToSave = false; /* helpers */ void CreateColumnHeaders(void); void CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey); wxString To_wxString(uKeyboardHotkey hotkey); + wxString To_wxString(ControllerHotkey_t hotkey); bool IsValidKeycodeUp(int keycode); + bool SetActiveController(void); template void FinalizeInput(wxButton* inputButton); @@ -46,6 +52,9 @@ private: /* events */ void OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event); + void OnControllerHotkeyInputLeftClick(wxCommandEvent& event); void OnKeyboardHotkeyInputRightClick(wxMouseEvent& event); + void OnControllerHotkeyInputRightClick(wxMouseEvent& event); void OnKeyUp(wxKeyEvent& event); + void OnControllerTimer(wxTimerEvent& event); }; diff --git a/src/input/api/Controller.cpp b/src/input/api/Controller.cpp index 75505740..07465220 100644 --- a/src/input/api/Controller.cpp +++ b/src/input/api/Controller.cpp @@ -1,5 +1,6 @@ #include "input/api/Controller.h" #include "config/CemuConfig.h" +#include "gui/input/HotkeySettings.h" #include "gui/guiWrapper.h" @@ -68,6 +69,7 @@ const ControllerState& ControllerBase::update_state() #undef APPLY_AXIS_BUTTON + HotkeySettings::CaptureInput(result, m_last_state); m_last_state = std::move(result); return m_last_state;