input: configurable controller hotkeys

This commit is contained in:
Anime 2025-03-30 15:20:22 +03:00
parent a9dbc3f9b5
commit 86424d907e
5 changed files with 160 additions and 7 deletions

View file

@ -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);

View file

@ -208,16 +208,17 @@ typedef sint16 ControllerHotkey_t;
struct sHotkeyCfg
{
uKeyboardHotkey keyboard{WXK_NONE};
ControllerHotkey_t controller{-1}; // -1 = disabled
/* 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;
}
};
@ -226,7 +227,7 @@ struct fmt::formatter<sHotkeyCfg> : formatter<string_view>
{
template <typename FormatContext>
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<string_view>::format(xml_values, ctx);
}
};
@ -542,6 +543,7 @@ struct CemuConfig
// hotkeys
struct
{
sHotkeyCfg modifiers;
sHotkeyCfg toggleFullscreen;
sHotkeyCfg toggleFullscreenAlt;
sHotkeyCfg exitFullscreen;

View file

@ -20,17 +20,21 @@ struct HotkeyEntry
{
enum class InputButtonType : wxWindowID {
Keyboard,
Controller,
};
std::unique_ptr<wxStaticText> name;
std::unique_ptr<wxButton> keyInput;
std::unique_ptr<wxButton> 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);
keyInput->SetId(static_cast<wxWindowID>(InputButtonType::Keyboard));
controllerInput->SetClientData(&hotkey);
controllerInput->SetId(static_cast<wxWindowID>(InputButtonType::Controller));
}
};
@ -39,24 +43,35 @@ HotkeySettings::HotkeySettings(wxWindow* parent)
{
SetIcon(wxICON(X_HOTKEY_SETTINGS));
m_sizer = new wxFlexGridSizer(0, 2, 10, 10);
m_sizer = new wxFlexGridSizer(0, 3, 10, 10);
m_sizer->AddGrowableCol(1);
m_sizer->AddGrowableCol(2);
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE);
m_panel->SetSizer(m_sizer);
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();
@ -73,6 +88,11 @@ void HotkeySettings::Init(wxFrame* mainWindowFrame)
{
s_keyboardHotkeyToFuncMap[keyboardHotkey] = func;
}
auto controllerHotkey = cfgHotkey->controller;
if (controllerHotkey > 0)
{
s_controllerHotkeyToFuncMap[controllerHotkey] = func;
}
}
s_mainWindow = mainWindowFrame;
}
@ -81,34 +101,78 @@ 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::OnHotkeyInputRightClick), NULL, this);
controllerInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this);
keyInput->SetMinSize(m_minButtonSize);
controllerInput->SetMinSize(m_minButtonSize);
auto flags = wxSizerFlags().Expand();
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<wxButton*>(m_controllerTimer->GetClientData());
auto& cfgHotkey = *static_cast<sHotkeyCfg*>(inputButton->GetClientData());
const auto oldHotkey = cfgHotkey.controller;
const bool is_modifier = (&cfgHotkey == &s_cfgHotkeys.modifiers);
/* ignore same hotkeys and block duplicate hotkeys */
if ((newHotkey != oldHotkey) && (is_modifier || (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 (!is_modifier) {
s_controllerHotkeyToFuncMap.erase(oldHotkey);
s_controllerHotkeyToFuncMap[newHotkey] = s_cfgHotkeyToFuncMap.at(&cfgHotkey);
}
}
FinalizeInput<ControllerHotkey_t>(inputButton);
return;
}
}
}
void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event)
{
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
@ -123,6 +187,26 @@ void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event)
m_activeInputButton = inputButton;
}
void HotkeySettings::OnControllerHotkeyInputLeftClick(wxCommandEvent& event)
{
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
if (m_activeInputButton)
{
/* ignore multiple clicks of the same button */
if (inputButton == m_activeInputButton) return;
RestoreInputButton(m_activeInputButton);
}
m_controllerTimer->Stop();
if (!SetActiveController())
{
return;
}
inputButton->SetLabelText('_');
m_controllerTimer->SetClientData(inputButton);
m_controllerTimer->Start(25);
m_activeInputButton = inputButton;
}
void HotkeySettings::OnHotkeyInputRightClick(wxMouseEvent& event)
{
if (m_activeInputButton)
@ -145,10 +229,36 @@ void HotkeySettings::OnHotkeyInputRightClick(wxMouseEvent& event)
FinalizeInput<uKeyboardHotkey>(inputButton);
}
} break;
case InputButtonType::Controller: {
ControllerHotkey_t newHotkey{ -1 };
if (cfgHotkey.controller != newHotkey)
{
m_needToSave |= true;
s_controllerHotkeyToFuncMap.erase(cfgHotkey.controller);
cfgHotkey.controller = newHotkey;
FinalizeInput<ControllerHotkey_t>(inputButton);
}
} break;
default: break;
}
}
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<wxButton*>(event.GetEventObject());
@ -181,6 +291,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<T, ControllerHotkey_t>)
{
inputButton->SetLabelText(To_wxString(cfgHotkey.controller));
}
m_activeInputButton = nullptr;
}
@ -193,6 +306,9 @@ void HotkeySettings::RestoreInputButton(wxButton* inputButton)
case InputButtonType::Keyboard: {
FinalizeInput<uKeyboardHotkey>(inputButton);
} break;
case InputButtonType::Controller: {
FinalizeInput<ControllerHotkey_t>(inputButton);
} break;
default: break;
}
}
@ -233,3 +349,13 @@ wxString HotkeySettings::To_wxString(uKeyboardHotkey hotkey)
}
return ret;
}
wxString HotkeySettings::To_wxString(ControllerHotkey_t hotkey)
{
wxString ret{};
if ((hotkey != -1) && !m_activeController.expired())
{
ret = m_activeController.lock()->get_button_name(hotkey);
}
return ret;
}

