diff --git a/Source/Core/Common/Keyboard.cpp b/Source/Core/Common/Keyboard.cpp index c93c97bd2a..29d8cef59e 100644 --- a/Source/Core/Common/Keyboard.cpp +++ b/Source/Core/Common/Keyboard.cpp @@ -140,7 +140,10 @@ int GetGameLayout() u8 MapVirtualKeyToHID(u8 virtual_key, int host_layout, int game_layout) { // SDL3 keyboard state uses scan codes already based on HID usage id - return TranslateUsageID(virtual_key, host_layout, game_layout); + u8 usage_id = virtual_key; + if (Config::Get(Config::MAIN_WII_KEYBOARD_TRANSLATION)) + usage_id = TranslateUsageID(usage_id, host_layout, game_layout); + return usage_id; } std::weak_ptr s_keyboard_context; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 423b292444..a6b22f7160 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -191,6 +191,8 @@ const Info MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFiles const Info MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false}; const Info MAIN_WII_KEYBOARD_HOST_LAYOUT{{System::Main, "Core", "WiiKeyboardHostLayout"}, 0}; const Info MAIN_WII_KEYBOARD_GAME_LAYOUT{{System::Main, "Core", "WiiKeyboardGameLayout"}, 0}; +const Info MAIN_WII_KEYBOARD_TRANSLATION{{System::Main, "Core", "WiiKeyboardTranslation"}, + false}; const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING{ {System::Main, "Core", "WiimoteContinuousScanning"}, false}; const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{ diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 46778d0f23..2f506edded 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -109,6 +109,7 @@ extern const Info MAIN_WII_SD_CARD_FILESIZE; extern const Info MAIN_WII_KEYBOARD; extern const Info MAIN_WII_KEYBOARD_HOST_LAYOUT; extern const Info MAIN_WII_KEYBOARD_GAME_LAYOUT; +extern const Info MAIN_WII_KEYBOARD_TRANSLATION; extern const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING; extern const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES; extern const Info MAIN_WIIMOTE_ENABLE_SPEAKER; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index f42b84925d..8dc6b4655b 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -249,6 +249,8 @@ add_executable(dolphin-emu DiscordHandler.h DiscordJoinRequestDialog.cpp DiscordJoinRequestDialog.h + EmulatedUSB/Keyboard.cpp + EmulatedUSB/Keyboard.h EmulatedUSB/WiiSpeakWindow.cpp EmulatedUSB/WiiSpeakWindow.h FIFO/FIFOAnalyzer.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 206b3203bb..e44e170176 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -158,6 +158,7 @@ + @@ -379,6 +380,7 @@ + diff --git a/Source/Core/DolphinQt/EmulatedUSB/Keyboard.cpp b/Source/Core/DolphinQt/EmulatedUSB/Keyboard.cpp new file mode 100644 index 0000000000..e21b390b36 --- /dev/null +++ b/Source/Core/DolphinQt/EmulatedUSB/Keyboard.cpp @@ -0,0 +1,90 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/EmulatedUSB/Keyboard.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Keyboard.h" +#include "Core/Config/MainSettings.h" +#include "DolphinQt/Config/ConfigControls/ConfigBool.h" +#include "DolphinQt/Settings.h" + +KeyboardWindow::KeyboardWindow(QWidget* parent) : QWidget(parent) +{ + // i18n: Window for managing the Wii keyboard emulation + setWindowTitle(tr("Keyboard Manager")); + setObjectName(QStringLiteral("keyboard_manager")); + setMinimumSize(QSize(500, 200)); + + auto* main_layout = new QVBoxLayout(); + + { + auto* group = new QGroupBox(); + auto* hbox_layout = new QHBoxLayout(); + hbox_layout->setAlignment(Qt::AlignHCenter); + auto* checkbox_emulate = new QCheckBox(tr("Emulate USB keyboard"), this); + checkbox_emulate->setChecked(Settings::Instance().IsUSBKeyboardConnected()); + connect(checkbox_emulate, &QCheckBox::toggled, this, &KeyboardWindow::EmulateKeyboard); + connect(&Settings::Instance(), &Settings::USBKeyboardConnectionChanged, checkbox_emulate, + &QCheckBox::setChecked); + hbox_layout->addWidget(checkbox_emulate); + group->setLayout(hbox_layout); + + main_layout->addWidget(group); + } + + { + auto* group = new QGroupBox(tr("Layout Configuration")); + auto* grid_layout = new QGridLayout(); + auto* checkbox_translate = + new ConfigBool(tr("Enable partial translation"), Config::MAIN_WII_KEYBOARD_TRANSLATION); + grid_layout->addWidget(checkbox_translate, 0, 0, 1, 2, Qt::AlignLeft); + + auto create_combo = [checkbox_translate, grid_layout](int row, const QString& name, + const auto& config_info) { + grid_layout->addWidget(new QLabel(name), row, 0); + auto* combo = new QComboBox(); + + combo->addItem(tr("Automatic detection"), Common::KeyboardLayout::AUTO); + combo->addItem(QStringLiteral("QWERTY"), Common::KeyboardLayout::QWERTY); + combo->addItem(QStringLiteral("AZERTY"), Common::KeyboardLayout::AZERTY); + combo->addItem(QStringLiteral("QWERTZ"), Common::KeyboardLayout::QWERTZ); + combo->setCurrentIndex(combo->findData(Config::Get(config_info))); + combo->setEnabled(checkbox_translate->isChecked()); + + connect(combo, &QComboBox::currentIndexChanged, combo, [combo, config_info] { + const auto keyboard_layout = combo->currentData(); + if (!keyboard_layout.isValid()) + return; + + Config::SetBaseOrCurrent(config_info, keyboard_layout.toInt()); + Common::KeyboardContext::UpdateLayout(); + }); + connect(checkbox_translate, &QCheckBox::toggled, combo, &QComboBox::setEnabled); + + grid_layout->addWidget(combo, row, 1); + }; + + create_combo(1, tr("Host layout:"), Config::MAIN_WII_KEYBOARD_HOST_LAYOUT); + create_combo(2, tr("Game layout:"), Config::MAIN_WII_KEYBOARD_GAME_LAYOUT); + group->setLayout(grid_layout); + + main_layout->addWidget(group); + } + + setLayout(main_layout); +} + +KeyboardWindow::~KeyboardWindow() = default; + +void KeyboardWindow::EmulateKeyboard(bool emulate) const +{ + Settings::Instance().SetUSBKeyboardConnected(emulate); +} diff --git a/Source/Core/DolphinQt/EmulatedUSB/Keyboard.h b/Source/Core/DolphinQt/EmulatedUSB/Keyboard.h new file mode 100644 index 0000000000..73d3547744 --- /dev/null +++ b/Source/Core/DolphinQt/EmulatedUSB/Keyboard.h @@ -0,0 +1,17 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class KeyboardWindow : public QWidget +{ + Q_OBJECT +public: + explicit KeyboardWindow(QWidget* parent = nullptr); + ~KeyboardWindow() override; + +private: + void EmulateKeyboard(bool emulate) const; +}; diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 891d11c094..23f7ffbad7 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -94,6 +94,7 @@ #include "DolphinQt/Debugger/ThreadWidget.h" #include "DolphinQt/Debugger/WatchWidget.h" #include "DolphinQt/DiscordHandler.h" +#include "DolphinQt/EmulatedUSB/Keyboard.h" #include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h" #include "DolphinQt/FIFO/FIFOPlayerWindow.h" #include "DolphinQt/GCMemcardManager.h" @@ -580,6 +581,7 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal); connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase); connect(m_menu_bar, &MenuBar::ShowWiiSpeakWindow, this, &MainWindow::ShowWiiSpeakWindow); + connect(m_menu_bar, &MenuBar::ShowKeyboard, this, &MainWindow::ShowKeyboard); connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote); #ifdef USE_RETRO_ACHIEVEMENTS @@ -1433,6 +1435,18 @@ void MainWindow::ShowWiiSpeakWindow() m_wii_speak_window->activateWindow(); } +void MainWindow::ShowKeyboard() +{ + if (!m_keyboard_window) + { + m_keyboard_window = new KeyboardWindow(); + } + + m_keyboard_window->show(); + m_keyboard_window->raise(); + m_keyboard_window->activateWindow(); +} + void MainWindow::StateLoad() { QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ? diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index afb44f4608..b71a829476 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -37,6 +37,7 @@ class GCTASInputWindow; class GraphicsWindow; class HotkeyScheduler; class InfinityBaseWindow; +class KeyboardWindow; class JITWidget; class LogConfigWidget; class LogWidget; @@ -178,6 +179,7 @@ private: void ShowSkylanderPortal(); void ShowInfinityBase(); void ShowWiiSpeakWindow(); + void ShowKeyboard(); void ShowMemcardManager(); void ShowResourcePackManager(); void ShowCheatsManager(); @@ -251,6 +253,7 @@ private: SkylanderPortalWindow* m_skylander_window = nullptr; InfinityBaseWindow* m_infinity_window = nullptr; WiiSpeakWindow* m_wii_speak_window = nullptr; + KeyboardWindow* m_keyboard_window = nullptr; MappingWindow* m_hotkey_window = nullptr; FreeLookWindow* m_freelook_window = nullptr; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index ead86839cb..0986f3469d 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -281,6 +281,7 @@ void MenuBar::AddToolsMenu() usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal); usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase); usb_device_menu->addAction(tr("&Wii Speak"), this, &MenuBar::ShowWiiSpeakWindow); + usb_device_menu->addAction(tr("&Keyboard"), this, &MenuBar::ShowKeyboard); tools_menu->addMenu(usb_device_menu); tools_menu->addSeparator(); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index 934772e72c..075155739c 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -95,6 +95,7 @@ signals: void ShowSkylanderPortal(); void ShowInfinityBase(); void ShowWiiSpeakWindow(); + void ShowKeyboard(); void ConnectWiiRemote(int id); #ifdef USE_RETRO_ACHIEVEMENTS