diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 6bb7ac34..6946aa37 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -343,6 +343,13 @@ void CemuConfig::Load(XMLConfigParser& parser) dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.port = dsuc.get_attribute("port", dsu_client.port); + // hotkeys + auto xml_hotkeys = parser.get("Hotkeys"); + hotkeys.exitFullscreen.raw = xml_hotkeys.get("ExitFullscreen", WXK_ESCAPE); + hotkeys.toggleFullscreen.raw = xml_hotkeys.get("ToggleFullscreen", WXK_F11); + hotkeys.toggleFullscreenAlt.raw = xml_hotkeys.get("ToggleFullscreenAlt", uHotkey{WXK_CONTROL_M, true}.raw); // ALT+ENTER + hotkeys.takeScreenshot.raw = xml_hotkeys.get("TakeScreenshot", WXK_F12); + // emulatedusbdevices auto usbdevices = parser.get("EmulatedUsbDevices"); emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); @@ -544,6 +551,13 @@ void CemuConfig::Save(XMLConfigParser& parser) dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("port", dsu_client.port); + // hotkeys + auto xml_hotkeys = config.set("Hotkeys"); + xml_hotkeys.set("ExitFullscreen", hotkeys.exitFullscreen.raw); + xml_hotkeys.set("ToggleFullscreen", hotkeys.toggleFullscreen.raw); + xml_hotkeys.set("ToggleFullscreenAlt", hotkeys.toggleFullscreenAlt.raw); + xml_hotkeys.set("TakeScreenshot", hotkeys.takeScreenshot.raw); + // emulated usb devices auto usbdevices = config.set("EmulatedUsbDevices"); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 62665f6d..fb9912d5 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -191,6 +191,18 @@ enum class CrashDump ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif +typedef union +{ + struct + { + uint16_t key : 13; // enough bits for all keycodes + uint16_t alt : 1; + uint16_t ctrl : 1; + uint16_t shift : 1; + }; + uint16_t raw; +} uHotkey; + template <> struct fmt::formatter : formatter { template @@ -499,6 +511,15 @@ struct CemuConfig ConfigValue port{ 26760 }; }dsu_client{}; + // hotkeys + struct + { + uHotkey toggleFullscreen{}; + uHotkey toggleFullscreenAlt{}; + uHotkey exitFullscreen{}; + uHotkey takeScreenshot{}; + } hotkeys{}; + // debug ConfigValueBounds crash_dump{ CrashDump::Disabled }; ConfigValue gdb_port{ 1337 }; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7cdc208e..744ad544 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -71,6 +71,8 @@ add_library(CemuGui helpers/wxLogEvent.h helpers/wxWayland.cpp helpers/wxWayland.h + input/HotkeySettings.cpp + input/HotkeySettings.h input/InputAPIAddWindow.cpp input/InputAPIAddWindow.h input/InputSettings2.cpp diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index c3606292..37fdbe6d 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -11,6 +11,7 @@ #include "input/InputManager.h" #include "gui/helpers/wxHelpers.h" #include "Cemu/ncrypto/ncrypto.h" +#include "gui/input/HotkeySettings.h" #if BOOST_OS_LINUX && HAS_WAYLAND #include "gui/helpers/wxWayland.h" @@ -331,6 +332,8 @@ bool CemuApp::OnInit() std::unique_lock lock(g_mutex); g_window_info.app_active = true; + HotkeySettings::Init(m_mainFrame); + SetTopWindow(m_mainFrame); m_mainFrame->Show(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 48bdd7d7..f6be72f4 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -40,6 +40,7 @@ #include "gui/helpers/wxHelpers.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" #include "gui/input/InputSettings2.h" +#include "gui/input/HotkeySettings.h" #include "input/InputManager.h" #if BOOST_OS_WINDOWS @@ -91,6 +92,7 @@ enum MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MAINFRAME_MENU_ID_OPTIONS_AUDIO, MAINFRAME_MENU_ID_OPTIONS_INPUT, + MAINFRAME_MENU_ID_OPTIONS_HOTKEY, // options -> account MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, @@ -186,6 +188,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, MainWindow::OnOptionsInput) // tools menu EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) @@ -922,6 +925,12 @@ void MainWindow::OnOptionsInput(wxCommandEvent& event) break; } + case MAINFRAME_MENU_ID_OPTIONS_HOTKEY: + { + auto* frame = new HotkeySettings(this); + frame->Show(); + break; + } } } @@ -1437,13 +1446,15 @@ void MainWindow::OnKeyUp(wxKeyEvent& event) if (swkbd_hasKeyboardInputHook()) return; - const auto code = event.GetKeyCode(); - if (code == WXK_ESCAPE) - SetFullScreen(false); - else if (code == WXK_RETURN && event.AltDown() || code == WXK_F11) - SetFullScreen(!IsFullScreen()); - else if (code == WXK_F12) - g_window_info.has_screenshot_request = true; // async screenshot request + uHotkey hotkey{}; + hotkey.key = event.GetKeyCode(); + hotkey.alt = event.AltDown(); + hotkey.ctrl = event.ControlDown(); + hotkey.shift = event.ShiftDown(); + const auto& hotkeyMap = HotkeySettings::s_hotkeyToFuncMap; + const auto it = hotkeyMap.find(hotkey.raw); + if (it != hotkeyMap.end()) + it->second(); } void MainWindow::OnKeyDown(wxKeyEvent& event) @@ -2159,6 +2170,7 @@ void MainWindow::RecreateMenu() optionsMenu->AppendSeparator(); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings")); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings")); + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, _("&Hotkey settings")); optionsMenu->AppendSeparator(); optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account")); diff --git a/src/gui/input/HotkeySettings.cpp b/src/gui/input/HotkeySettings.cpp new file mode 100644 index 00000000..d6e22ecb --- /dev/null +++ b/src/gui/input/HotkeySettings.cpp @@ -0,0 +1,181 @@ +#include "gui/input/HotkeySettings.h" +#include +#include + +extern WindowInfo g_window_info; +const std::unordered_map> HotkeySettings::s_cfgHotkeyToFuncMap{ + {&s_cfgHotkeys.toggleFullscreen, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }}, + {&s_cfgHotkeys.toggleFullscreenAlt, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }}, + {&s_cfgHotkeys.exitFullscreen, [](void) { s_mainWindow->ShowFullScreen(false); }}, + {&s_cfgHotkeys.takeScreenshot, [](void) { g_window_info.has_screenshot_request = true; }}, +}; + +struct HotkeyEntry +{ + std::unique_ptr name; + std::unique_ptr keyInput; + uHotkey& hotkey; + + HotkeyEntry(wxStaticText* name, wxButton* keyInput, uHotkey& hotkey) + : name(name), keyInput(keyInput), hotkey(hotkey) + { + keyInput->SetClientData(&hotkey); + } +}; + +HotkeySettings::HotkeySettings(wxWindow* parent) + : wxFrame(parent, wxID_ANY, "Hotkey Settings") +{ + m_panel = new wxPanel(this); + m_sizer = new wxFlexGridSizer(0, 2, wxSize(0, 0)); + + m_panel->SetSizer(m_sizer); + Center(); + + CreateHotkey("Toggle fullscreen", s_cfgHotkeys.toggleFullscreen); + CreateHotkey("Take screenshot", s_cfgHotkeys.takeScreenshot); + + m_sizer->SetSizeHints(this); +} + +HotkeySettings::~HotkeySettings() +{ + if (m_needToSave) + { + g_config.Save(); + } +} + +void HotkeySettings::Init(wxFrame* mainWindowFrame) +{ + s_hotkeyToFuncMap.reserve(s_cfgHotkeyToFuncMap.size()); + for (const auto& [cfgHotkey, func] : s_cfgHotkeyToFuncMap) + { + auto hotkeyRaw = cfgHotkey->raw; + if (hotkeyRaw > 0) + { + s_hotkeyToFuncMap[hotkeyRaw] = func; + } + } + s_mainWindow = mainWindowFrame; +} + +void HotkeySettings::CreateHotkey(const wxString& label, uHotkey& hotkey) +{ + /* add new hotkey */ + { + auto* name = new wxStaticText(m_panel, wxID_ANY, label, wxDefaultPosition, wxSize(240, 30), wxALIGN_CENTER); + auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(hotkey), wxDefaultPosition, wxSize(120, 30), wxWANTS_CHARS); + + keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnHotkeyInputLeftClick, this); + keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this); + + auto flags = wxSizerFlags().Expand(); + m_sizer->Add(name, flags); + m_sizer->Add(keyInput, flags); + + m_hotkeys.emplace_back(name, keyInput, hotkey); + } +} + +void HotkeySettings::OnHotkeyInputLeftClick(wxCommandEvent& event) +{ + auto* inputButton = static_cast(event.GetEventObject()); + if (m_activeInputButton) + { + /* ignore multiple clicks of the same button */ + if (inputButton == m_activeInputButton) return; + FinalizeInput(m_activeInputButton); + } + inputButton->Bind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); + inputButton->SetLabelText('_'); + m_activeInputButton = inputButton; +} + +void HotkeySettings::OnHotkeyInputRightClick(wxMouseEvent& event) { + if (m_activeInputButton) + { + FinalizeInput(m_activeInputButton); + return; + } + auto* inputButton = static_cast(event.GetEventObject()); + auto& cfgHotkey = *static_cast(inputButton->GetClientData()); + uHotkey newHotkey{}; + if (cfgHotkey.raw != newHotkey.raw) + { + m_needToSave |= true; + s_hotkeyToFuncMap.erase(cfgHotkey.raw); + cfgHotkey = newHotkey; + } + FinalizeInput(inputButton); +} + +void HotkeySettings::OnKeyUp(wxKeyEvent& event) +{ + auto* inputButton = static_cast(event.GetEventObject()); + auto& cfgHotkey = *static_cast(inputButton->GetClientData()); + if (auto keycode = event.GetKeyCode(); IsValidKeycodeUp(keycode)) + { + auto oldHotkey = cfgHotkey; + uHotkey newHotkey{}; + newHotkey.key = keycode; + newHotkey.alt = event.AltDown(); + newHotkey.ctrl = event.ControlDown(); + newHotkey.shift = event.ShiftDown(); + if ((newHotkey.raw != oldHotkey.raw) && + (s_hotkeyToFuncMap.find(newHotkey.raw) == s_hotkeyToFuncMap.end())) + { + m_needToSave |= true; + cfgHotkey = newHotkey; + s_hotkeyToFuncMap.erase(oldHotkey.raw); + s_hotkeyToFuncMap[cfgHotkey.raw] = s_cfgHotkeyToFuncMap.at(&cfgHotkey); + } + } + FinalizeInput(inputButton); +} + +void HotkeySettings::FinalizeInput(wxButton* inputButton) +{ + auto& cfgHotkey = *static_cast(inputButton->GetClientData()); + inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); + inputButton->SetLabelText(To_wxString(cfgHotkey)); + m_activeInputButton = nullptr; +} + +bool HotkeySettings::IsValidKeycodeUp(int keycode) +{ + switch (keycode) + { + case WXK_NONE: + case WXK_ESCAPE: + case WXK_ALT: + case WXK_CONTROL: + case WXK_SHIFT: + return false; + default: + return true; + } +} + +wxString HotkeySettings::To_wxString(uHotkey hotkey) +{ + if (hotkey.raw <= 0) + { + return ""; + } + wxString ret_val; + if (hotkey.alt) + { + ret_val.append("ALT + "); + } + if (hotkey.ctrl) + { + ret_val.append("CTRL + "); + } + if (hotkey.shift) + { + ret_val.append("SHIFT + "); + } + ret_val.append(wxAcceleratorEntry(0, hotkey.key).ToString()); + return ret_val; +} diff --git a/src/gui/input/HotkeySettings.h b/src/gui/input/HotkeySettings.h new file mode 100644 index 00000000..8956fcd3 --- /dev/null +++ b/src/gui/input/HotkeySettings.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "config/CemuConfig.h" + +class HotkeyEntry; + +class HotkeySettings : public wxFrame +{ +public: + static void Init(wxFrame* mainWindowFrame); + inline static std::unordered_map> s_hotkeyToFuncMap{}; + + HotkeySettings(wxWindow* parent); + ~HotkeySettings(); + +private: + inline static wxFrame* s_mainWindow = nullptr; + inline static auto& s_cfgHotkeys = GetConfig().hotkeys; + static const std::unordered_map> s_cfgHotkeyToFuncMap; + + wxPanel* m_panel; + wxFlexGridSizer* m_sizer; + std::vector m_hotkeys; + wxButton* m_activeInputButton = nullptr; + bool m_needToSave = false; + + /* helpers */ + void CreateHotkey(const wxString& label, uHotkey& hotkey); + wxString To_wxString(uHotkey hotkey); + bool IsValidKeycodeUp(int keycode); + void FinalizeInput(wxButton* inputButton); + + /* events */ + void OnHotkeyInputLeftClick(wxCommandEvent& event); + void OnHotkeyInputRightClick(wxMouseEvent& event); + void OnKeyUp(wxKeyEvent& event); +};