View file

@ -11,6 +11,7 @@ class HotkeySettings : public wxFrame
public:
static void Init(wxFrame* mainWindowFrame);
inline static std::unordered_map<uint16, std::function<void(void)>> s_keyboardHotkeyToFuncMap{};
inline static std::unordered_map<uint16, std::function<void(void)>> s_controllerHotkeyToFuncMap{};
inline static auto& s_cfgHotkeys = GetConfig().hotkeys;
HotkeySettings(wxWindow* parent);
@ -23,16 +24,20 @@ private:
wxPanel* m_panel;
wxFlexGridSizer* m_sizer;
wxButton* m_activeInputButton{ nullptr };
wxTimer* m_controllerTimer{ nullptr };
const wxSize m_minButtonSize{ 200, 30 };
std::vector<HotkeyEntry> m_hotkeys;
std::weak_ptr<ControllerBase> 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<typename T>
void FinalizeInput(wxButton* inputButton);
@ -40,6 +45,8 @@ private:
/* events */
void OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event);
void OnControllerHotkeyInputLeftClick(wxCommandEvent& event);
void OnHotkeyInputRightClick(wxMouseEvent& event);
void OnKeyUp(wxKeyEvent& event);
void OnControllerTimer(wxTimerEvent& event);
};

View file

@ -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,21 @@ const ControllerState& ControllerBase::update_state()
#undef APPLY_AXIS_BUTTON
/* hotkey capturing */
const auto& hotkey_mod = HotkeySettings::s_cfgHotkeys.modifiers.controller;
if ((hotkey_mod >= 0) && result.buttons.GetButtonState(hotkey_mod)) {
const auto& hotkey_map = HotkeySettings::s_controllerHotkeyToFuncMap;
for (const auto& button_id : result.buttons.GetButtonList()) {
const auto it = hotkey_map.find(button_id);
if (it == hotkey_map.end())
continue;
/* only capture clicks */
if (m_last_state.buttons.GetButtonState(button_id))
break;
it->second();
break;
}
}
m_last_state = std::move(result);
return m_last_state;