diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake
index 90110cd33..fce95b18a 100644
--- a/CMakeModules/GenerateSettingKeys.cmake
+++ b/CMakeModules/GenerateSettingKeys.cmake
@@ -194,6 +194,7 @@ if (ENABLE_QT)
"geometry"
"state"
"geometryRenderWindow"
+ "geometrySecondaryWindow"
"gameListHeaderState"
"microProfileDialogGeometry"
"name"
@@ -202,12 +203,15 @@ if (ENABLE_QT)
"use_touchpad"
"controller_touch_device"
"use_touch_from_button"
+ "input_maptype"
+ "controller_hotkey_maptype"
"touch_from_button_map"
"touch_from_button_maps" # Why are these two so similar? Basically typo bait
"nand_directory"
"sdmc_directory"
"game_id"
"KeySeq"
+ "controller_keyseq"
"gamedirs"
"libvorbis"
"Context"
diff --git a/dist/qt_themes/default/icons/256x256/automap_face_buttons.png b/dist/qt_themes/default/icons/256x256/automap_face_buttons.png
new file mode 100644
index 000000000..581192664
Binary files /dev/null and b/dist/qt_themes/default/icons/256x256/automap_face_buttons.png differ
diff --git a/dist/qt_themes/default/theme_default.qrc b/dist/qt_themes/default/theme_default.qrc
index 90ae777aa..4d035c33c 100644
--- a/dist/qt_themes/default/theme_default.qrc
+++ b/dist/qt_themes/default/theme_default.qrc
@@ -15,6 +15,7 @@
icons/48x48/sd_card.png
icons/128x128/cartridge.png
icons/256x256/azahar.png
+ icons/256x256/automap_face_buttons.png
icons/48x48/star.png
icons/256x256/plus_folder.png
diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h
index 5e2eab1d1..74556eb99 100644
--- a/src/android/app/src/main/jni/default_ini.h
+++ b/src/android/app/src/main/jni/default_ini.h
@@ -13,30 +13,28 @@ namespace DefaultINI {
// All of these setting keys are either not currently used by Android or are too niche to bother
// documenting (people can contribute documentation if they care), or some other explained reason
-constexpr std::array android_config_omitted_keys = {
- Keys::enable_gamemode,
- Keys::use_custom_storage,
- Keys::init_time_offset,
- Keys::physical_device,
- Keys::use_gles, // Niche
- Keys::dump_command_buffers,
- Keys::use_display_refresh_rate_detection,
- Keys::screen_top_stretch,
- Keys::screen_top_leftright_padding,
- Keys::screen_top_topbottom_padding,
- Keys::screen_bottom_stretch,
- Keys::screen_bottom_leftright_padding,
- Keys::screen_bottom_topbottom_padding,
- Keys::mono_render_option,
- Keys::log_regex_filter, // Niche
- Keys::video_encoder,
- Keys::video_encoder_options,
- Keys::video_bitrate,
- Keys::audio_encoder,
- Keys::audio_encoder_options,
- Keys::audio_bitrate,
- Keys::last_artic_base_addr, // On Android, this value is stored as a "preference"
-};
+constexpr std::array android_config_omitted_keys = {Keys::enable_gamemode,
+ Keys::use_custom_storage,
+ Keys::init_time_offset,
+ Keys::physical_device,
+ Keys::use_gles, // Niche
+ Keys::dump_command_buffers,
+ Keys::use_display_refresh_rate_detection,
+ Keys::screen_top_stretch,
+ Keys::screen_top_leftright_padding,
+ Keys::screen_top_topbottom_padding,
+ Keys::screen_bottom_stretch,
+ Keys::screen_bottom_leftright_padding,
+ Keys::screen_bottom_topbottom_padding,
+ Keys::mono_render_option,
+ Keys::log_regex_filter, // Niche
+ Keys::video_encoder,
+ Keys::video_encoder_options,
+ Keys::video_bitrate,
+ Keys::audio_encoder,
+ Keys::audio_encoder_options,
+ Keys::audio_bitrate,
+ Keys::last_artic_base_addr};
// clang-format off
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 20971828a..3732da83f 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -63,6 +63,9 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
configuration/configure_hotkeys.cpp
configuration/configure_hotkeys.h
configuration/configure_hotkeys.ui
+ configuration/configure_hotkeys_controller.cpp
+ configuration/configure_hotkeys_controller.h
+ configuration/configure_hotkeys_controller.ui
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input.ui
@@ -140,6 +143,8 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
game_list_worker.h
hotkeys.cpp
hotkeys.h
+ hotkey_monitor.cpp
+ hotkey_monitor.h
loading_screen.cpp
loading_screen.h
loading_screen.ui
@@ -188,6 +193,8 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL
util/graphics_device_info.h
util/sequence_dialog/sequence_dialog.cpp
util/sequence_dialog/sequence_dialog.h
+ util/sequence_dialog/controller_sequence_dialog.cpp
+ util/sequence_dialog/controller_sequence_dialog.h
util/spinbox.cpp
util/spinbox.h
util/util.cpp
diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp
index 9bb0b2e34..6e78d0826 100644
--- a/src/citra_qt/citra_qt.cpp
+++ b/src/citra_qt/citra_qt.cpp
@@ -499,6 +499,7 @@ void GMainWindow::InitializeWidgets() {
action_secondary_rotate_screen = new QAction(secondary_window);
game_list = new GameList(*play_time_manager, this);
+
ui->horizontalLayout->addWidget(game_list);
game_list_placeholder = new GameListPlaceholder(this);
@@ -778,19 +779,32 @@ void GMainWindow::InitializeSaveStateMenuActions() {
void GMainWindow::InitializeHotkeys() {
hotkey_registry.LoadHotkeys();
-
- const QString main_window = QStringLiteral("Main Window");
+ hotkey_registry.buttonMonitor.start(16);
+ LOG_DEBUG(Frontend, "Initializing hotkeys");
const QString fullscreen = QStringLiteral("Fullscreen");
-
// QAction Hotkeys
const auto link_action_shortcut = [&](QAction* action, const QString& action_name,
- const bool primary_only = false) {
- static const QString main_window = QStringLiteral("Main Window");
- action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
+ const bool primary_only = false,
+ const QString group = QStringLiteral("Main Window")) {
+ auto context = hotkey_registry.GetShortcutContext(group, action_name);
+ auto shortcut = hotkey_registry.GetKeySequence(group, action_name);
+ action->setShortcut(shortcut);
+ action->setShortcutContext(context);
action->setAutoRepeat(false);
this->addAction(action);
+<<<<<<< HEAD
if (!primary_only)
secondary_window->addAction(action);
+ hotkey_registry.SetAction(main_window, action_name, action);
+=======
+ // handle the shortcuts that are different per-screen
+ if (context == Qt::WidgetShortcut) {
+ render_window->addAction(action);
+ if (!primary_only) {
+ secondary_window->addAction(action);
+ }
+ }
+>>>>>>> 88d1bef1c (Bugfix: All desktop hotkeys now work in dual-window mode.)
};
link_action_shortcut(ui->action_Load_File, QStringLiteral("Load File"));
@@ -802,7 +816,7 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Stop, QStringLiteral("Stop Emulation"));
link_action_shortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar"));
link_action_shortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
- link_action_shortcut(ui->action_Fullscreen, fullscreen, true);
+ link_action_shortcut(ui->action_Fullscreen, fullscreen);
link_action_shortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens"));
link_action_shortcut(ui->action_Screen_Layout_Upright_Screens,
@@ -822,12 +836,13 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
// QShortcut Hotkeys
- const auto connect_shortcut = [&](const QString& action_name, const auto& function) {
- const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
- const auto* secondary_hotkey =
- hotkey_registry.GetHotkey(main_window, action_name, secondary_window);
- connect(hotkey, &QShortcut::activated, this, function);
- connect(secondary_hotkey, &QShortcut::activated, this, function);
+ const auto connect_shortcut = [&](const QString& action_name, const auto& function,
+ const QString& group = QStringLiteral("Main Window"),
+ QObject* parent = nullptr) {
+ if (parent == nullptr)
+ parent = this;
+ const auto* hotkey = hotkey_registry.GetHotkey(group, action_name, parent);
+ connect(hotkey, &QShortcut::activated, parent, function);
};
connect_shortcut(QStringLiteral("Toggle Screen Layout"), &GMainWindow::ToggleScreenLayout);
@@ -888,6 +903,36 @@ void GMainWindow::InitializeHotkeys() {
}
});
+ // Navigation Actions - send keyboard shortcuts from controller buttons, but only when
+ // a game window is NOT in focus
+ const auto connect_navigation_shortcut =
+ [&](const QString& action_name, Qt::Key key, Qt::KeyboardModifier mod = Qt::NoModifier,
+ const QString& group = QStringLiteral("Navigation"), QObject* parent = nullptr) {
+ if (parent == nullptr)
+ parent = this;
+ const auto* hotkey = hotkey_registry.GetHotkey(group, action_name, parent);
+ connect(hotkey, &QShortcut::activated, this, [this, key, mod] {
+ if (QApplication::focusWidget() == render_window ||
+ QApplication::focusWidget() == secondary_window)
+ return;
+ QKeyEvent press(QEvent::KeyPress, key, mod);
+ QKeyEvent release(QEvent::KeyRelease, key, mod);
+ QApplication::sendEvent(QApplication::focusWidget(), &press);
+ QApplication::sendEvent(QApplication::focusWidget(), &release);
+ });
+ };
+
+ // TODO: Improve keyboard navigation everywhere so these actually work everywhere
+ connect_navigation_shortcut(QStringLiteral("Move down (Down Arrow)"), Qt::Key_Down);
+ connect_navigation_shortcut(QStringLiteral("Move up (Up Arrow)"), Qt::Key_Up);
+ connect_navigation_shortcut(QStringLiteral("Move left (Left Arrow)"), Qt::Key_Left);
+ connect_navigation_shortcut(QStringLiteral("Move right (Right Arrow)"), Qt::Key_Right);
+ connect_navigation_shortcut(QStringLiteral("Confirm / Run game (Enter)"), Qt::Key_Enter);
+ connect_navigation_shortcut(QStringLiteral("Go back / Cancel (Esc)"), Qt::Key_Escape);
+ connect_navigation_shortcut(QStringLiteral("Change Focus Forward (Tab)"), Qt::Key_Tab);
+ connect_navigation_shortcut(QStringLiteral("Change Focus Backward (Shift+Tab)"), Qt::Key_Tab,
+ Qt::ShiftModifier);
+
// Secondary Window QAction Hotkeys
const auto add_secondary_window_hotkey = [this](QAction* action, QKeySequence hotkey,
const char* slot) {
@@ -897,11 +942,6 @@ void GMainWindow::InitializeHotkeys() {
connect(action, SIGNAL(triggered()), this, slot);
secondary_window->addAction(action);
};
-
- // Use the same fullscreen hotkey as the main window
- const auto fullscreen_hotkey = hotkey_registry.GetKeySequence(main_window, fullscreen);
- add_secondary_window_hotkey(action_secondary_fullscreen, fullscreen_hotkey,
- SLOT(ToggleSecondaryFullscreen()));
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -920,6 +960,7 @@ void GMainWindow::RestoreUIState() {
restoreGeometry(UISettings::values.geometry);
restoreState(UISettings::values.state);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
+ secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry);
#if MICROPROFILE_ENABLED
microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
microProfileDialog->setVisible(UISettings::values.microprofile_visible.GetValue());
@@ -2592,14 +2633,25 @@ void GMainWindow::ToggleSecondaryFullscreen() {
}
void GMainWindow::ShowFullscreen() {
+ QWidget* window_to_change =
+ ui->action_Single_Window_Mode->isChecked() ? static_cast(this) : render_window;
+
if (ui->action_Single_Window_Mode->isChecked()) {
UISettings::values.geometry = saveGeometry();
ui->menubar->hide();
statusBar()->hide();
- showFullScreen();
} else {
UISettings::values.renderwindow_geometry = render_window->saveGeometry();
- render_window->showFullScreen();
+ }
+
+ bool secondary_on_same = window_to_change->screen() == secondary_window->screen();
+ window_to_change->showFullScreen();
+
+ // try to fullscreen the second window if it is visible and on a different screen from main
+ if (secondary_window->isVisible() && !secondary_on_same) {
+ UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry();
+ LOG_INFO(Frontend, "Attempting to fullscreen secondary window");
+ secondary_window->showFullScreen();
}
}
@@ -2613,12 +2665,16 @@ void GMainWindow::HideFullscreen() {
render_window->showNormal();
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
}
+ if (secondary_window->isFullScreen()) {
+ secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry);
+ secondary_window->showNormal();
+ }
}
void GMainWindow::ToggleWindowMode() {
if (ui->action_Single_Window_Mode->isChecked()) {
// Render in the main window...
- render_window->BackupGeometry();
+ UISettings::values.renderwindow_geometry = render_window->saveGeometry();
ui->horizontalLayout->addWidget(render_window);
render_window->setFocusPolicy(Qt::StrongFocus);
if (emulation_running) {
@@ -2634,7 +2690,7 @@ void GMainWindow::ToggleWindowMode() {
render_window->setFocusPolicy(Qt::NoFocus);
if (emulation_running) {
render_window->setVisible(true);
- render_window->RestoreGeometry();
+ render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
game_list->show();
}
}
@@ -2645,10 +2701,10 @@ void GMainWindow::UpdateSecondaryWindowVisibility() {
return;
}
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
- secondary_window->RestoreGeometry();
+ secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry);
secondary_window->show();
} else {
- secondary_window->BackupGeometry();
+ UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry();
secondary_window->hide();
}
}
@@ -3075,7 +3131,6 @@ void GMainWindow::OnCaptureScreenshot() {
.toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"))
.toStdString();
path.append(fmt::format("/{}_{}.png", filename, timestamp));
-
auto* const screenshot_window =
secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(
@@ -4137,6 +4192,7 @@ void GMainWindow::UpdateUISettings() {
if (!ui->action_Fullscreen->isChecked()) {
UISettings::values.geometry = saveGeometry();
UISettings::values.renderwindow_geometry = render_window->saveGeometry();
+ UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry();
}
UISettings::values.state = saveState();
#if MICROPROFILE_ENABLED
diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h
index cdf9eaff6..523d001b5 100644
--- a/src/citra_qt/citra_qt.h
+++ b/src/citra_qt/citra_qt.h
@@ -171,6 +171,7 @@ private:
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
void ShutdownGame();
+ std::vector> m_test_hotkeys;
#ifdef USE_DISCORD_PRESENCE
void SetDiscordEnabled(bool state);
@@ -371,6 +372,7 @@ private:
// Hotkeys
bool turbo_mode_active = false;
+ std::vector> nav_hotkeys;
// Whether emulation is currently running in Citra.
bool emulation_running = false;
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index a5aa9896b..762ef4196 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -52,50 +52,69 @@ const std::array, Settings::NativeAnalog::NumAnalogs> QtConfi
},
}};
-// This shouldn't have anything except static initializers (no functions). So
-// QKeySequence(...).toString() is NOT ALLOWED HERE.
-// This must be in alphabetical order according to action name as it must have the same order as
-// UISetting::values.shortcuts, which is alphabetically ordered.
+// These will be alphabetized when displayed, but the order doesn't matter here.
+// All hotkeys left at default context of Qt::ApplicationShortcut for now
+// TODO: determine if some hotkeys should only be applied in certain widgets and update group and
+// context
+
// clang-format off
-const std::array QtConfig::default_hotkeys {{
- {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
- {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
- {QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), Qt::ApplicationShortcut}},
- {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
- {QStringLiteral("Exit Azahar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
- {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
- {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
- {QStringLiteral("Increase 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl++"), Qt::ApplicationShortcut}},
- {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
- {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
- {QStringLiteral("Load from Newest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
- {QStringLiteral("Multiplayer Browse Public Rooms"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
- {QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
- {QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
- {QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
- {QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
- {QStringLiteral("Quick Save"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Quick Load"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
- {QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
- {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
- {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
- {QStringLiteral("Save to Oldest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}},
- {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
- {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
- {QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
- {QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Per-Application Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
- {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
- {QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
- {QStringLiteral("Toggle Turbo Mode"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
+const std::array QtConfig::default_hotkeys {{
+ {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral("")}},
+ {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("")}},
+ {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("")}},
+ {QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), QStringLiteral("")}},
+ {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral("")}},
+ {QStringLiteral("Exit Azahar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("")}},
+ {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral("")}},
+ {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("")}},
+ {QStringLiteral("Increase 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl++"), QStringLiteral("")}},
+ {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral("")}},
+ {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("")}},
+ {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral("")}},
+ {QStringLiteral("Load from Newest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), QStringLiteral("")}},
+ {QStringLiteral("Multiplayer Browse Public Rooms"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), QStringLiteral("")}},
+ {QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), QStringLiteral("")}},
+ {QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), QStringLiteral("")}},
+ {QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), QStringLiteral("")}},
+ {QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), QStringLiteral("")}},
+ {QStringLiteral("Quick Save"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Quick Load"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), QStringLiteral("")}},
+ {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral("")}},
+ {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral("")}},
+ {QStringLiteral("Save to Oldest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), QStringLiteral("")}},
+ {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral("")}},
+ {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral("")}},
+ {QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Per-Application Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral("")}},
+ {QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ {QStringLiteral("Toggle Turbo Mode"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral("")}},
+ // Navigation shortcuts will only be shown on the Controller Hotkeys pane, to duplicate keyboard navigation
+ // They will be ignored in the render_window and secondary_window at implementation, so still leave as default Qt::ApplicationShortcut context
+ {QStringLiteral("Move down (Down Arrow)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:12,engine:sdl,maptype:all,name:D-Pad Down")}},
+ {QStringLiteral("Move up (Up Arrow)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:11,engine:sdl,maptype:all,name:D-Pad Up")}},
+ {QStringLiteral("Move left (Left Arrow)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:13,engine:sdl,maptype:all,name:D-Pad Left")}},
+ {QStringLiteral("Move right (Right Arrow)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:14,engine:sdl,maptype:all,name:D-Pad Right")}},
+ {QStringLiteral("Confirm / Run game (Enter)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:0,engine:sdl,maptype:all,name:A / ✖ Button")}},
+ {QStringLiteral("Go back / Cancel (Esc)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:1,engine:sdl,maptype:all,name:B / ● Button")}},
+ {QStringLiteral("Change Focus Forward (Tab)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:10,engine:sdl,maptype:all,name:RB")}},
+ {QStringLiteral("Change Focus Backward (Shift+Tab)"), QStringLiteral("Navigation"), {QStringLiteral(""),
+ QStringLiteral("api:controller,button:9,engine:sdl,maptype:all,name:LB")}},
}};
// clang-format on
@@ -227,7 +246,8 @@ void QtConfig::WriteBasicSetting(const Settings::Setting, rang
}
qt_config->setValue(name, stringList);
}
-// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable
+// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not
+// human-readable
template <>
void QtConfig::WriteBasicSetting(const Settings::Setting& setting) {
const QString name = QString::fromStdString(setting.GetLabel());
@@ -336,6 +356,11 @@ void QtConfig::ReadControlValues() {
ReadBasicSetting(Settings::values.use_artic_base_controller);
+ UISettings::values.controller_hotkey_maptype = static_cast(
+ ReadSetting(Settings::QKeys::controller_hotkey_maptype,
+ static_cast(Settings::InputMappingType::AllControllers))
+ .toInt());
+
int num_touch_from_button_maps =
qt_config->beginReadArray(Settings::QKeys::touch_from_button_maps);
@@ -374,6 +399,8 @@ void QtConfig::ReadControlValues() {
Settings::InputProfile profile;
profile.name =
ReadSetting(Settings::QKeys::name, QStringLiteral("Default")).toString().toStdString();
+ profile.maptype = static_cast(
+ ReadSetting(Settings::QKeys::input_maptype, 2).toInt());
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
profile.buttons[i] = ReadSetting(QString::fromUtf8(Settings::NativeButton::mapping[i]),
@@ -500,7 +527,8 @@ void QtConfig::ReadDataStorageValues() {
void QtConfig::ReadDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
- // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ // Intentionally not using the QT default setting as this is intended to be changed in the
+ // ini
Settings::values.record_frame_times =
qt_config->value(Settings::QKeys::record_frame_times, false).toBool();
ReadBasicSetting(Settings::values.use_gdbstub);
@@ -735,13 +763,16 @@ void QtConfig::ReadShortcutValues() {
for (const auto& [name, group, shortcut] : default_hotkeys) {
qt_config->beginGroup(group);
qt_config->beginGroup(name);
- // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
- // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
- // a file dialog in windowed mode
+ // No longer using ReadSetting for shortcut.second as it innacurately returns a value of
+ // 1 for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the
+ // open a file dialog in windowed mode
UISettings::values.shortcuts.push_back(
{name,
group,
- {ReadSetting(Settings::QKeys::KeySeq, shortcut.keyseq).toString(), shortcut.context}});
+ {ReadSetting(Settings::QKeys::KeySeq, shortcut.keyseq).toString(),
+ ReadSetting(Settings::QKeys::controller_keyseq, shortcut.controller_keyseq)
+ .toString(),
+ shortcut.context}});
qt_config->endGroup();
qt_config->endGroup();
}
@@ -886,6 +917,8 @@ void QtConfig::ReadUILayoutValues() {
UISettings::values.state = ReadSetting(Settings::QKeys::state).toByteArray();
UISettings::values.renderwindow_geometry =
ReadSetting(Settings::QKeys::geometryRenderWindow).toByteArray();
+ UISettings::values.secondarywindow_geometry =
+ ReadSetting(Settings::QKeys::geometrySecondaryWindow).toByteArray();
UISettings::values.gamelist_header_state =
ReadSetting(Settings::QKeys::gameListHeaderState).toByteArray();
UISettings::values.microprofile_geometry =
@@ -983,7 +1016,9 @@ void QtConfig::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
WriteBasicSetting(Settings::values.use_artic_base_controller);
-
+ WriteSetting(Settings::QKeys::controller_hotkey_maptype,
+ static_cast(UISettings::values.controller_hotkey_maptype.GetValue()),
+ static_cast(Settings::InputMappingType::GuidPort));
WriteSetting(Settings::QKeys::profile, Settings::values.current_input_profile_index, 0);
qt_config->beginWriteArray(QStringLiteral("profiles"));
for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) {
@@ -991,6 +1026,8 @@ void QtConfig::SaveControlValues() {
const auto& profile = Settings::values.input_profiles[p];
WriteSetting(Settings::QKeys::name, QString::fromStdString(profile.name),
QStringLiteral("default"));
+ WriteSetting(Settings::QKeys::input_maptype, static_cast(profile.maptype),
+ static_cast(Settings::InputMappingType::GuidPort));
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(QString::fromStdString(Settings::NativeButton::mapping[i]),
@@ -1086,7 +1123,8 @@ void QtConfig::SaveDataStorageValues() {
void QtConfig::SaveDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
- // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ // Intentionally not using the QT default setting as this is intended to be changed in the
+ // ini
qt_config->setValue(Settings::QKeys::record_frame_times, Settings::values.record_frame_times);
WriteBasicSetting(Settings::values.use_gdbstub);
WriteBasicSetting(Settings::values.gdbstub_port);
@@ -1287,6 +1325,8 @@ void QtConfig::SaveShortcutValues() {
qt_config->beginGroup(name);
WriteSetting(Settings::QKeys::KeySeq, shortcut.keyseq, default_hotkey.keyseq);
WriteSetting(Settings::QKeys::Context, shortcut.context, default_hotkey.context);
+ WriteSetting(Settings::QKeys::controller_keyseq, shortcut.controller_keyseq,
+ default_hotkey.controller_keyseq);
qt_config->endGroup();
qt_config->endGroup();
}
@@ -1413,6 +1453,8 @@ void QtConfig::SaveUILayoutValues() {
WriteSetting(Settings::QKeys::geometry, UISettings::values.geometry);
WriteSetting(Settings::QKeys::state, UISettings::values.state);
WriteSetting(Settings::QKeys::geometryRenderWindow, UISettings::values.renderwindow_geometry);
+ WriteSetting(Settings::QKeys::geometrySecondaryWindow,
+ UISettings::values.secondarywindow_geometry);
WriteSetting(Settings::QKeys::gameListHeaderState, UISettings::values.gamelist_header_state);
WriteSetting(Settings::QKeys::microProfileDialogGeometry,
UISettings::values.microprofile_geometry);
diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h
index 3fba498ed..f2f040262 100644
--- a/src/citra_qt/configuration/config.h
+++ b/src/citra_qt/configuration/config.h
@@ -26,7 +26,7 @@ public:
static const std::array default_buttons;
static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs;
- static const std::array default_hotkeys;
+ static const std::array default_hotkeys;
private:
void Initialize(const std::string& config_name);
diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp
index 0f0fe2e71..c8de31b88 100644
--- a/src/citra_qt/configuration/configure_dialog.cpp
+++ b/src/citra_qt/configuration/configure_dialog.cpp
@@ -12,6 +12,7 @@
#include "citra_qt/configuration/configure_general.h"
#include "citra_qt/configuration/configure_graphics.h"
#include "citra_qt/configuration/configure_hotkeys.h"
+#include "citra_qt/configuration/configure_hotkeys_controller.h"
#include "citra_qt/configuration/configure_input.h"
#include "citra_qt/configuration/configure_layout.h"
#include "citra_qt/configuration/configure_storage.h"
@@ -32,6 +33,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
system_tab{std::make_unique(system, this)},
input_tab{std::make_unique(system, this)},
hotkeys_tab{std::make_unique(this)},
+ hotkeys_controller_tab{std::make_unique(this)},
graphics_tab{
std::make_unique(gl_renderer, physical_devices, is_powered_on, this)},
enhancements_tab{std::make_unique(this)},
@@ -48,7 +50,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
ui->tabWidget->addTab(general_tab.get(), tr("General"));
ui->tabWidget->addTab(system_tab.get(), tr("System"));
ui->tabWidget->addTab(input_tab.get(), tr("Input"));
- ui->tabWidget->addTab(hotkeys_tab.get(), tr("Hotkeys"));
+ ui->tabWidget->addTab(hotkeys_controller_tab.get(), tr("Controller Hotkeys"));
+ ui->tabWidget->addTab(hotkeys_tab.get(), tr("Keyboard Hotkeys"));
ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
ui->tabWidget->addTab(enhancements_tab.get(), tr("Enhancements"));
ui->tabWidget->addTab(layout_tab.get(), tr("Layout"));
@@ -60,7 +63,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
ui->tabWidget->addTab(ui_tab.get(), tr("UI"));
hotkeys_tab->Populate(registry);
-
+ hotkeys_controller_tab->Populate(registry);
PopulateSelectionList();
connect(ui_tab.get(), &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
@@ -104,6 +107,7 @@ void ConfigureDialog::ApplyConfiguration() {
input_tab->ApplyConfiguration();
input_tab->ApplyProfile();
hotkeys_tab->ApplyConfiguration(registry);
+ hotkeys_controller_tab->ApplyConfiguration(registry);
graphics_tab->ApplyConfiguration();
enhancements_tab->ApplyConfiguration();
layout_tab->ApplyConfiguration();
@@ -127,7 +131,7 @@ void ConfigureDialog::PopulateSelectionList() {
{tr("System"), {system_tab.get(), camera_tab.get(), storage_tab.get()}},
{tr("Graphics"), {enhancements_tab.get(), layout_tab.get(), graphics_tab.get()}},
{tr("Audio"), {audio_tab.get()}},
- {tr("Controls"), {input_tab.get(), hotkeys_tab.get()}}}};
+ {tr("Controls"), {input_tab.get(), hotkeys_controller_tab.get(), hotkeys_tab.get()}}}};
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
@@ -158,6 +162,7 @@ void ConfigureDialog::RetranslateUI() {
system_tab->RetranslateUI();
input_tab->RetranslateUI();
hotkeys_tab->RetranslateUI();
+ hotkeys_controller_tab->RetranslateUI();
graphics_tab->RetranslateUI();
enhancements_tab->RetranslateUI();
layout_tab->RetranslateUI();
@@ -174,19 +179,21 @@ void ConfigureDialog::UpdateVisibleTabs() {
if (items.isEmpty())
return;
- const std::map widgets = {{general_tab.get(), tr("General")},
- {system_tab.get(), tr("System")},
- {input_tab.get(), tr("Input")},
- {hotkeys_tab.get(), tr("Hotkeys")},
- {enhancements_tab.get(), tr("Enhancements")},
- {layout_tab.get(), tr("Layout")},
- {graphics_tab.get(), tr("Advanced")},
- {audio_tab.get(), tr("Audio")},
- {camera_tab.get(), tr("Camera")},
- {debug_tab.get(), tr("Debug")},
- {storage_tab.get(), tr("Storage")},
- {web_tab.get(), tr("Web")},
- {ui_tab.get(), tr("UI")}};
+ const std::map widgets = {
+ {general_tab.get(), tr("General")},
+ {system_tab.get(), tr("System")},
+ {input_tab.get(), tr("Input")},
+ {hotkeys_tab.get(), tr("Keyboard Hotkeys")},
+ {hotkeys_controller_tab.get(), tr("Controller Hotkeys")},
+ {enhancements_tab.get(), tr("Enhancements")},
+ {layout_tab.get(), tr("Layout")},
+ {graphics_tab.get(), tr("Advanced")},
+ {audio_tab.get(), tr("Audio")},
+ {camera_tab.get(), tr("Camera")},
+ {debug_tab.get(), tr("Debug")},
+ {storage_tab.get(), tr("Storage")},
+ {web_tab.get(), tr("Web")},
+ {ui_tab.get(), tr("UI")}};
ui->tabWidget->clear();
diff --git a/src/citra_qt/configuration/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h
index ce3c6170c..ed9b8d9d0 100644
--- a/src/citra_qt/configuration/configure_dialog.h
+++ b/src/citra_qt/configuration/configure_dialog.h
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -23,6 +23,7 @@ class ConfigureGeneral;
class ConfigureSystem;
class ConfigureInput;
class ConfigureHotkeys;
+class ConfigureControllerHotkeys;
class ConfigureGraphics;
class ConfigureLayout;
class ConfigureEnhancements;
@@ -65,6 +66,7 @@ private:
std::unique_ptr system_tab;
std::unique_ptr input_tab;
std::unique_ptr hotkeys_tab;
+ std::unique_ptr hotkeys_controller_tab;
std::unique_ptr graphics_tab;
std::unique_ptr enhancements_tab;
std::unique_ptr layout_tab;
diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp
index ecaed462b..0f260c159 100644
--- a/src/citra_qt/configuration/configure_hotkeys.cpp
+++ b/src/citra_qt/configuration/configure_hotkeys.cpp
@@ -29,7 +29,7 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->hotkey_list->setModel(model);
- ui->hotkey_list->setColumnWidth(0, 250);
+ ui->hotkey_list->setColumnWidth(0, 300);
ui->hotkey_list->resizeColumnToContents(hotkey_column);
connect(ui->button_restore_defaults, &QPushButton::clicked, this,
@@ -57,17 +57,18 @@ QList ConfigureHotkeys::GetUsedKeyList() const {
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
for (const auto& group : registry.hotkey_groups) {
+ // skip the controller navigation group in the keyboard hotkey config window
+ if (group.first == QStringLiteral("Navigation"))
+ continue;
QStandardItem* parent_item = new QStandardItem(group.first);
parent_item->setEditable(false);
for (const auto& hotkey : group.second) {
QStandardItem* action = new QStandardItem(hotkey.first);
QStandardItem* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
- QStandardItem* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
action->setEditable(false);
keyseq->setEditable(false);
- controller_keyseq->setEditable(false);
- parent_item->appendRow({action, keyseq, controller_keyseq});
+ parent_item->appendRow({action, keyseq});
}
model->appendRow(parent_item);
}
diff --git a/src/citra_qt/configuration/configure_hotkeys_controller.cpp b/src/citra_qt/configuration/configure_hotkeys_controller.cpp
new file mode 100644
index 000000000..48259c4c5
--- /dev/null
+++ b/src/citra_qt/configuration/configure_hotkeys_controller.cpp
@@ -0,0 +1,166 @@
+// Copyright Citra Emulator Project / Azahar Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+#include "citra_qt/configuration/config.h"
+#include "citra_qt/configuration/configure_hotkeys_controller.h"
+#include "citra_qt/configuration/configure_input.h"
+#include "citra_qt/hotkeys.h"
+#include "citra_qt/util/sequence_dialog/controller_sequence_dialog.h"
+#include "ui_configure_hotkeys_controller.h"
+
+constexpr int name_column = 0;
+constexpr int readable_hotkey_column = 1;
+constexpr int hotkey_column = 2;
+
+ConfigureControllerHotkeys::ConfigureControllerHotkeys(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique()) {
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+ ui->comboBoxMappingType->setCurrentIndex(
+ static_cast(UISettings::values.controller_hotkey_maptype.GetValue()));
+ model = new QStandardItemModel(this);
+ model->setColumnCount(2);
+ model->setHorizontalHeaderLabels({tr("Action"), tr("Controller Hotkey")});
+
+ connect(ui->hotkey_list, &QTreeView::doubleClicked, this,
+ &ConfigureControllerHotkeys::Configure);
+ connect(ui->hotkey_list, &QTreeView::customContextMenuRequested, this,
+ &ConfigureControllerHotkeys::PopupContextMenu);
+ ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->hotkey_list->setModel(model);
+
+ ui->hotkey_list->setColumnWidth(0, 300);
+ ui->hotkey_list->resizeColumnToContents(readable_hotkey_column);
+
+ connect(ui->button_clear_all, &QPushButton::clicked, this,
+ &ConfigureControllerHotkeys::ClearAll);
+}
+
+ConfigureControllerHotkeys::~ConfigureControllerHotkeys() = default;
+
+void ConfigureControllerHotkeys::Populate(const HotkeyRegistry& registry) {
+ for (const auto& group : registry.hotkey_groups) {
+ QStandardItem* parent_item = new QStandardItem(group.first);
+ parent_item->setEditable(false);
+ for (const auto& hotkey : group.second) {
+ QStandardItem* action = new QStandardItem(hotkey.first);
+ QStandardItem* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
+ QStandardItem* readable_keyseq = new QStandardItem(
+ HotkeyRegistry::SequenceToString(hotkey.second.controller_keyseq));
+ action->setEditable(false);
+ controller_keyseq->setEditable(false);
+ parent_item->appendRow({action, readable_keyseq, controller_keyseq});
+ }
+ model->appendRow(parent_item);
+ }
+
+ ui->hotkey_list->expandAll();
+}
+
+void ConfigureControllerHotkeys::Configure(QModelIndex index) {
+ if (!index.parent().isValid()) {
+ return;
+ }
+
+ // Swap to the hotkey column
+ index = index.sibling(index.row(), hotkey_column);
+ QModelIndex readableIndex = index.sibling(index.row(), readable_hotkey_column);
+
+ const auto previous_key = model->data(index);
+
+ ControllerSequenceDialog hotkey_dialog{this};
+
+ const int return_code = hotkey_dialog.exec();
+ const auto key_sequence = hotkey_dialog.GetSequence();
+ if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {
+ return;
+ }
+ model->setData(index, key_sequence);
+ model->setData(readableIndex, HotkeyRegistry::SequenceToString(key_sequence));
+}
+
+void ConfigureControllerHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
+ Settings::InputMappingType maptype = UISettings::values.controller_hotkey_maptype =
+ static_cast(ui->comboBoxMappingType->currentIndex());
+
+ for (int key_id = 0; key_id < model->rowCount(); key_id++) {
+ QStandardItem* parent = model->item(key_id, 0);
+ for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
+ const QStandardItem* action = parent->child(key_column_id, name_column);
+ const QStandardItem* controller_keyseq = parent->child(key_column_id, hotkey_column);
+ if (controller_keyseq->text().isEmpty())
+ continue;
+ const QStringList sequences = controller_keyseq->text().split(QStringLiteral("||"));
+ std::vector params;
+ std::transform(sequences.begin(), sequences.end(), std::back_inserter(params),
+ [](const QString& s) { return Common::ParamPackage(s.toStdString()); });
+ if (maptype == Settings::InputMappingType::AllControllers) {
+ for (auto& param : params)
+ param.Set("maptype", "all");
+ } else if (maptype == Settings::InputMappingType::Guid) {
+ for (auto& param : params)
+ param.Set("maptype", "guid");
+ } else {
+ for (auto& param : params)
+ param.Set("maptype", "guid+port");
+ }
+
+ for (auto& [group, sub_actions] : registry.hotkey_groups) {
+ if (group != parent->text())
+ continue;
+ for (auto& [action_name, hotkey] : sub_actions) {
+ if (action_name == action->text()) {
+ QStringList parts;
+ for (const auto& param : params) {
+ parts.append(QString::fromStdString(param.Serialize()));
+ }
+ hotkey.controller_keyseq = parts.join(QStringLiteral("||"));
+ registry.UpdateControllerHotkey(action_name, hotkey);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ registry.SaveHotkeys();
+}
+
+void ConfigureControllerHotkeys::ClearAll() {
+ for (int r = 0; r < model->rowCount(); ++r) {
+ const QStandardItem* parent = model->item(r, 0);
+
+ for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
+ model->item(r, 0)->child(r2, readable_hotkey_column)->setText(QString{});
+ model->item(r, 0)->child(r2, hotkey_column)->setText(QString{});
+ }
+ }
+}
+
+void ConfigureControllerHotkeys::PopupContextMenu(const QPoint& menu_location) {
+ const auto index = ui->hotkey_list->indexAt(menu_location);
+ if (!index.parent().isValid()) {
+ return;
+ }
+
+ QMenu context_menu;
+ QAction* clear = context_menu.addAction(tr("Clear"));
+
+ const auto readable_hotkey_index = index.sibling(index.row(), readable_hotkey_column);
+ const auto hotkey_index = index.sibling(index.row(), hotkey_column);
+ connect(clear, &QAction::triggered, this, [this, hotkey_index, readable_hotkey_index] {
+ model->setData(hotkey_index, QString{});
+ model->setData(readable_hotkey_index, QString{});
+ });
+
+ context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
+}
+
+void ConfigureControllerHotkeys::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/citra_qt/configuration/configure_hotkeys_controller.h b/src/citra_qt/configuration/configure_hotkeys_controller.h
new file mode 100644
index 000000000..115b59f91
--- /dev/null
+++ b/src/citra_qt/configuration/configure_hotkeys_controller.h
@@ -0,0 +1,42 @@
+// Copyright Citra Emulator Project / Azahar Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+namespace Ui {
+class ConfigureControllerHotkeys;
+}
+
+class HotkeyRegistry;
+class QStandardItemModel;
+
+class ConfigureControllerHotkeys : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureControllerHotkeys(QWidget* parent = nullptr);
+ ~ConfigureControllerHotkeys() override;
+
+ void ApplyConfiguration(HotkeyRegistry& registry);
+ void RetranslateUI();
+
+ /**
+ * Populates the hotkey list widget using data from the provided registry.
+ * Called everytime the Configure dialog is opened.
+ * @param registry The HotkeyRegistry whose data is used to populate the list.
+ */
+ void Populate(const HotkeyRegistry& registry);
+
+private:
+ void Configure(QModelIndex index);
+ void ClearAll();
+ void PopupContextMenu(const QPoint& menu_location);
+
+ std::unique_ptr ui;
+
+ QStandardItemModel* model;
+};
diff --git a/src/citra_qt/configuration/configure_hotkeys_controller.ui b/src/citra_qt/configuration/configure_hotkeys_controller.ui
new file mode 100644
index 000000000..a6422caec
--- /dev/null
+++ b/src/citra_qt/configuration/configure_hotkeys_controller.ui
@@ -0,0 +1,135 @@
+
+
+ ConfigureControllerHotkeys
+
+
+
+ 0
+ 0
+ 933
+ 388
+
+
+
+ Controller Hotkey Settings
+
+
+ -
+
+
-
+
+
+ Double-click on a binding to change it. You can use two-button chords as well.
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Clear All
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 166
+ 0
+
+
+
+
+ 1920192
+ 16777215
+
+
+
+ Some mappings cannot be applied to all controllers, such as back buttons
+
+
+ Apply Controller Hotkeys To:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 150
+ 0
+
+
+
-
+
+ All controllers
+
+
+ -
+
+ Controllers of the mapped type
+
+
+ -
+
+ Only the mapped controller
+
+
+
+
+
+
+ -
+
+
-
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp
index 2cc319968..6f072a7ad 100644
--- a/src/citra_qt/configuration/configure_input.cpp
+++ b/src/citra_qt/configuration/configure_input.cpp
@@ -70,88 +70,15 @@ static void SetAnalogButton(const Common::ParamPackage& input_param,
}
static QString ButtonToText(const Common::ParamPackage& param) {
- if (!param.Has("engine")) {
- return QObject::tr("[not set]");
- }
- const auto engine_str = param.Get("engine", "");
- if (engine_str == "keyboard") {
+ if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
+ } else {
+ return QString::fromStdString(InputCommon::ButtonToText(param));
}
-
- if (engine_str == "sdl") {
- if (param.Has("hat")) {
- const QString hat_str = QString::fromStdString(param.Get("hat", ""));
- const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
- return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
- }
-
- if (param.Has("axis")) {
- const QString axis_str = QString::fromStdString(param.Get("axis", ""));
- const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
- return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
- }
-
- if (param.Has("button")) {
- const QString button_str = QString::fromStdString(param.Get("button", ""));
-
- return QObject::tr("Button %1").arg(button_str);
- }
-
- return {};
- }
-
- if (engine_str == "gcpad") {
- if (param.Has("axis")) {
- const QString axis_str = QString::fromStdString(param.Get("axis", ""));
- const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
- return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
- }
- if (param.Has("button")) {
- const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
- return QObject::tr("GC Button %1").arg(button_str);
- }
- return GetKeyName(param.Get("code", 0));
- }
-
- return QObject::tr("[unknown]");
}
static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
- if (!param.Has("engine")) {
- return QObject::tr("[not set]");
- }
-
- const auto engine_str = param.Get("engine", "");
- if (engine_str == "analog_from_button") {
- return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
- }
-
- const QString axis_x_str{QString::fromStdString(param.Get("axis_x", ""))};
- const QString axis_y_str{QString::fromStdString(param.Get("axis_y", ""))};
- static const QString plus_str{QString::fromStdString("+")};
- static const QString minus_str{QString::fromStdString("-")};
- if (engine_str == "sdl" || engine_str == "gcpad") {
- if (dir == "modifier") {
- return QObject::tr("[unused]");
- }
- if (dir == "left") {
- return QObject::tr("Axis %1%2").arg(axis_x_str, minus_str);
- }
- if (dir == "right") {
- return QObject::tr("Axis %1%2").arg(axis_x_str, plus_str);
- }
- if (dir == "up") {
- return QObject::tr("Axis %1%2").arg(axis_y_str, plus_str);
- }
- if (dir == "down") {
- return QObject::tr("Axis %1%2").arg(axis_y_str, minus_str);
- }
- return {};
- }
- return QObject::tr("[unknown]");
+ return QString::fromStdString(InputCommon::AnalogToText(param, dir));
}
ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
@@ -230,7 +157,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
// If the user closes the dialog, the changes are reverted in
// `GMainWindow::OnConfigure()`
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Button);
});
@@ -241,7 +167,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), this, [&] {
buttons_param[button_id] =
@@ -249,7 +174,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
QtConfig::default_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
@@ -269,7 +193,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Button);
});
@@ -281,7 +204,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), this, [&] {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
@@ -291,7 +213,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
menu_location));
@@ -308,7 +229,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
[this, analog_id](const Common::ParamPackage& params) {
analogs_param[analog_id] = params;
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Analog);
}
@@ -328,7 +248,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
}
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
}
@@ -343,7 +262,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
SetAnalogButton(params, analogs_param[analog_id], "modifier");
}
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
},
InputCommon::Polling::DeviceType::Button);
});
@@ -357,7 +275,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
}
ui->buttonCircleMod->setText(tr("[not set]"));
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.addAction(tr("Restore Default"), this, [&] {
@@ -371,7 +288,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
AnalogToText(analogs_param[analog_id], "modifier"));
}
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
});
context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location));
});
@@ -395,7 +311,6 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
connect(ui->profile, qOverload(&QComboBox::currentIndexChanged), this, [this](int i) {
ApplyConfiguration();
- Settings::SaveProfile(Settings::values.current_input_profile_index);
Settings::LoadProfile(i);
LoadConfiguration();
});
@@ -407,7 +322,8 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
- if (params.Has("engine")) {
+ // skip button downs and only process button ups to maintain former behavior
+ if (params.Has("engine") && !params.Has("down")) {
SetPollingResult(params, false);
return;
}
@@ -423,12 +339,44 @@ void ConfigureInput::ApplyConfiguration() {
Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked();
+ Settings::values.current_input_profile.maptype =
+ static_cast(ui->comboBoxMappingType->currentIndex());
+
std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.current_input_profile.buttons.begin(),
- [](const Common::ParamPackage& param) { return param.Serialize(); });
+ [](Common::ParamPackage& param) {
+ if (param.Get("engine", "keyboard") == "sdl") {
+ if (Settings::values.current_input_profile.maptype ==
+ Settings::InputMappingType::AllControllers)
+ param.Set("maptype", "all");
+ else if (Settings::values.current_input_profile.maptype ==
+ Settings::InputMappingType::Guid)
+ param.Set("maptype", "guid");
+ else
+ param.Set("maptype", "guid+port");
+ } else {
+ param.Erase("maptype");
+ }
+ return param.Serialize();
+ });
std::transform(analogs_param.begin(), analogs_param.end(),
Settings::values.current_input_profile.analogs.begin(),
- [](const Common::ParamPackage& param) { return param.Serialize(); });
+ [](Common::ParamPackage& param) {
+ if (param.Get("engine", "keyboard") == "sdl") {
+ if (Settings::values.current_input_profile.maptype ==
+ Settings::InputMappingType::AllControllers)
+ param.Set("maptype", "all");
+ else if (Settings::values.current_input_profile.maptype ==
+ Settings::InputMappingType::Guid)
+ param.Set("maptype", "guid");
+ else
+ param.Set("maptype", "guid+port");
+ } else {
+ param.Erase("maptype");
+ }
+ return param.Serialize();
+ });
+ Settings::SaveProfile(ui->profile->currentIndex());
}
void ConfigureInput::ApplyProfile() {
@@ -469,6 +417,8 @@ QList ConfigureInput::GetUsedKeyboardKeys() {
void ConfigureInput::LoadConfiguration() {
ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue());
+ ui->comboBoxMappingType->setCurrentIndex(
+ static_cast(Settings::values.current_input_profile.maptype));
ui->use_artic_controller->setEnabled(!system.IsPoweredOn());
std::transform(Settings::values.current_input_profile.buttons.begin(),
@@ -495,7 +445,6 @@ void ConfigureInput::RestoreDefaults() {
UpdateButtonLabels();
ApplyConfiguration();
- Settings::SaveProfile(Settings::values.current_input_profile_index);
}
void ConfigureInput::ClearAll() {
@@ -509,7 +458,6 @@ void ConfigureInput::ClearAll() {
UpdateButtonLabels();
ApplyConfiguration();
- Settings::SaveProfile(Settings::values.current_input_profile_index);
}
void ConfigureInput::UpdateButtonLabels() {
@@ -583,16 +531,21 @@ void ConfigureInput::MapFromButton(const Common::ParamPackage& params) {
void ConfigureInput::AutoMap() {
ui->buttonAutoMap->setEnabled(false);
- if (QMessageBox::information(this, tr("Information"),
- tr("After pressing OK, press any button on your joystick"),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
+ QMessageBox box(this);
+ box.setWindowTitle(tr("Auto map Controller"));
+ box.setText(tr("After pressing OK, press the A (right) button on your gamepad"));
+ box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
+ QPixmap pixmap(QStringLiteral(":/icons/default/256x256/automap_face_buttons.png"));
+ pixmap = pixmap.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ box.setIconPixmap(pixmap);
+ int result = box.exec();
+ if (result == QMessageBox::Cancel) {
ui->buttonAutoMap->setEnabled(true);
return;
}
input_setter = [this](const Common::ParamPackage& params) {
MapFromButton(params);
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
};
device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button);
want_keyboard_keys = false;
@@ -690,7 +643,6 @@ void ConfigureInput::NewProfile() {
}
ApplyConfiguration();
- Settings::SaveProfile(ui->profile->currentIndex());
Settings::CreateProfile(name.toStdString());
ui->profile->addItem(name);
ui->profile->setCurrentIndex(Settings::values.current_input_profile_index);
diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h
index f70b32240..a984811ba 100644
--- a/src/citra_qt/configuration/configure_input.h
+++ b/src/citra_qt/configuration/configure_input.h
@@ -1,4 +1,4 @@
-// Copyright Citra Emulator Project / Lime3DS Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -43,6 +43,7 @@ public:
/// Save the current input profile index
void ApplyProfile();
+
public slots:
void OnHotkeysChanged(QList new_key_list);
diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui
index 67cc7688f..b1e743c5e 100644
--- a/src/citra_qt/configuration/configure_input.ui
+++ b/src/citra_qt/configuration/configure_input.ui
@@ -6,61 +6,163 @@
0
0
- 441
- 727
+ 716
+ 694
ConfigureInput
+
+ 2
+
+
+ 12
+
-
-
-
-
-
-
- Profile
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- New
-
-
-
- -
-
-
- Delete
-
-
-
- -
-
-
- Rename
-
-
-
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+
+ 4
+
+
+ 12
+
+
+ 8
+
+
+ 12
+
+
+ 8
+
+ -
+
+
-
+
+
+ Profile
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ New
+
+
+
+ -
+
+
+ Delete
+
+
+
+ -
+
+
+ Rename
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 166
+ 0
+
+
+
+
+ 1920192
+ 16777215
+
+
+
+ Some mappings cannot be applied to all controllers, such as back buttons
+
+
+ Apply Game Controller Maps To:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 150
+ 0
+
+
+
-
+
+ All controllers
+
+
+ -
+
+ Controllers of the mapped type
+
+
+ -
+
+ Only the mapped controller
+
+
+
+
+
+
+
+
-
@@ -68,6 +170,14 @@
true
+
+
+ 0
+ 0
+ 675
+ 996
+
+
-
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index f34753ca7..84d057922 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include
#include
#include
#include
@@ -44,6 +45,37 @@
#include "core/hle/service/fs/archive.h"
#include "qcursor.h"
+// On Mac OS X, neither the Return nor Enter key triggers the activation signal for some reason. Fix
+// it.
+
+void GameListTreeView::keyPressEvent(QKeyEvent* event) {
+ if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
+ const QModelIndex idx = currentIndex();
+ if (idx.isValid()) {
+ emit activated(idx);
+ return;
+ }
+ }
+ QTreeView::keyPressEvent(event);
+}
+
+// if focus goes into the game list and nothing is already selected, select the first actual game
+void GameListTreeView::focusInEvent(QFocusEvent* event) {
+ if (!currentIndex().isValid()) {
+ for (int i = 0; i < model()->rowCount(); i++) {
+ const QModelIndex folder = model()->index(i, 0);
+ if (model()->rowCount(folder) > 0) {
+ const QModelIndex first = model()->index(0, 0, folder);
+ setCurrentIndex(first);
+ selectionModel()->select(first,
+ QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ break;
+ }
+ }
+ }
+ QTreeView::focusInEvent(event);
+}
+
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
: QObject(parent), gamelist{gamelist} {}
@@ -88,6 +120,13 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
}
break;
}
+ // up, down, tab
+ // If the up arrow, down arrow, or tab key is pressed, this should move focus to the
+ // treeview
+ case Qt::Key_Up:
+ case Qt::Key_Down: {
+ gamelist->tree_view->setFocus();
+ }
default:
return QObject::eventFilter(obj, event);
}
@@ -355,7 +394,7 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p
this->main_window = parent;
layout = new QVBoxLayout;
- tree_view = new QTreeView;
+ tree_view = new GameListTreeView;
search_field = new GameListSearchField(this);
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
@@ -371,7 +410,6 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p
tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
tree_view->setItemDelegateForColumn(0, new CartridgeIconDelegate(tree_view));
tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
-
UpdateColumnVisibility();
item_model->insertColumns(0, COLUMN_COUNT);
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index 25348d86b..f71f3849e 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -21,6 +21,7 @@ enum class MediaType : u32;
class GameListWorker;
class GameListDir;
class GameListSearchField;
+class GameListTreeView;
class GMainWindow;
class QFileSystemWatcher;
class QHBoxLayout;
@@ -94,6 +95,10 @@ public:
static const QStringList supported_file_extensions;
+ GameListTreeView* GetGameTreeView() {
+ return tree_view;
+ }
+
signals:
void GameChosen(const QString& game_path);
void ShouldCancelWorker();
@@ -137,10 +142,11 @@ private:
void changeEvent(QEvent*) override;
void RetranslateUI();
+protected:
GameListSearchField* search_field;
GMainWindow* main_window = nullptr;
QVBoxLayout* layout = nullptr;
- QTreeView* tree_view = nullptr;
+ GameListTreeView* tree_view = nullptr;
QStandardItemModel* item_model = nullptr;
GameListWorker* current_worker = nullptr;
QFileSystemWatcher* watcher = nullptr;
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 45df2a483..d74170749 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
#include "citra_qt/uisettings.h"
#include "citra_qt/util/util.h"
@@ -529,3 +530,9 @@ private:
QLabel* label_filter_result = nullptr;
QToolButton* button_filter_close = nullptr;
};
+
+class GameListTreeView : public QTreeView {
+protected:
+ void keyPressEvent(QKeyEvent* event) override;
+ void focusInEvent(QFocusEvent* event) override;
+};
diff --git a/src/citra_qt/hotkey_monitor.cpp b/src/citra_qt/hotkey_monitor.cpp
new file mode 100644
index 000000000..7ffce7df1
--- /dev/null
+++ b/src/citra_qt/hotkey_monitor.cpp
@@ -0,0 +1,92 @@
+// Copyright Citra Emulator Project / Azahar Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+#include
+#include "core/frontend/input.h"
+#include "hotkey_monitor.h"
+#include "hotkeys.h"
+
+struct ControllerHotkeyMonitor::ButtonState {
+ Hotkey* hk;
+ bool lastStatus = false;
+ bool lastStatus2 = false;
+};
+
+ControllerHotkeyMonitor::ControllerHotkeyMonitor() {
+ m_buttons = std::make_unique>();
+ m_timer = new QTimer();
+ QObject::connect(m_timer, &QTimer::timeout, [this]() { checkAllButtons(); });
+}
+
+void ControllerHotkeyMonitor::start(const int msec) {
+ m_timer->start(msec);
+}
+
+ControllerHotkeyMonitor::~ControllerHotkeyMonitor() {
+ delete m_timer;
+}
+
+void ControllerHotkeyMonitor::addButton(const QString& name, Hotkey* hk) {
+ (*m_buttons)[name] = {hk, false};
+}
+
+void ControllerHotkeyMonitor::removeButton(const QString& name) {
+ m_buttons->erase(name);
+}
+
+void ControllerHotkeyMonitor::checkAllButtons() {
+ // Controller Hotkeys
+ for (auto& [name, it] : *m_buttons) {
+ bool trigger = false;
+ if (!it.hk || !it.hk->button_device)
+ continue;
+ bool currentStatus = it.hk->button_device->GetStatus();
+ if (it.hk->button_device2) {
+ // two buttons, need both pressed and one *just now* pressed
+ bool currentStatus2 = it.hk->button_device2->GetStatus();
+ trigger = currentStatus && currentStatus2 && (!it.lastStatus || !it.lastStatus2);
+ it.lastStatus = currentStatus;
+ it.lastStatus2 = currentStatus2;
+ } else {
+ // if only one button, trigger as soon as pressed
+ trigger = currentStatus && !it.lastStatus;
+ it.lastStatus = currentStatus;
+ }
+ if (trigger) {
+ if (it.hk->action) {
+ it.hk->action->trigger();
+ }
+ for (auto const& [name, hotkey_shortcut] : it.hk->shortcuts) {
+ if (hotkey_shortcut && hotkey_shortcut->isEnabled()) {
+ QWidget* parent = qobject_cast(hotkey_shortcut->parent());
+ if (!parent)
+ continue;
+ if (name == QStringLiteral("move down")) {
+ std::cout << "move down triggered before context check" << std::endl;
+ }
+ bool shouldFire = true;
+ // Code to honor context, so we can set different contexts and parents
+ // appropriately
+ if (hotkey_shortcut->context() == Qt::WidgetShortcut) {
+ shouldFire = parent == QApplication::focusWidget();
+ } else if (hotkey_shortcut->context() == Qt::WidgetWithChildrenShortcut) {
+ shouldFire = parent == QApplication::focusWidget() ||
+ parent->isAncestorOf(QApplication::focusWidget());
+ } else if (hotkey_shortcut->context() == Qt::WindowShortcut) {
+ shouldFire = parent->window()->isActiveWindow();
+ }
+
+ if (shouldFire) {
+ hotkey_shortcut->activated();
+ break;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/citra_qt/hotkey_monitor.h b/src/citra_qt/hotkey_monitor.h
new file mode 100644
index 000000000..ecd4e3296
--- /dev/null
+++ b/src/citra_qt/hotkey_monitor.h
@@ -0,0 +1,28 @@
+// Copyright Citra Emulator Project / Azahar Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include