From 9a48f252ae7b4471286a7d8899a0c70dff354b1a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 14 Jul 2021 00:29:12 -0400 Subject: applets: Append qt_ prefix to Qt frontend applets --- src/yuzu/CMakeLists.txt | 25 +- src/yuzu/applets/controller.cpp | 695 ------ src/yuzu/applets/controller.h | 164 -- src/yuzu/applets/controller.ui | 2653 ---------------------- src/yuzu/applets/error.cpp | 63 - src/yuzu/applets/error.h | 33 - src/yuzu/applets/profile_select.cpp | 163 -- src/yuzu/applets/profile_select.h | 72 - src/yuzu/applets/qt_controller.cpp | 695 ++++++ src/yuzu/applets/qt_controller.h | 164 ++ src/yuzu/applets/qt_controller.ui | 2653 ++++++++++++++++++++++ src/yuzu/applets/qt_error.cpp | 63 + src/yuzu/applets/qt_error.h | 33 + src/yuzu/applets/qt_profile_select.cpp | 163 ++ src/yuzu/applets/qt_profile_select.h | 72 + src/yuzu/applets/qt_software_keyboard.cpp | 1620 +++++++++++++ src/yuzu/applets/qt_software_keyboard.h | 285 +++ src/yuzu/applets/qt_software_keyboard.ui | 3503 +++++++++++++++++++++++++++++ src/yuzu/applets/qt_web_browser.cpp | 417 ++++ src/yuzu/applets/qt_web_browser.h | 218 ++ src/yuzu/applets/qt_web_browser_scripts.h | 193 ++ src/yuzu/applets/software_keyboard.cpp | 1620 ------------- src/yuzu/applets/software_keyboard.h | 285 --- src/yuzu/applets/software_keyboard.ui | 3503 ----------------------------- src/yuzu/applets/web_browser.cpp | 417 ---- src/yuzu/applets/web_browser.h | 218 -- src/yuzu/applets/web_browser_scripts.h | 193 -- src/yuzu/main.cpp | 10 +- 28 files changed, 10097 insertions(+), 10096 deletions(-) delete mode 100644 src/yuzu/applets/controller.cpp delete mode 100644 src/yuzu/applets/controller.h delete mode 100644 src/yuzu/applets/controller.ui delete mode 100644 src/yuzu/applets/error.cpp delete mode 100644 src/yuzu/applets/error.h delete mode 100644 src/yuzu/applets/profile_select.cpp delete mode 100644 src/yuzu/applets/profile_select.h create mode 100644 src/yuzu/applets/qt_controller.cpp create mode 100644 src/yuzu/applets/qt_controller.h create mode 100644 src/yuzu/applets/qt_controller.ui create mode 100644 src/yuzu/applets/qt_error.cpp create mode 100644 src/yuzu/applets/qt_error.h create mode 100644 src/yuzu/applets/qt_profile_select.cpp create mode 100644 src/yuzu/applets/qt_profile_select.h create mode 100644 src/yuzu/applets/qt_software_keyboard.cpp create mode 100644 src/yuzu/applets/qt_software_keyboard.h create mode 100644 src/yuzu/applets/qt_software_keyboard.ui create mode 100644 src/yuzu/applets/qt_web_browser.cpp create mode 100644 src/yuzu/applets/qt_web_browser.h create mode 100644 src/yuzu/applets/qt_web_browser_scripts.h delete mode 100644 src/yuzu/applets/software_keyboard.cpp delete mode 100644 src/yuzu/applets/software_keyboard.h delete mode 100644 src/yuzu/applets/software_keyboard.ui delete mode 100644 src/yuzu/applets/web_browser.cpp delete mode 100644 src/yuzu/applets/web_browser.h delete mode 100644 src/yuzu/applets/web_browser_scripts.h diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f870b33b1..cb4bdcc7e 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -15,18 +15,19 @@ add_executable(yuzu about_dialog.cpp about_dialog.h aboutdialog.ui - applets/controller.cpp - applets/controller.h - applets/controller.ui - applets/error.cpp - applets/error.h - applets/profile_select.cpp - applets/profile_select.h - applets/software_keyboard.cpp - applets/software_keyboard.h - applets/software_keyboard.ui - applets/web_browser.cpp - applets/web_browser.h + applets/qt_controller.cpp + applets/qt_controller.h + applets/qt_controller.ui + applets/qt_error.cpp + applets/qt_error.h + applets/qt_profile_select.cpp + applets/qt_profile_select.h + applets/qt_software_keyboard.cpp + applets/qt_software_keyboard.h + applets/qt_software_keyboard.ui + applets/qt_web_browser.cpp + applets/qt_web_browser.h + applets/qt_web_browser_scripts.h bootmanager.cpp bootmanager.h compatdb.ui diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp deleted file mode 100644 index 836d90fda..000000000 --- a/src/yuzu/applets/controller.cpp +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include - -#include "common/assert.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/hle/lock.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" -#include "ui_controller.h" -#include "yuzu/applets/controller.h" -#include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_input_profile_dialog.h" -#include "yuzu/configuration/configure_motion_touch.h" -#include "yuzu/configuration/configure_vibration.h" -#include "yuzu/configuration/input_profiles.h" -#include "yuzu/main.h" - -namespace { - -constexpr std::size_t HANDHELD_INDEX = 8; - -constexpr std::array, 8> led_patterns{{ - {true, false, false, false}, - {true, true, false, false}, - {true, true, true, false}, - {true, true, true, true}, - {true, false, false, true}, - {true, false, true, false}, - {true, false, true, true}, - {false, true, true, false}, -}}; - -void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, - bool connected) { - Core::System& system{Core::System::GetInstance()}; - - if (!system.IsPoweredOn()) { - return; - } - - Service::SM::ServiceManager& sm = system.ServiceManager(); - - auto& npad = - sm.GetService("hid") - ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad); - - npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); -} - -// Returns true if the given controller type is compatible with the given parameters. -bool IsControllerCompatible(Settings::ControllerType controller_type, - Core::Frontend::ControllerParameters parameters) { - switch (controller_type) { - case Settings::ControllerType::ProController: - return parameters.allow_pro_controller; - case Settings::ControllerType::DualJoyconDetached: - return parameters.allow_dual_joycons; - case Settings::ControllerType::LeftJoycon: - return parameters.allow_left_joycon; - case Settings::ControllerType::RightJoycon: - return parameters.allow_right_joycon; - case Settings::ControllerType::Handheld: - return parameters.enable_single_mode && parameters.allow_handheld; - case Settings::ControllerType::GameCube: - return parameters.allow_gamecube_controller; - default: - return false; - } -} - -} // namespace - -QtControllerSelectorDialog::QtControllerSelectorDialog( - QWidget* parent, Core::Frontend::ControllerParameters parameters_, - InputCommon::InputSubsystem* input_subsystem_) - : QDialog(parent), ui(std::make_unique()), - parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, - input_profiles(std::make_unique()) { - ui->setupUi(this); - - player_widgets = { - ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, - ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, - }; - - player_groupboxes = { - ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, - ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, - ui->groupPlayer7Connected, ui->groupPlayer8Connected, - }; - - connected_controller_icons = { - ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, - ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, - }; - - led_patterns_boxes = {{ - {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, - ui->checkboxPlayer1LED4}, - {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, - ui->checkboxPlayer2LED4}, - {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, - ui->checkboxPlayer3LED4}, - {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, - ui->checkboxPlayer4LED4}, - {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, - ui->checkboxPlayer5LED4}, - {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, - ui->checkboxPlayer6LED4}, - {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, - ui->checkboxPlayer7LED4}, - {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, - ui->checkboxPlayer8LED4}, - }}; - - explain_text_labels = { - ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, - ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, - ui->labelPlayer7Explain, ui->labelPlayer8Explain, - }; - - emulated_controllers = { - ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, - ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, - ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, - }; - - player_labels = { - ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, - ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, - }; - - connected_controller_labels = { - ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, - ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, - ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, - }; - - connected_controller_checkboxes = { - ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, - ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, - ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, - }; - - // Setup/load everything prior to setting up connections. - // This avoids unintentionally changing the states of elements while loading them in. - SetSupportedControllers(); - DisableUnsupportedPlayers(); - - for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { - SetEmulatedControllers(player_index); - } - - LoadConfiguration(); - - for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { - SetExplainText(i); - UpdateControllerIcon(i); - UpdateLEDPattern(i); - UpdateBorderColor(i); - - connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { - if (checked) { - for (std::size_t index = 0; index <= i; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } else { - for (std::size_t index = i; index < NUM_PLAYERS; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } - }); - - connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), - [this, i](int) { - UpdateControllerIcon(i); - UpdateControllerState(i); - UpdateLEDPattern(i); - CheckIfParametersMet(); - }); - - connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { - player_groupboxes[i]->setChecked(state == Qt::Checked); - UpdateControllerIcon(i); - UpdateControllerState(i); - UpdateLEDPattern(i); - UpdateBorderColor(i); - CheckIfParametersMet(); - }); - - if (i == 0) { - connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), - [this, i](int index) { - UpdateDockedState(GetControllerTypeFromIndex(index, i) == - Settings::ControllerType::Handheld); - }); - } - } - - connect(ui->vibrationButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureVibrationDialog); - - connect(ui->motionButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureMotionTouchDialog); - - connect(ui->inputConfigButton, &QPushButton::clicked, this, - &QtControllerSelectorDialog::CallConfigureInputProfileDialog); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, - &QtControllerSelectorDialog::ApplyConfiguration); - - // Enhancement: Check if the parameters have already been met before disconnecting controllers. - // If all the parameters are met AND only allows a single player, - // stop the constructor here as we do not need to continue. - if (CheckIfParametersMet() && parameters.enable_single_mode) { - return; - } - - // If keep_controllers_connected is false, forcefully disconnect all controllers - if (!parameters.keep_controllers_connected) { - for (auto player : player_groupboxes) { - player->setChecked(false); - } - } - - resize(0, 0); -} - -QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; - -int QtControllerSelectorDialog::exec() { - if (parameters_met && parameters.enable_single_mode) { - return QDialog::Accepted; - } - return QDialog::exec(); -} - -void QtControllerSelectorDialog::ApplyConfiguration() { - const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); - Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); - - Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); - Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); -} - -void QtControllerSelectorDialog::LoadConfiguration() { - for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { - const auto connected = - Settings::values.players.GetValue()[index].connected || - (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); - player_groupboxes[index]->setChecked(connected); - connected_controller_checkboxes[index]->setChecked(connected); - emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( - Settings::values.players.GetValue()[index].controller_type, index)); - } - - UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); - - ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); - ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); -} - -void QtControllerSelectorDialog::CallConfigureVibrationDialog() { - ConfigureVibration dialog(this); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} - -void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() { - ConfigureMotionTouch dialog(this, input_subsystem); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Accepted) { - dialog.ApplyConfiguration(); - } -} - -void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { - ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get()); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - dialog.exec(); -} - -bool QtControllerSelectorDialog::CheckIfParametersMet() { - // Here, we check and validate the current configuration against all applicable parameters. - const auto num_connected_players = static_cast( - std::count_if(player_groupboxes.begin(), player_groupboxes.end(), - [this](const QGroupBox* player) { return player->isChecked(); })); - - const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; - const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; - - // First, check against the number of connected players. - if (num_connected_players < min_supported_players || - num_connected_players > max_supported_players) { - parameters_met = false; - ui->buttonBox->setEnabled(parameters_met); - return parameters_met; - } - - // Next, check against all connected controllers. - const auto all_controllers_compatible = [this] { - for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { - // Skip controllers that are not used, we only care about the currently connected ones. - if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { - continue; - } - - const auto compatible = IsControllerCompatible( - GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), - parameters); - - // If any controller is found to be incompatible, return false early. - if (!compatible) { - return false; - } - } - - // Reaching here means all currently connected controllers are compatible. - return true; - }(); - - parameters_met = all_controllers_compatible; - ui->buttonBox->setEnabled(parameters_met); - return parameters_met; -} - -void QtControllerSelectorDialog::SetSupportedControllers() { - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); - - if (parameters.enable_single_mode && parameters.allow_handheld) { - ui->controllerSupported1->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); - } else { - ui->controllerSupported1->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); - } - - if (parameters.allow_dual_joycons) { - ui->controllerSupported2->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); - } else { - ui->controllerSupported2->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); - } - - if (parameters.allow_left_joycon) { - ui->controllerSupported3->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); - } else { - ui->controllerSupported3->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); - } - - if (parameters.allow_right_joycon) { - ui->controllerSupported4->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); - } else { - ui->controllerSupported4->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); - } - - if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { - ui->controllerSupported5->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); - } else { - ui->controllerSupported5->setStyleSheet( - QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") - .arg(theme)); - } - - // enable_single_mode overrides min_players and max_players. - if (parameters.enable_single_mode) { - ui->numberSupportedLabel->setText(QStringLiteral("1")); - return; - } - - if (parameters.min_players == parameters.max_players) { - ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); - } else { - ui->numberSupportedLabel->setText( - QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); - } -} - -void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { - auto& pairs = index_controller_type_pairs[player_index]; - - pairs.clear(); - emulated_controllers[player_index]->clear(); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::ProController); - emulated_controllers[player_index]->addItem(tr("Pro Controller")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::DualJoyconDetached); - emulated_controllers[player_index]->addItem(tr("Dual Joycons")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::LeftJoycon); - emulated_controllers[player_index]->addItem(tr("Left Joycon")); - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::RightJoycon); - emulated_controllers[player_index]->addItem(tr("Right Joycon")); - - if (player_index == 0) { - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::Handheld); - emulated_controllers[player_index]->addItem(tr("Handheld")); - } - - pairs.emplace_back(emulated_controllers[player_index]->count(), - Settings::ControllerType::GameCube); - emulated_controllers[player_index]->addItem(tr("GameCube Controller")); -} - -Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( - int index, std::size_t player_index) const { - const auto& pairs = index_controller_type_pairs[player_index]; - - const auto it = std::find_if(pairs.begin(), pairs.end(), - [index](const auto& pair) { return pair.first == index; }); - - if (it == pairs.end()) { - return Settings::ControllerType::ProController; - } - - return it->second; -} - -int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, - std::size_t player_index) const { - const auto& pairs = index_controller_type_pairs[player_index]; - - const auto it = std::find_if(pairs.begin(), pairs.end(), - [type](const auto& pair) { return pair.second == type; }); - - if (it == pairs.end()) { - return 0; - } - - return it->first; -} - -void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { - if (!player_groupboxes[player_index]->isChecked()) { - connected_controller_icons[player_index]->setStyleSheet(QString{}); - player_labels[player_index]->show(); - return; - } - - const QString stylesheet = [this, player_index] { - switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), - player_index)) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: - return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); - case Settings::ControllerType::DualJoyconDetached: - return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); - case Settings::ControllerType::LeftJoycon: - return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); - case Settings::ControllerType::RightJoycon: - return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); - case Settings::ControllerType::Handheld: - return QStringLiteral("image: url(:/controller/applet_handheld%0); "); - default: - return QString{}; - } - }(); - - if (stylesheet.isEmpty()) { - connected_controller_icons[player_index]->setStyleSheet(QString{}); - player_labels[player_index]->show(); - return; - } - - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); - - connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); - player_labels[player_index]->hide(); -} - -void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { - auto& player = Settings::values.players.GetValue()[player_index]; - - const auto controller_type = GetControllerTypeFromIndex( - emulated_controllers[player_index]->currentIndex(), player_index); - const auto player_connected = player_groupboxes[player_index]->isChecked() && - controller_type != Settings::ControllerType::Handheld; - - if (player.controller_type == controller_type && player.connected == player_connected) { - // Set vibration devices in the event that the input device has changed. - ConfigureVibration::SetVibrationDevices(player_index); - return; - } - - // Disconnect the controller first. - UpdateController(controller_type, player_index, false); - - player.controller_type = controller_type; - player.connected = player_connected; - - ConfigureVibration::SetVibrationDevices(player_index); - - // Handheld - if (player_index == 0) { - auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; - if (controller_type == Settings::ControllerType::Handheld) { - handheld = player; - } - handheld.connected = player_groupboxes[player_index]->isChecked() && - controller_type == Settings::ControllerType::Handheld; - UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); - } - - if (!player.connected) { - return; - } - - // This emulates a delay between disconnecting and reconnecting controllers as some games - // do not respond to a change in controller type if it was instantaneous. - using namespace std::chrono_literals; - std::this_thread::sleep_for(60ms); - - UpdateController(controller_type, player_index, player_connected); -} - -void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { - if (!player_groupboxes[player_index]->isChecked() || - GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), - player_index) == Settings::ControllerType::Handheld) { - led_patterns_boxes[player_index][0]->setChecked(false); - led_patterns_boxes[player_index][1]->setChecked(false); - led_patterns_boxes[player_index][2]->setChecked(false); - led_patterns_boxes[player_index][3]->setChecked(false); - return; - } - - led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); - led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); - led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); - led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); -} - -void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { - if (!parameters.enable_border_color || - player_index >= static_cast(parameters.max_players) || - player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { - return; - } - - player_groupboxes[player_index]->setStyleSheet( - player_groupboxes[player_index]->styleSheet().append( - QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " - "{ border: 1px solid rgba(%2, %3, %4, %5); }") - .arg(player_index + 1) - .arg(parameters.border_colors[player_index][0]) - .arg(parameters.border_colors[player_index][1]) - .arg(parameters.border_colors[player_index][2]) - .arg(parameters.border_colors[player_index][3]))); -} - -void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { - if (!parameters.enable_explain_text || - player_index >= static_cast(parameters.max_players)) { - return; - } - - explain_text_labels[player_index]->setText(QString::fromStdString( - Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), - parameters.explain_text[player_index].size()))); -} - -void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { - // Disallow changing the console mode if the controller type is handheld. - ui->radioDocked->setEnabled(!is_handheld); - ui->radioUndocked->setEnabled(!is_handheld); - - ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); - ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); - - // Also force into undocked mode if the controller type is handheld. - if (is_handheld) { - ui->radioUndocked->setChecked(true); - } -} - -void QtControllerSelectorDialog::DisableUnsupportedPlayers() { - const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; - - switch (max_supported_players) { - case 0: - default: - UNREACHABLE(); - return; - case 1: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - ui->widgetSpacer3->hide(); - ui->widgetSpacer4->hide(); - break; - case 2: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - ui->widgetSpacer3->hide(); - break; - case 3: - ui->widgetSpacer->hide(); - ui->widgetSpacer2->hide(); - break; - case 4: - ui->widgetSpacer->hide(); - break; - case 5: - case 6: - case 7: - case 8: - break; - } - - for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { - // Disconnect any unsupported players here and disable or hide them if applicable. - Settings::values.players.GetValue()[index].connected = false; - UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false); - // Hide the player widgets when max_supported_controllers is less than or equal to 4. - if (max_supported_players <= 4) { - player_widgets[index]->hide(); - } - - // Disable and hide the following to prevent these from interaction. - player_widgets[index]->setDisabled(true); - connected_controller_checkboxes[index]->setDisabled(true); - connected_controller_labels[index]->hide(); - connected_controller_checkboxes[index]->hide(); - } -} - -QtControllerSelector::QtControllerSelector(GMainWindow& parent) { - connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, - &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, - &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); -} - -QtControllerSelector::~QtControllerSelector() = default; - -void QtControllerSelector::ReconfigureControllers( - std::function callback_, const Core::Frontend::ControllerParameters& parameters) const { - callback = std::move(callback_); - emit MainWindowReconfigureControllers(parameters); -} - -void QtControllerSelector::MainWindowReconfigureFinished() { - // Acquire the HLE mutex - std::lock_guard lock(HLE::g_hle_lock); - callback(); -} diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h deleted file mode 100644 index 9b57aea1a..000000000 --- a/src/yuzu/applets/controller.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "core/frontend/applets/controller.h" - -class GMainWindow; -class QCheckBox; -class QComboBox; -class QDialogButtonBox; -class QGroupBox; -class QLabel; - -class InputProfiles; - -namespace InputCommon { -class InputSubsystem; -} - -namespace Settings { -enum class ControllerType; -} - -namespace Ui { -class QtControllerSelectorDialog; -} - -class QtControllerSelectorDialog final : public QDialog { - Q_OBJECT - -public: - explicit QtControllerSelectorDialog(QWidget* parent, - Core::Frontend::ControllerParameters parameters_, - InputCommon::InputSubsystem* input_subsystem_); - ~QtControllerSelectorDialog() override; - - int exec() override; - -private: - // Applies the current configuration. - void ApplyConfiguration(); - - // Loads the current input configuration into the frontend applet. - void LoadConfiguration(); - - // Initializes the "Configure Vibration" Dialog. - void CallConfigureVibrationDialog(); - - // Initializes the "Configure Motion / Touch" Dialog. - void CallConfigureMotionTouchDialog(); - - // Initializes the "Create Input Profile" Dialog. - void CallConfigureInputProfileDialog(); - - // Checks the current configuration against the given parameters. - // This sets and returns the value of parameters_met. - bool CheckIfParametersMet(); - - // Sets the controller icons for "Supported Controller Types". - void SetSupportedControllers(); - - // Sets the emulated controllers per player. - void SetEmulatedControllers(std::size_t player_index); - - // Gets the Controller Type for a given controller combobox index per player. - Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; - - // Gets the controller combobox index for a given Controller Type per player. - int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; - - // Updates the controller icons per player. - void UpdateControllerIcon(std::size_t player_index); - - // Updates the controller state (type and connection status) per player. - void UpdateControllerState(std::size_t player_index); - - // Updates the LED pattern per player. - void UpdateLEDPattern(std::size_t player_index); - - // Updates the border color per player. - void UpdateBorderColor(std::size_t player_index); - - // Sets the "Explain Text" per player. - void SetExplainText(std::size_t player_index); - - // Updates the console mode. - void UpdateDockedState(bool is_handheld); - - // Disables and disconnects unsupported players based on the given parameters. - void DisableUnsupportedPlayers(); - - std::unique_ptr ui; - - // Parameters sent in from the backend HLE applet. - Core::Frontend::ControllerParameters parameters; - - InputCommon::InputSubsystem* input_subsystem; - - std::unique_ptr input_profiles; - - // This is true if and only if all parameters are met. Otherwise, this is false. - // This determines whether the "OK" button can be clicked to exit the applet. - bool parameters_met{false}; - - static constexpr std::size_t NUM_PLAYERS = 8; - - // Widgets encapsulating the groupboxes and comboboxes per player. - std::array player_widgets; - - // Groupboxes encapsulating the controller icons and LED patterns per player. - std::array player_groupboxes; - - // Icons for currently connected controllers/players. - std::array connected_controller_icons; - - // Labels that represent the player numbers in place of the controller icons. - std::array player_labels; - - // LED patterns for currently connected controllers/players. - std::array, NUM_PLAYERS> led_patterns_boxes; - - // Labels representing additional information known as "Explain Text" per player. - std::array explain_text_labels; - - // Comboboxes with a list of emulated controllers per player. - std::array emulated_controllers; - - /// Pairs of emulated controller index and Controller Type enum per player. - std::array>, NUM_PLAYERS> - index_controller_type_pairs; - - // Labels representing the number of connected controllers - // above the "Connected Controllers" checkboxes. - std::array connected_controller_labels; - - // Checkboxes representing the "Connected Controllers". - std::array connected_controller_checkboxes; -}; - -class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { - Q_OBJECT - -public: - explicit QtControllerSelector(GMainWindow& parent); - ~QtControllerSelector() override; - - void ReconfigureControllers( - std::function callback_, - const Core::Frontend::ControllerParameters& parameters) const override; - -signals: - void MainWindowReconfigureControllers( - const Core::Frontend::ControllerParameters& parameters) const; - -private: - void MainWindowReconfigureFinished(); - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui deleted file mode 100644 index c8cb6bcf3..000000000 --- a/src/yuzu/applets/controller.ui +++ /dev/null @@ -1,2653 +0,0 @@ - - - QtControllerSelectorDialog - - - - 0 - 0 - 839 - 630 - - - - Controller Applet - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 10 - - - 0 - - - 10 - - - 0 - - - 10 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 75 - true - - - - Supported Controller Types: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - - - - 70 - 70 - - - - - 70 - 70 - - - - - 0 - - - 0 - - - 16 - - - 14 - - - 16 - - - - - - 75 - true - - - - Players: - - - Qt::AlignCenter - - - false - - - - - - - - 14 - - - - 1 - 8 - - - Qt::AlignCenter - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 5 - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P4 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P2 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P1 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::LeftToRight - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - Handheld - - - - - - - - - Use Current Config - - - - - - - - - - - - 25 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 25 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P3 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P7 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P8 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P5 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::LeftToRight - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 100 - - - - - 100 - 100 - - - - - - - true - - - false - - - - 7 - - - 14 - - - 7 - - - 14 - - - 4 - - - - - - - - - 16 - - - - - P6 - - - - - - - - - - false - - - - 0 - 10 - - - - - 4 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - - - - - 150 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::AlignCenter - - - - - - - - - - - Pro Controller - - - - - Dual Joycons - - - - - Left Joycon - - - - - Right Joycon - - - - - - - - - Use Current Config - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 25 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 25 - 20 - - - - - - - - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 25 - - - - - - - - - - - - - - - - - 15 - - - 15 - - - 8 - - - 15 - - - 15 - - - - - - 16777215 - 16777215 - - - - Console Mode - - - - 6 - - - 8 - - - 6 - - - 3 - - - 6 - - - - - Docked - - - true - - - - - - - Undocked - - - - - - - - - - Vibration - - - true - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Configure - - - - - - - - - - Motion - - - true - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 0 - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Configure - - - - - - - - - - Profiles - - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 68 - 16777215 - - - - min-width: 68px; - - - Create - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 3 - - - - - - - - - - - - Controllers - - - - - - - - - - - - - - 1 - - - Qt::AlignCenter - - - - - - - - - - - - - - Qt::LeftToRight - - - false - - - - - - - 2 - - - Qt::AlignCenter - - - - - - - 4 - - - Qt::AlignCenter - - - - - - - 3 - - - Qt::AlignCenter - - - - - - - Connected - - - - - - - - - - - - - - 5 - - - Qt::AlignCenter - - - - - - - - - - - - - - 7 - - - Qt::AlignCenter - - - - - - - - - - - - - - 6 - - - Qt::AlignCenter - - - - - - - 8 - - - Qt::AlignCenter - - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - true - - - QDialogButtonBox::Ok - - - - - - - - - - - - - - - buttonBox - accepted() - QtControllerSelectorDialog - accept() - - - diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp deleted file mode 100644 index 085688cd4..000000000 --- a/src/yuzu/applets/error.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "core/hle/lock.h" -#include "yuzu/applets/error.h" -#include "yuzu/main.h" - -QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { - connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, - &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ErrorDisplayFinished, this, - &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); -} - -QtErrorDisplay::~QtErrorDisplay() = default; - -void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { - callback = std::move(finished); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error has occurred.\nPlease try again or contact the developer of the software.")); -} - -void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, - std::function finished) const { - callback = std::move(finished); - - const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " - "software.") - .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) - .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); -} - -void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, - std::string fullscreen_text, - std::function finished) const { - callback = std::move(finished); - emit MainWindowDisplayError( - tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')), - tr("An error has occurred.\n\n%1\n\n%2") - .arg(QString::fromStdString(dialog_text)) - .arg(QString::fromStdString(fullscreen_text))); -} - -void QtErrorDisplay::MainWindowFinishedError() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - callback(); -} diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h deleted file mode 100644 index 8bd895a32..000000000 --- a/src/yuzu/applets/error.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "core/frontend/applets/error.h" - -class GMainWindow; - -class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet { - Q_OBJECT - -public: - explicit QtErrorDisplay(GMainWindow& parent); - ~QtErrorDisplay() override; - - void ShowError(ResultCode error, std::function finished) const override; - void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, - std::function finished) const override; - void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, - std::function finished) const override; - -signals: - void MainWindowDisplayError(QString error_code, QString error_text) const; - -private: - void MainWindowFinishedError(); - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp deleted file mode 100644 index 62fd1141c..000000000 --- a/src/yuzu/applets/profile_select.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/fs/path_util.h" -#include "common/string_util.h" -#include "core/constants.h" -#include "core/hle/lock.h" -#include "yuzu/applets/profile_select.h" -#include "yuzu/main.h" - -namespace { -QString FormatUserEntryText(const QString& username, Common::UUID uuid) { - return QtProfileSelectionDialog::tr( - "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " - "00112233-4455-6677-8899-AABBCCDDEEFF))") - .arg(username, QString::fromStdString(uuid.FormatSwitch())); -} - -QString GetImagePath(Common::UUID uuid) { - const auto path = - Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / - fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); - return QString::fromStdString(Common::FS::PathToUTF8String(path)); -} - -QPixmap GetIcon(Common::UUID uuid) { - QPixmap icon{GetImagePath(uuid)}; - - if (!icon) { - icon.fill(Qt::black); - icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), - static_cast(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); - } - - return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); -} -} // Anonymous namespace - -QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) - : QDialog(parent), profile_manager(std::make_unique()) { - outer_layout = new QVBoxLayout; - - instruction_label = new QLabel(tr("Select a user:")); - - scroll_area = new QScrollArea; - - buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); - - outer_layout->addWidget(instruction_label); - outer_layout->addWidget(scroll_area); - outer_layout->addWidget(buttons); - - layout = new QVBoxLayout; - tree_view = new QTreeView; - item_model = new QStandardItemModel(tree_view); - tree_view->setModel(item_model); - - tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::SingleSelection); - tree_view->setSelectionBehavior(QHeaderView::SelectRows); - tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setSortingEnabled(true); - tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setUniformRowHeights(true); - tree_view->setIconSize({64, 64}); - tree_view->setContextMenuPolicy(Qt::NoContextMenu); - - item_model->insertColumns(0, 1); - item_model->setHeaderData(0, Qt::Horizontal, tr("Users")); - - // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrella of custom types. - qRegisterMetaType>("QList"); - - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addWidget(tree_view); - - scroll_area->setLayout(layout); - - connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); - - const auto& profiles = profile_manager->GetAllUsers(); - for (const auto& user : profiles) { - Service::Account::ProfileBase profile{}; - if (!profile_manager->GetProfileBase(user, profile)) - continue; - - const auto username = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(profile.username.data()), profile.username.size()); - - list_items.push_back(QList{new QStandardItem{ - GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); - } - - for (const auto& item : list_items) - item_model->appendRow(item); - - setLayout(outer_layout); - setWindowTitle(tr("Profile Selector")); - resize(550, 400); -} - -QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; - -int QtProfileSelectionDialog::exec() { - // Skip profile selection when there's only one. - if (profile_manager->GetUserCount() == 1) { - user_index = 0; - return QDialog::Accepted; - } - return QDialog::exec(); -} - -void QtProfileSelectionDialog::accept() { - QDialog::accept(); -} - -void QtProfileSelectionDialog::reject() { - user_index = 0; - QDialog::reject(); -} - -int QtProfileSelectionDialog::GetIndex() const { - return user_index; -} - -void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { - user_index = index.row(); -} - -QtProfileSelector::QtProfileSelector(GMainWindow& parent) { - connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, - &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); - connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, - &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); -} - -QtProfileSelector::~QtProfileSelector() = default; - -void QtProfileSelector::SelectProfile( - std::function)> callback_) const { - callback = std::move(callback_); - emit MainWindowSelectProfile(); -} - -void QtProfileSelector::MainWindowFinishedSelection(std::optional uuid) { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - callback(uuid); -} diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h deleted file mode 100644 index 4e9037488..000000000 --- a/src/yuzu/applets/profile_select.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "core/frontend/applets/profile_select.h" -#include "core/hle/service/acc/profile_manager.h" - -class GMainWindow; -class QDialogButtonBox; -class QGraphicsScene; -class QLabel; -class QScrollArea; -class QStandardItem; -class QStandardItemModel; -class QVBoxLayout; - -class QtProfileSelectionDialog final : public QDialog { - Q_OBJECT - -public: - explicit QtProfileSelectionDialog(QWidget* parent); - ~QtProfileSelectionDialog() override; - - int exec() override; - void accept() override; - void reject() override; - - int GetIndex() const; - -private: - void SelectUser(const QModelIndex& index); - - int user_index = 0; - - QVBoxLayout* layout; - QTreeView* tree_view; - QStandardItemModel* item_model; - QGraphicsScene* scene; - - std::vector> list_items; - - QVBoxLayout* outer_layout; - QLabel* instruction_label; - QScrollArea* scroll_area; - QDialogButtonBox* buttons; - - std::unique_ptr profile_manager; -}; - -class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { - Q_OBJECT - -public: - explicit QtProfileSelector(GMainWindow& parent); - ~QtProfileSelector() override; - - void SelectProfile(std::function)> callback_) const override; - -signals: - void MainWindowSelectProfile() const; - -private: - void MainWindowFinishedSelection(std::optional uuid); - - mutable std::function)> callback; -}; diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp new file mode 100644 index 000000000..97106d2cc --- /dev/null +++ b/src/yuzu/applets/qt_controller.cpp @@ -0,0 +1,695 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "ui_qt_controller.h" +#include "yuzu/applets/qt_controller.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/main.h" + +namespace { + +constexpr std::size_t HANDHELD_INDEX = 8; + +constexpr std::array, 8> led_patterns{{ + {true, false, false, false}, + {true, true, false, false}, + {true, true, true, false}, + {true, true, true, true}, + {true, false, false, true}, + {true, false, true, false}, + {true, false, true, true}, + {false, true, true, false}, +}}; + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, + bool connected) { + Core::System& system{Core::System::GetInstance()}; + + if (!system.IsPoweredOn()) { + return; + } + + Service::SM::ServiceManager& sm = system.ServiceManager(); + + auto& npad = + sm.GetService("hid") + ->GetAppletResource() + ->GetController(Service::HID::HidController::NPad); + + npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +// Returns true if the given controller type is compatible with the given parameters. +bool IsControllerCompatible(Settings::ControllerType controller_type, + Core::Frontend::ControllerParameters parameters) { + switch (controller_type) { + case Settings::ControllerType::ProController: + return parameters.allow_pro_controller; + case Settings::ControllerType::DualJoyconDetached: + return parameters.allow_dual_joycons; + case Settings::ControllerType::LeftJoycon: + return parameters.allow_left_joycon; + case Settings::ControllerType::RightJoycon: + return parameters.allow_right_joycon; + case Settings::ControllerType::Handheld: + return parameters.enable_single_mode && parameters.allow_handheld; + case Settings::ControllerType::GameCube: + return parameters.allow_gamecube_controller; + default: + return false; + } +} + +} // namespace + +QtControllerSelectorDialog::QtControllerSelectorDialog( + QWidget* parent, Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), ui(std::make_unique()), + parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, + input_profiles(std::make_unique()) { + ui->setupUi(this); + + player_widgets = { + ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, + ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, + }; + + player_groupboxes = { + ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, + ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, + ui->groupPlayer7Connected, ui->groupPlayer8Connected, + }; + + connected_controller_icons = { + ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, + ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, + }; + + led_patterns_boxes = {{ + {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, + ui->checkboxPlayer1LED4}, + {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, + ui->checkboxPlayer2LED4}, + {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, + ui->checkboxPlayer3LED4}, + {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, + ui->checkboxPlayer4LED4}, + {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, + ui->checkboxPlayer5LED4}, + {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, + ui->checkboxPlayer6LED4}, + {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, + ui->checkboxPlayer7LED4}, + {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, + ui->checkboxPlayer8LED4}, + }}; + + explain_text_labels = { + ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, + ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, + ui->labelPlayer7Explain, ui->labelPlayer8Explain, + }; + + emulated_controllers = { + ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, + ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, + ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, + }; + + player_labels = { + ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, + ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, + }; + + connected_controller_labels = { + ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, + ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, + ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, + }; + + connected_controller_checkboxes = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; + + // Setup/load everything prior to setting up connections. + // This avoids unintentionally changing the states of elements while loading them in. + SetSupportedControllers(); + DisableUnsupportedPlayers(); + + for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + SetEmulatedControllers(player_index); + } + + LoadConfiguration(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + SetExplainText(i); + UpdateControllerIcon(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { + if (checked) { + for (std::size_t index = 0; index <= i; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } else { + for (std::size_t index = i; index < NUM_PLAYERS; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } + }); + + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this, i](int) { + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + CheckIfParametersMet(); + }); + + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); + + if (i == 0) { + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this, i](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index, i) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->vibrationButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureVibrationDialog); + + connect(ui->motionButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureMotionTouchDialog); + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputProfileDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + // Enhancement: Check if the parameters have already been met before disconnecting controllers. + // If all the parameters are met AND only allows a single player, + // stop the constructor here as we do not need to continue. + if (CheckIfParametersMet() && parameters.enable_single_mode) { + return; + } + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +int QtControllerSelectorDialog::exec() { + if (parameters_met && parameters.enable_single_mode) { + return QDialog::Accepted; + } + return QDialog::exec(); +} + +void QtControllerSelectorDialog::ApplyConfiguration() { + const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); + Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); + + Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); + Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto connected = + Settings::values.players.GetValue()[index].connected || + (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + player_groupboxes[index]->setChecked(connected); + connected_controller_checkboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( + Settings::values.players.GetValue()[index].controller_type, index)); + } + + UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); + ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); +} + +void QtControllerSelectorDialog::CallConfigureVibrationDialog() { + ConfigureVibration dialog(this); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + +void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() { + ConfigureMotionTouch dialog(this, input_subsystem); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + +void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { + ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get()); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); +} + +bool QtControllerSelectorDialog::CheckIfParametersMet() { + // Here, we check and validate the current configuration against all applicable parameters. + const auto num_connected_players = static_cast( + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [this](const QGroupBox* player) { return player->isChecked(); })); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + // First, check against the number of connected players. + if (num_connected_players < min_supported_players || + num_connected_players > max_supported_players) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; + } + + // Next, check against all connected controllers. + const auto all_controllers_compatible = [this] { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + // Skip controllers that are not used, we only care about the currently connected ones. + if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { + continue; + } + + const auto compatible = IsControllerCompatible( + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), + parameters); + + // If any controller is found to be incompatible, return false early. + if (!compatible) { + return false; + } + } + + // Reaching here means all currently connected controllers are compatible. + return true; + }(); + + parameters_met = all_controllers_compatible; + ui->buttonBox->setEnabled(parameters_met); + return parameters_met; +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + if (parameters.enable_single_mode && parameters.allow_handheld) { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); + } else { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); + } + + if (parameters.allow_dual_joycons) { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); + } else { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); + } + + if (parameters.allow_left_joycon) { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); + } else { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); + } + + if (parameters.allow_right_joycon) { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); + } else { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); + } + + if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); + } else { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") + .arg(theme)); + } + + // enable_single_mode overrides min_players and max_players. + if (parameters.enable_single_mode) { + ui->numberSupportedLabel->setText(QStringLiteral("1")); + return; + } + + if (parameters.min_players == parameters.max_players) { + ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); + } else { + ui->numberSupportedLabel->setText( + QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); + } +} + +void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { + auto& pairs = index_controller_type_pairs[player_index]; + + pairs.clear(); + emulated_controllers[player_index]->clear(); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::ProController); + emulated_controllers[player_index]->addItem(tr("Pro Controller")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::DualJoyconDetached); + emulated_controllers[player_index]->addItem(tr("Dual Joycons")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::LeftJoycon); + emulated_controllers[player_index]->addItem(tr("Left Joycon")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::RightJoycon); + emulated_controllers[player_index]->addItem(tr("Right Joycon")); + + if (player_index == 0) { + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::Handheld); + emulated_controllers[player_index]->addItem(tr("Handheld")); + } + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::GameCube); + emulated_controllers[player_index]->addItem(tr("GameCube Controller")); +} + +Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( + int index, std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [index](const auto& pair) { return pair.first == index; }); + + if (it == pairs.end()) { + return Settings::ControllerType::ProController; + } + + return it->second; +} + +int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, + std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [type](const auto& pair) { return pair.second == type; }); + + if (it == pairs.end()) { + return 0; + } + + return it->first; +} + +void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString stylesheet = [this, player_index] { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index)) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/applet_handheld%0); "); + default: + return QString{}; + } + }(); + + if (stylesheet.isEmpty()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); + player_labels[player_index]->hide(); +} + +void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { + auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = GetControllerTypeFromIndex( + emulated_controllers[player_index]->currentIndex(), player_index); + const auto player_connected = player_groupboxes[player_index]->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); + + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + // Handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + if (controller_type == Settings::ControllerType::Handheld) { + handheld = player; + } + handheld.connected = player_groupboxes[player_index]->isChecked() && + controller_type == Settings::ControllerType::Handheld; + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); + } + + if (!player.connected) { + return; + } + + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + using namespace std::chrono_literals; + std::this_thread::sleep_for(60ms); + + UpdateController(controller_type, player_index, player_connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index) == Settings::ControllerType::Handheld) { + led_patterns_boxes[player_index][0]->setChecked(false); + led_patterns_boxes[player_index][1]->setChecked(false); + led_patterns_boxes[player_index][2]->setChecked(false); + led_patterns_boxes[player_index][3]->setChecked(false); + return; + } + + led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); + led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); + led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); + led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); +} + +void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { + if (!parameters.enable_border_color || + player_index >= static_cast(parameters.max_players) || + player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { + return; + } + + player_groupboxes[player_index]->setStyleSheet( + player_groupboxes[player_index]->styleSheet().append( + QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " + "{ border: 1px solid rgba(%2, %3, %4, %5); }") + .arg(player_index + 1) + .arg(parameters.border_colors[player_index][0]) + .arg(parameters.border_colors[player_index][1]) + .arg(parameters.border_colors[player_index][2]) + .arg(parameters.border_colors[player_index][3]))); +} + +void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { + if (!parameters.enable_explain_text || + player_index >= static_cast(parameters.max_players)) { + return; + } + + explain_text_labels[player_index]->setText(QString::fromStdString( + Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), + parameters.explain_text[player_index].size()))); +} + +void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { + // Disallow changing the console mode if the controller type is handheld. + ui->radioDocked->setEnabled(!is_handheld); + ui->radioUndocked->setEnabled(!is_handheld); + + ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); + + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); + } +} + +void QtControllerSelectorDialog::DisableUnsupportedPlayers() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + switch (max_supported_players) { + case 0: + default: + UNREACHABLE(); + return; + case 1: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + ui->widgetSpacer4->hide(); + break; + case 2: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + break; + case 3: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + break; + case 4: + ui->widgetSpacer->hide(); + break; + case 5: + case 6: + case 7: + case 8: + break; + } + + for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { + // Disconnect any unsupported players here and disable or hide them if applicable. + Settings::values.players.GetValue()[index].connected = false; + UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false); + // Hide the player widgets when max_supported_controllers is less than or equal to 4. + if (max_supported_players <= 4) { + player_widgets[index]->hide(); + } + + // Disable and hide the following to prevent these from interaction. + player_widgets[index]->setDisabled(true); + connected_controller_checkboxes[index]->setDisabled(true); + connected_controller_labels[index]->hide(); + connected_controller_checkboxes[index]->hide(); + } +} + +QtControllerSelector::QtControllerSelector(GMainWindow& parent) { + connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, + &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, + &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); +} + +QtControllerSelector::~QtControllerSelector() = default; + +void QtControllerSelector::ReconfigureControllers( + std::function callback_, const Core::Frontend::ControllerParameters& parameters) const { + callback = std::move(callback_); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard lock(HLE::g_hle_lock); + callback(); +} diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h new file mode 100644 index 000000000..9b57aea1a --- /dev/null +++ b/src/yuzu/applets/qt_controller.h @@ -0,0 +1,164 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "core/frontend/applets/controller.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Settings { +enum class ControllerType; +} + +namespace Ui { +class QtControllerSelectorDialog; +} + +class QtControllerSelectorDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtControllerSelectorDialog(QWidget* parent, + Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_); + ~QtControllerSelectorDialog() override; + + int exec() override; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + // Initializes the "Configure Vibration" Dialog. + void CallConfigureVibrationDialog(); + + // Initializes the "Configure Motion / Touch" Dialog. + void CallConfigureMotionTouchDialog(); + + // Initializes the "Create Input Profile" Dialog. + void CallConfigureInputProfileDialog(); + + // Checks the current configuration against the given parameters. + // This sets and returns the value of parameters_met. + bool CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // Sets the emulated controllers per player. + void SetEmulatedControllers(std::size_t player_index); + + // Gets the Controller Type for a given controller combobox index per player. + Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; + + // Gets the controller combobox index for a given Controller Type per player. + int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; + + // Updates the controller icons per player. + void UpdateControllerIcon(std::size_t player_index); + + // Updates the controller state (type and connection status) per player. + void UpdateControllerState(std::size_t player_index); + + // Updates the LED pattern per player. + void UpdateLEDPattern(std::size_t player_index); + + // Updates the border color per player. + void UpdateBorderColor(std::size_t player_index); + + // Sets the "Explain Text" per player. + void SetExplainText(std::size_t player_index); + + // Updates the console mode. + void UpdateDockedState(bool is_handheld); + + // Disables and disconnects unsupported players based on the given parameters. + void DisableUnsupportedPlayers(); + + std::unique_ptr ui; + + // Parameters sent in from the backend HLE applet. + Core::Frontend::ControllerParameters parameters; + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr input_profiles; + + // This is true if and only if all parameters are met. Otherwise, this is false. + // This determines whether the "OK" button can be clicked to exit the applet. + bool parameters_met{false}; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Widgets encapsulating the groupboxes and comboboxes per player. + std::array player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array player_groupboxes; + + // Icons for currently connected controllers/players. + std::array connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array player_labels; + + // LED patterns for currently connected controllers/players. + std::array, NUM_PLAYERS> led_patterns_boxes; + + // Labels representing additional information known as "Explain Text" per player. + std::array explain_text_labels; + + // Comboboxes with a list of emulated controllers per player. + std::array emulated_controllers; + + /// Pairs of emulated controller index and Controller Type enum per player. + std::array>, NUM_PLAYERS> + index_controller_type_pairs; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array connected_controller_checkboxes; +}; + +class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { + Q_OBJECT + +public: + explicit QtControllerSelector(GMainWindow& parent); + ~QtControllerSelector() override; + + void ReconfigureControllers( + std::function callback_, + const Core::Frontend::ControllerParameters& parameters) const override; + +signals: + void MainWindowReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui new file mode 100644 index 000000000..c8cb6bcf3 --- /dev/null +++ b/src/yuzu/applets/qt_controller.ui @@ -0,0 +1,2653 @@ + + + QtControllerSelectorDialog + + + + 0 + 0 + 839 + 630 + + + + Controller Applet + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + 0 + + + 10 + + + 0 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 75 + true + + + + Supported Controller Types: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 16 + + + 14 + + + 16 + + + + + + 75 + true + + + + Players: + + + Qt::AlignCenter + + + false + + + + + + + + 14 + + + + 1 - 8 + + + Qt::AlignCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P4 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P2 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P1 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + Handheld + + + + + + + + + Use Current Config + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P3 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P7 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P8 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P5 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P6 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + + + + + + 15 + + + 15 + + + 8 + + + 15 + + + 15 + + + + + + 16777215 + 16777215 + + + + Console Mode + + + + 6 + + + 8 + + + 6 + + + 3 + + + 6 + + + + + Docked + + + true + + + + + + + Undocked + + + + + + + + + + Vibration + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Configure + + + + + + + + + + Motion + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Configure + + + + + + + + + + Profiles + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Create + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 3 + + + + + + + + + + + + Controllers + + + + + + + + + + + + + + 1 + + + Qt::AlignCenter + + + + + + + + + + + + + + Qt::LeftToRight + + + false + + + + + + + 2 + + + Qt::AlignCenter + + + + + + + 4 + + + Qt::AlignCenter + + + + + + + 3 + + + Qt::AlignCenter + + + + + + + Connected + + + + + + + + + + + + + + 5 + + + Qt::AlignCenter + + + + + + + + + + + + + + 7 + + + Qt::AlignCenter + + + + + + + + + + + + + + 6 + + + Qt::AlignCenter + + + + + + + 8 + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + true + + + QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + QtControllerSelectorDialog + accept() + + + diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp new file mode 100644 index 000000000..45cf64603 --- /dev/null +++ b/src/yuzu/applets/qt_error.cpp @@ -0,0 +1,63 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "core/hle/lock.h" +#include "yuzu/applets/qt_error.h" +#include "yuzu/main.h" + +QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { + connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, + &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ErrorDisplayFinished, this, + &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); +} + +QtErrorDisplay::~QtErrorDisplay() = default; + +void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { + callback = std::move(finished); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\nPlease try again or contact the developer of the software.")); +} + +void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, + std::function finished) const { + callback = std::move(finished); + + const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " + "software.") + .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) + .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); +} + +void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, + std::string fullscreen_text, + std::function finished) const { + callback = std::move(finished); + emit MainWindowDisplayError( + tr("Error Code: %1-%2 (0x%3)") + .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\n\n%1\n\n%2") + .arg(QString::fromStdString(dialog_text)) + .arg(QString::fromStdString(fullscreen_text))); +} + +void QtErrorDisplay::MainWindowFinishedError() { + // Acquire the HLE mutex + std::lock_guard lock{HLE::g_hle_lock}; + callback(); +} diff --git a/src/yuzu/applets/qt_error.h b/src/yuzu/applets/qt_error.h new file mode 100644 index 000000000..8bd895a32 --- /dev/null +++ b/src/yuzu/applets/qt_error.h @@ -0,0 +1,33 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/frontend/applets/error.h" + +class GMainWindow; + +class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet { + Q_OBJECT + +public: + explicit QtErrorDisplay(GMainWindow& parent); + ~QtErrorDisplay() override; + + void ShowError(ResultCode error, std::function finished) const override; + void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, + std::function finished) const override; + void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, + std::function finished) const override; + +signals: + void MainWindowDisplayError(QString error_code, QString error_text) const; + +private: + void MainWindowFinishedError(); + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp new file mode 100644 index 000000000..a56638e21 --- /dev/null +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -0,0 +1,163 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/fs/path_util.h" +#include "common/string_util.h" +#include "core/constants.h" +#include "core/hle/lock.h" +#include "yuzu/applets/qt_profile_select.h" +#include "yuzu/main.h" + +namespace { +QString FormatUserEntryText(const QString& username, Common::UUID uuid) { + return QtProfileSelectionDialog::tr( + "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " + "00112233-4455-6677-8899-AABBCCDDEEFF))") + .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QString GetImagePath(Common::UUID uuid) { + const auto path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); + return QString::fromStdString(Common::FS::PathToUTF8String(path)); +} + +QPixmap GetIcon(Common::UUID uuid) { + QPixmap icon{GetImagePath(uuid)}; + + if (!icon) { + icon.fill(Qt::black); + icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), + static_cast(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); + } + + return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} +} // Anonymous namespace + +QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) + : QDialog(parent), profile_manager(std::make_unique()) { + outer_layout = new QVBoxLayout; + + instruction_label = new QLabel(tr("Select a user:")); + + scroll_area = new QScrollArea; + + buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); + + outer_layout->addWidget(instruction_label); + outer_layout->addWidget(scroll_area); + outer_layout->addWidget(buttons); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setIconSize({64, 64}); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 1); + item_model->setHeaderData(0, Qt::Horizontal, tr("Users")); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrella of custom types. + qRegisterMetaType>("QList"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + scroll_area->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + + const auto& profiles = profile_manager->GetAllUsers(); + for (const auto& user : profiles) { + Service::Account::ProfileBase profile{}; + if (!profile_manager->GetProfileBase(user, profile)) + continue; + + const auto username = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast(profile.username.data()), profile.username.size()); + + list_items.push_back(QList{new QStandardItem{ + GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); + } + + for (const auto& item : list_items) + item_model->appendRow(item); + + setLayout(outer_layout); + setWindowTitle(tr("Profile Selector")); + resize(550, 400); +} + +QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; + +int QtProfileSelectionDialog::exec() { + // Skip profile selection when there's only one. + if (profile_manager->GetUserCount() == 1) { + user_index = 0; + return QDialog::Accepted; + } + return QDialog::exec(); +} + +void QtProfileSelectionDialog::accept() { + QDialog::accept(); +} + +void QtProfileSelectionDialog::reject() { + user_index = 0; + QDialog::reject(); +} + +int QtProfileSelectionDialog::GetIndex() const { + return user_index; +} + +void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { + user_index = index.row(); +} + +QtProfileSelector::QtProfileSelector(GMainWindow& parent) { + connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, + &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, + &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); +} + +QtProfileSelector::~QtProfileSelector() = default; + +void QtProfileSelector::SelectProfile( + std::function)> callback_) const { + callback = std::move(callback_); + emit MainWindowSelectProfile(); +} + +void QtProfileSelector::MainWindowFinishedSelection(std::optional uuid) { + // Acquire the HLE mutex + std::lock_guard lock{HLE::g_hle_lock}; + callback(uuid); +} diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h new file mode 100644 index 000000000..4e9037488 --- /dev/null +++ b/src/yuzu/applets/qt_profile_select.h @@ -0,0 +1,72 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "core/frontend/applets/profile_select.h" +#include "core/hle/service/acc/profile_manager.h" + +class GMainWindow; +class QDialogButtonBox; +class QGraphicsScene; +class QLabel; +class QScrollArea; +class QStandardItem; +class QStandardItemModel; +class QVBoxLayout; + +class QtProfileSelectionDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtProfileSelectionDialog(QWidget* parent); + ~QtProfileSelectionDialog() override; + + int exec() override; + void accept() override; + void reject() override; + + int GetIndex() const; + +private: + void SelectUser(const QModelIndex& index); + + int user_index = 0; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector> list_items; + + QVBoxLayout* outer_layout; + QLabel* instruction_label; + QScrollArea* scroll_area; + QDialogButtonBox* buttons; + + std::unique_ptr profile_manager; +}; + +class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { + Q_OBJECT + +public: + explicit QtProfileSelector(GMainWindow& parent); + ~QtProfileSelector() override; + + void SelectProfile(std::function)> callback_) const override; + +signals: + void MainWindowSelectProfile() const; + +private: + void MainWindowFinishedSelection(std::optional uuid); + + mutable std::function)> callback; +}; diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp new file mode 100644 index 000000000..848801cec --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -0,0 +1,1620 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_qt_software_keyboard.h" +#include "yuzu/applets/qt_software_keyboard.h" +#include "yuzu/main.h" +#include "yuzu/util/overlay_dialog.h" + +namespace { + +using namespace Service::AM::Applets; + +constexpr float BASE_HEADER_FONT_SIZE = 23.0f; +constexpr float BASE_SUB_FONT_SIZE = 17.0f; +constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; +constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; +constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; +constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; +[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; + +} // Anonymous namespace + +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_) + : QDialog(parent), ui{std::make_unique()}, system{system_}, + is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); + + keyboard_buttons = {{ + {{ + { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + }, + { + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + }, + { + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_return, + }, + { + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + }, + { + ui->button_shift, + ui->button_shift, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_ok, + }, + }}, + {{ + { + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + }, + { + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + }, + { + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_return_shift, + }, + { + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + }, + { + ui->button_shift_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_ok_shift, + }, + }}, + }}; + + numberpad_buttons = {{ + { + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + }, + { + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + }, + { + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_ok_num, + }, + { + nullptr, + ui->button_0_num, + nullptr, + ui->button_ok_num, + }, + }}; + + all_buttons = { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + ui->button_shift, + ui->button_space, + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_0_num, + }; + + SetupMouseHover(); + + if (!initialize_parameters.ok_text.empty()) { + ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); + } + + ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); + ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + + current_text = initialize_parameters.initial_text; + cursor_position = initialize_parameters.initial_cursor_position; + + SetTextDrawType(); + + for (auto* button : all_buttons) { + connect(button, &QPushButton::clicked, this, [this, button](bool) { + if (is_inline) { + InlineKeyboardButtonClicked(button); + } else { + NormalKeyboardButtonClicked(button); + } + }); + } + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + } +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { + StopInputThread(); +} + +void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { + if (isVisible()) { + return; + } + + MoveAndResizeWindow(pos, size); + + SetKeyboardType(); + SetPasswordMode(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + case SwkbdTextCheckResult::Silent: + default: + break; + case SwkbdTextCheckResult::Failure: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + QString{}, tr("OK"), Qt::AlignCenter); + dialog.exec(); + + StartInputThread(); + break; + } + case SwkbdTextCheckResult::Confirm: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + tr("Cancel"), tr("OK"), Qt::AlignCenter); + if (dialog.exec() != QDialog::Accepted) { + StartInputThread(); + break; + } + + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { + MoveAndResizeWindow(pos, size); + + ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); + + ui->headerOSK->hide(); + ui->subOSK->hide(); + ui->inputOSK->hide(); + ui->charactersOSK->hide(); + ui->inputBoxOSK->hide(); + ui->charactersBoxOSK->hide(); + + initialize_parameters.max_text_length = appear_parameters.max_text_length; + initialize_parameters.min_text_length = appear_parameters.min_text_length; + initialize_parameters.type = appear_parameters.type; + initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; + initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; + initialize_parameters.enable_return_button = appear_parameters.enable_return_button; + initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + + SetKeyboardType(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::HideInlineKeyboard() { + StopInputThread(); + QDialog::hide(); +} + +void QtSoftwareKeyboardDialog::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + current_text = text_parameters.input_text; + cursor_position = text_parameters.cursor_position; + + SetBackspaceOkEnabled(); +} + +void QtSoftwareKeyboardDialog::ExitKeyboard() { + StopInputThread(); + QDialog::done(QDialog::Accepted); +} + +void QtSoftwareKeyboardDialog::open() { + QDialog::open(); + + row = 0; + column = 0; + + const auto* const curr_button = + keyboard_buttons[static_cast(bottom_osk_index)][row][column]; + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + + StartInputThread(); +} + +void QtSoftwareKeyboardDialog::reject() { + // Pressing the ESC key in a dialog calls QDialog::reject(). + // We will override this behavior to the "Cancel" action on the software keyboard. + TranslateButtonPress(HIDButton::X); +} + +void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { + if (!is_inline) { + QDialog::keyPressEvent(event); + return; + } + + const auto entered_key = event->key(); + + switch (entered_key) { + case Qt::Key_Escape: + QDialog::keyPressEvent(event); + return; + case Qt::Key_Backspace: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Return: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Left: + MoveTextCursorDirection(Direction::Left); + return; + case Qt::Key_Right: + MoveTextCursorDirection(Direction::Right); + return; + default: + break; + } + + const auto entered_text = event->text(); + + if (entered_text.isEmpty()) { + return; + } + + InlineTextInsertString(entered_text.toStdU16String()); +} + +void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { + QDialog::move(pos); + QDialog::resize(size); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + RescaleKeyboardElements(size.width(), size.height(), dpi_scale); +} + +void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { + const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto char_button_font_size = + BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto label_button_font_size = + BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); + QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); + QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); + QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); + QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, + QFont::Normal); + + ui->label_header->setFont(header_font); + ui->label_sub->setFont(sub_font); + ui->line_edit_osk->setFont(editor_font); + ui->text_edit_osk->setFont(editor_font); + ui->label_characters->setFont(sub_font); + ui->label_characters_box->setFont(sub_font); + + ui->label_shift->setFont(label_button_font); + ui->label_shift_shift->setFont(label_button_font); + ui->label_cancel->setFont(label_button_font); + ui->label_cancel_shift->setFont(label_button_font); + ui->label_cancel_num->setFont(label_button_font); + ui->label_enter->setFont(label_button_font); + ui->label_enter_shift->setFont(label_button_font); + ui->label_enter_num->setFont(label_button_font); + + for (auto* button : all_buttons) { + if (button == ui->button_return || button == ui->button_return_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || + button == ui->button_ok_num) { + button->setFont(label_button_font); + continue; + } + + button->setFont(char_button_font); + } +} + +void QtSoftwareKeyboardDialog::SetKeyboardType() { + switch (initialize_parameters.type) { + case SwkbdType::Normal: + case SwkbdType::Qwerty: + case SwkbdType::Unknown3: + case SwkbdType::Latin: + case SwkbdType::SimplifiedChinese: + case SwkbdType::TraditionalChinese: + case SwkbdType::Korean: + default: { + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 320); + ui->verticalLayout_2->setStretch(1, 400); + + ui->gridLineOSK->setRowStretch(5, 94); + ui->gridBoxOSK->setRowStretch(2, 81); + break; + } + case SwkbdType::NumberPad: { + bottom_osk_index = BottomOSKIndex::NumberPad; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 370); + ui->verticalLayout_2->setStretch(1, 350); + + ui->gridLineOSK->setRowStretch(5, 144); + ui->gridBoxOSK->setRowStretch(2, 131); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetPasswordMode() { + switch (initialize_parameters.password_mode) { + case SwkbdPasswordMode::Disabled: + default: + ui->line_edit_osk->setEchoMode(QLineEdit::Normal); + break; + case SwkbdPasswordMode::Enabled: + ui->line_edit_osk->setEchoMode(QLineEdit::Password); + break; + } +} + +void QtSoftwareKeyboardDialog::SetTextDrawType() { + switch (initialize_parameters.text_draw_type) { + case SwkbdTextDrawType::Line: + case SwkbdTextDrawType::DownloadCode: { + ui->topOSK->setCurrentIndex(0); + + if (initialize_parameters.max_text_length <= 10) { + ui->gridLineOSK->setColumnStretch(0, 390); + ui->gridLineOSK->setColumnStretch(1, 500); + ui->gridLineOSK->setColumnStretch(2, 390); + } else { + ui->gridLineOSK->setColumnStretch(0, 130); + ui->gridLineOSK->setColumnStretch(1, 1020); + ui->gridLineOSK->setColumnStretch(2, 130); + } + + if (is_inline) { + return; + } + + connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { + const auto is_valid = ValidateInputText(changed_string); + + const auto text_length = static_cast(changed_string.length()); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, + [this](int old_cursor_position, int new_cursor_position) { + ui->button_backspace->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_shift->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_num->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + + ui->line_edit_osk->setFocus(); + }); + + connect( + ui->line_edit_osk, &QLineEdit::returnPressed, this, + [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); + + ui->line_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); + ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + case SwkbdTextDrawType::Box: + default: { + ui->topOSK->setCurrentIndex(1); + + if (is_inline) { + return; + } + + connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { + if (static_cast(ui->text_edit_osk->toPlainText().length()) > + initialize_parameters.max_text_length) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } + + const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); + + const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->text_edit_osk->setFocus(); + }); + + connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { + const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + + ui->text_edit_osk->setFocus(); + }); + + ui->text_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 + ? QTextCursor::Start + : QTextCursor::End); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetControllerImage() { + const auto controller_type = Settings::values.players.GetValue()[8].connected + ? Settings::values.players.GetValue()[8].controller_type + : Settings::values.players.GetValue()[0].controller_type; + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_dark"); + } else { + return QString{}; + } + }(); + + switch (controller_type) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + break; + case Settings::ControllerType::DualJoyconDetached: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + break; + case Settings::ControllerType::LeftJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::RightJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::Handheld: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + default: { + for (const auto& keys : keyboard_buttons) { + for (const auto& rows : keys) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + } + + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + ui->button_space->setDisabled(key_disable_flags.space); + ui->button_space_shift->setDisabled(key_disable_flags.space); + + ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); + + ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); + + ui->button_slash->setDisabled(key_disable_flags.slash); + + ui->button_1->setDisabled(key_disable_flags.numbers); + ui->button_2->setDisabled(key_disable_flags.numbers); + ui->button_3->setDisabled(key_disable_flags.numbers); + ui->button_4->setDisabled(key_disable_flags.numbers); + ui->button_5->setDisabled(key_disable_flags.numbers); + ui->button_6->setDisabled(key_disable_flags.numbers); + ui->button_7->setDisabled(key_disable_flags.numbers); + ui->button_8->setDisabled(key_disable_flags.numbers); + ui->button_9->setDisabled(key_disable_flags.numbers); + ui->button_0->setDisabled(key_disable_flags.numbers); + + ui->button_return->setEnabled(initialize_parameters.enable_return_button); + ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); + break; + } + case BottomOSKIndex::NumberPad: { + for (const auto& rows : numberpad_buttons) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { + if (is_inline) { + ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(current_text.size() >= + initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + } else { + const auto text_length = [this] { + if (ui->topOSK->currentIndex() == 1) { + return static_cast(ui->text_edit_osk->toPlainText().length()); + } else { + return static_cast(ui->line_edit_osk->text().length()); + } + }(); + + const auto normal_cursor_position = [this] { + if (ui->topOSK->currentIndex() == 1) { + return ui->text_edit_osk->textCursor().position(); + } else { + return ui->line_edit_osk->cursorPosition(); + } + }(); + + ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + } +} + +bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + const auto input_text_length = static_cast(input_text.length()); + + if (input_text_length < initialize_parameters.min_text_length || + input_text_length > initialize_parameters.max_text_length) { + return false; + } + + if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { + return false; + } + + if ((key_disable_flags.at || key_disable_flags.username) && + input_text.contains(QLatin1Char{'@'})) { + return false; + } + + if ((key_disable_flags.percent || key_disable_flags.username) && + input_text.contains(QLatin1Char{'%'})) { + return false; + } + + if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { + return false; + } + + if ((key_disable_flags.backslash || key_disable_flags.username) && + input_text.contains(QLatin1Char('\\'))) { + return false; + } + + if (key_disable_flags.numbers && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { + return false; + } + + if (bottom_osk_index == BottomOSKIndex::NumberPad && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + return false; + } + + return true; +} + +void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + bottom_osk_index = BottomOSKIndex::UpperCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + break; + case BottomOSKIndex::UpperCase: + if (caps_lock_enabled) { + caps_lock_enabled = false; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); + + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + } else { + caps_lock_enabled = true; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);" + "\nimage-position: left;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); + } + break; + case BottomOSKIndex::NumberPad: + default: + break; + } +} + +void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { + if (button == ui->button_ampersand) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("&")); + } else { + ui->line_edit_osk->insert(QStringLiteral("&")); + } + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); + } else { + ui->line_edit_osk->insert(QStringLiteral("\n")); + } + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); + } else { + ui->line_edit_osk->insert(QStringLiteral(" ")); + } + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (ui->topOSK->currentIndex() == 1) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } else { + ui->line_edit_osk->backspace(); + } + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); + return; + } + + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(button->text()); + } else { + ui->line_edit_osk->insert(button->text()); + } + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { + if (!button->isEnabled()) { + return; + } + + if (button == ui->button_ampersand) { + InlineTextInsertString(u"&"); + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + InlineTextInsertString(u"\n"); + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + InlineTextInsertString(u" "); + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (cursor_position <= 0 || current_text.empty()) { + cursor_position = 0; + return; + } + + --cursor_position; + + current_text.erase(cursor_position, 1); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); + return; + } + + InlineTextInsertString(button->text().toStdU16String()); + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { + if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { + return; + } + + current_text.insert(cursor_position, string); + + cursor_position += static_cast(string.size()); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); +} + +void QtSoftwareKeyboardDialog::SetupMouseHover() { + // setFocus() has a bug where continuously changing focus will cause the focus UI to + // mysteriously disappear. A workaround we have found is using the mouse to hover over + // the buttons to act in place of the button focus. As a result, we will have to set + // a blank cursor when hovering over all the buttons and set a no focus policy so the + // buttons do not stay in focus in addition to the mouse hover. + for (auto* button : all_buttons) { + button->setCursor(QCursor(Qt::BlankCursor)); + button->setFocusPolicy(Qt::NoFocus); + } +} + +template +void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +template +void QtSoftwareKeyboardDialog::HandleButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { + switch (button) { + case HIDButton::A: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); + break; + case BottomOSKIndex::NumberPad: + numberpad_buttons[row][column]->click(); + break; + default: + break; + } + break; + case HIDButton::B: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + break; + case HIDButton::X: + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); + } + break; + case HIDButton::Y: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_space->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_space_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::LStick: + case HIDButton::RStick: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_shift->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_shift_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::L: + MoveTextCursorDirection(Direction::Left); + break; + case HIDButton::R: + MoveTextCursorDirection(Direction::Right); + break; + case HIDButton::Plus: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + case HIDButton::RStickLeft: + MoveButtonDirection(Direction::Left); + break; + case HIDButton::DUp: + case HIDButton::LStickUp: + case HIDButton::RStickUp: + MoveButtonDirection(Direction::Up); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + case HIDButton::RStickRight: + MoveButtonDirection(Direction::Right); + break; + case HIDButton::DDown: + case HIDButton::LStickDown: + case HIDButton::RStickDown: + MoveButtonDirection(Direction::Down); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { + // Changes the row or column index depending on the direction. + auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { + switch (direction) { + case Direction::Left: + column = (column + max_columns - 1) % max_columns; + break; + case Direction::Up: + row = (row + max_rows - 1) % max_rows; + break; + case Direction::Right: + column = (column + 1) % max_columns; + break; + case Direction::Down: + row = (row + 1) % max_rows; + break; + default: + break; + } + }; + + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: { + const auto index = static_cast(bottom_osk_index); + + const auto* const prev_button = keyboard_buttons[index][row][column]; + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + auto* curr_button = keyboard_buttons[index][row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + curr_button = keyboard_buttons[index][row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + case BottomOSKIndex::NumberPad: { + const auto* const prev_button = numberpad_buttons[row][column]; + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + auto* curr_button = numberpad_buttons[row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + curr_button = numberpad_buttons[row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { + switch (direction) { + case Direction::Left: + if (is_inline) { + if (cursor_position <= 0) { + cursor_position = 0; + } else { + --cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Left); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); + } + } + break; + case Direction::Right: + if (is_inline) { + if (cursor_position >= static_cast(current_text.size())) { + cursor_position = static_cast(current_text.size()); + } else { + ++cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Right); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); + } + } + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); +} + +void QtSoftwareKeyboardDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } + + if (input_interpreter) { + input_interpreter->ResetButtonStates(); + } +} + +void QtSoftwareKeyboardDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + HandleButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, + &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, + &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, + &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); +} + +QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + submit_inline_callback = std::move(submit_inline_callback_); + } else { + submit_normal_callback = std::move(submit_normal_callback_); + } + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(initialize_parameters.ok_text), + Common::UTF16ToUTF8(initialize_parameters.header_text), + Common::UTF16ToUTF8(initialize_parameters.sub_text), + Common::UTF16ToUTF8(initialize_parameters.guide_text), + Common::UTF16ToUTF8(initialize_parameters.initial_text), + initialize_parameters.max_text_length, initialize_parameters.min_text_length, + initialize_parameters.initial_cursor_position, initialize_parameters.type, + initialize_parameters.password_mode, initialize_parameters.text_draw_type, + initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, + initialize_parameters.enable_backspace_button, + initialize_parameters.enable_return_button, + initialize_parameters.disable_cancel_button); + + emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); +} + +void QtSoftwareKeyboard::ShowNormalKeyboard() const { + emit MainWindowShowNormalKeyboard(); +} + +void QtSoftwareKeyboard::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message)); +} + +void QtSoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); +} + +void QtSoftwareKeyboard::HideInlineKeyboard() const { + emit MainWindowHideInlineKeyboard(); +} + +void QtSoftwareKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + emit MainWindowInlineTextChanged(std::move(text_parameters)); +} + +void QtSoftwareKeyboard::ExitKeyboard() const { + emit MainWindowExitKeyboard(); +} + +void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const { + submit_normal_callback(result, submitted_text); +} + +void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, + s32 cursor_position) const { + submit_inline_callback(reply_type, submitted_text, cursor_position); +} diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h new file mode 100644 index 000000000..1a03c098c --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.h @@ -0,0 +1,285 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "core/frontend/applets/software_keyboard.h" + +enum class HIDButton : u8; + +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class QtSoftwareKeyboardDialog; +} + +class GMainWindow; + +class QtSoftwareKeyboardDialog final : public QDialog { + Q_OBJECT + +public: + QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_); + ~QtSoftwareKeyboardDialog() override; + + void ShowNormalKeyboard(QPoint pos, QSize size); + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, + QSize size); + + void HideInlineKeyboard(); + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + + void ExitKeyboard(); + +signals: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + +public slots: + void open() override; + void reject() override; + +protected: + /// We override the keyPressEvent for inputting text into the inline software keyboard. + void keyPressEvent(QKeyEvent* event) override; + +private: + enum class Direction { + Left, + Up, + Right, + Down, + }; + + enum class BottomOSKIndex { + LowerCase, + UpperCase, + NumberPad, + }; + + /** + * Moves and resizes the window to a specified position and size. + * + * @param pos Top-left window position + * @param size Window size + */ + void MoveAndResizeWindow(QPoint pos, QSize size); + + /** + * Rescales all keyboard elements to account for High DPI displays. + * + * @param width Window width + * @param height Window height + * @param dpi_scale Display scaling factor + */ + void RescaleKeyboardElements(float width, float height, float dpi_scale); + + /// Sets the keyboard type based on initialize_parameters. + void SetKeyboardType(); + + /// Sets the password mode based on initialize_parameters. + void SetPasswordMode(); + + /// Sets the text draw type based on initialize_parameters. + void SetTextDrawType(); + + /// Sets the controller image at the bottom left of the software keyboard. + void SetControllerImage(); + + /// Disables buttons based on initialize_parameters. + void DisableKeyboardButtons(); + + /// Changes whether the backspace or/and ok buttons should be enabled or disabled. + void SetBackspaceOkEnabled(); + + /** + * Validates the input text sent in based on the parameters in initialize_parameters. + * + * @param input_text Input text + * + * @returns True if the input text is valid, false otherwise. + */ + bool ValidateInputText(const QString& input_text); + + /// Switches between LowerCase and UpperCase (Shift and Caps Lock) + void ChangeBottomOSKIndex(); + + /// Processes a keyboard button click from the UI as normal keyboard input. + void NormalKeyboardButtonClicked(QPushButton* button); + + /// Processes a keyboard button click from the UI as inline keyboard input. + void InlineKeyboardButtonClicked(QPushButton* button); + + /** + * Inserts a string of arbitrary length into the current_text at the current cursor position. + * This is only used for the inline software keyboard. + */ + void InlineTextInsertString(std::u16string_view string); + + /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. + void SetupMouseHover(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonHold(); + + /** + * Translates a button press to focus or click a keyboard button. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + /** + * Moves the focus of a button in a certain direction. + * + * @param direction The direction to move. + */ + void MoveButtonDirection(Direction direction); + + /** + * Moves the text cursor in a certain direction. + * + * @param direction The direction to move. + */ + void MoveTextCursorDirection(Direction direction); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + Core::System& system; + + // True if it is the inline software keyboard. + bool is_inline; + + // Common software keyboard initialize parameters. + Core::Frontend::KeyboardInitializeParameters initialize_parameters; + + // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. + std::u16string current_text; + s32 cursor_position{0}; + + static constexpr std::size_t NUM_ROWS_NORMAL = 5; + static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; + static constexpr std::size_t NUM_ROWS_NUMPAD = 4; + static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; + + // Stores the normal keyboard layout. + std::array, NUM_ROWS_NORMAL>, 2> + keyboard_buttons; + // Stores the numberpad keyboard layout. + std::array, NUM_ROWS_NUMPAD> numberpad_buttons; + + // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. + std::array all_buttons; + + std::size_t row{0}; + std::size_t column{0}; + + BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; + std::atomic caps_lock_enabled{false}; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; +}; + +class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { + Q_OBJECT + +public: + explicit QtSoftwareKeyboard(GMainWindow& parent); + ~QtSoftwareKeyboard() override; + + void InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +signals: + void MainWindowInitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; + + void MainWindowShowNormalKeyboard() const; + + void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const; + + void MainWindowShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const; + + void MainWindowHideInlineKeyboard() const; + + void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; + + void MainWindowExitKeyboard() const; + +private: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; +}; diff --git a/src/yuzu/applets/qt_software_keyboard.ui b/src/yuzu/applets/qt_software_keyboard.ui new file mode 100644 index 000000000..b0a1fcde9 --- /dev/null +++ b/src/yuzu/applets/qt_software_keyboard.ui @@ -0,0 +1,3503 @@ + + + QtSoftwareKeyboardDialog + + + + 0 + 0 + 1280 + 720 + + + + Software Keyboard + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 17 + + + + 0/32 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 26 + 50 + false + + + + Qt::StrongFocus + + + + + + 32 + + + Enter Text + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 23 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 17 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 17 + + + + 0/500 + + + + + + + + + + + 0 + + + 14 + + + 9 + + + 14 + + + 9 + + + + + + 26 + + + + Qt::StrongFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Shift + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + - + + + + + + + + 1 + 1 + + + + + 28 + + + + ' + + + + + + + + 1 + 1 + + + + + 28 + + + + / + + + + + + + + 1 + 1 + + + + + 28 + + + + ! + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + w + + + + + + + + 1 + 1 + + + + + 28 + + + + r + + + + + + + + 1 + 1 + + + + + 28 + + + + e + + + + + + + + 1 + 1 + + + + + 28 + + + + q + + + + + + + + 1 + 1 + + + + + 28 + + + + u + + + + + + + + 1 + 1 + + + + + 28 + + + + y + + + + + + + + 1 + 1 + + + + + 28 + + + + t + + + + + + + + 1 + 1 + + + + + 28 + + + + o + + + + + + + + 1 + 1 + + + + + 28 + + + + p + + + + + + + + 1 + 1 + + + + + 28 + + + + i + + + + + + + + 1 + 1 + + + + + 28 + + + + a + + + + + + + + 1 + 1 + + + + + 28 + + + + s + + + + + + + + 1 + 1 + + + + + 28 + + + + d + + + + + + + + 1 + 1 + + + + + 28 + + + + f + + + + + + + + 1 + 1 + + + + + 28 + + + + h + + + + + + + + 1 + 1 + + + + + 28 + + + + j + + + + + + + + 1 + 1 + + + + + 28 + + + + g + + + + + + + + 1 + 1 + + + + + 28 + + + + k + + + + + + + + 1 + 1 + + + + + 28 + + + + l + + + + + + + + 1 + 1 + + + + + 28 + + + + : + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + z + + + + + + + + 1 + 1 + + + + + 28 + + + + c + + + + + + + + 1 + 1 + + + + + 28 + + + + x + + + + + + + + 1 + 1 + + + + + 28 + + + + v + + + + + + + + 1 + 1 + + + + + 28 + + + + m + + + + + + + + 1 + 1 + + + + + 28 + + + + , + + + + + + + + 1 + 1 + + + + + 28 + + + + n + + + + + + + + 1 + 1 + + + + + 28 + + + + b + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + ? + + + + + + + + 1 + 1 + + + + + 28 + + + + . + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Caps Lock + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + _ + + + + + + + + 1 + 1 + + + + + 28 + + + + " + + + + + + + + 1 + 1 + + + + + 28 + + + + @ + + + + + + + + 1 + 1 + + + + + 28 + + + + = + + + + + + + + 1 + 1 + + + + + 28 + + + + && + + + + + + + + 1 + 1 + + + + + 28 + + + + * + + + + + + + + 1 + 1 + + + + + 28 + + + + ) + + + + + + + + 1 + 1 + + + + + 28 + + + + ( + + + + + + + + 1 + 1 + + + + + 28 + + + + W + + + + + + + + 1 + 1 + + + + + 28 + + + + R + + + + + + + + 1 + 1 + + + + + 28 + + + + E + + + + + + + + 1 + 1 + + + + + 28 + + + + Q + + + + + + + + 1 + 1 + + + + + 28 + + + + U + + + + + + + + 1 + 1 + + + + + 28 + + + + Y + + + + + + + + 1 + 1 + + + + + 28 + + + + T + + + + + + + + 1 + 1 + + + + + 28 + + + + O + + + + + + + + 1 + 1 + + + + + 28 + + + + P + + + + + + + + 1 + 1 + + + + + 28 + + + + I + + + + + + + + 1 + 1 + + + + + 28 + + + + A + + + + + + + + 1 + 1 + + + + + 28 + + + + S + + + + + + + + 1 + 1 + + + + + 28 + + + + D + + + + + + + + 1 + 1 + + + + + 28 + + + + F + + + + + + + + 1 + 1 + + + + + 28 + + + + H + + + + + + + + 1 + 1 + + + + + 28 + + + + J + + + + + + + + 1 + 1 + + + + + 28 + + + + G + + + + + + + + 1 + 1 + + + + + 28 + + + + K + + + + + + + + 1 + 1 + + + + + 28 + + + + L + + + + + + + + 1 + 1 + + + + + 28 + + + + ; + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + Z + + + + + + + + 1 + 1 + + + + + 28 + + + + C + + + + + + + + 1 + 1 + + + + + 28 + + + + X + + + + + + + + 1 + 1 + + + + + 28 + + + + V + + + + + + + + 1 + 1 + + + + + 28 + + + + M + + + + + + + + 1 + 1 + + + + + 28 + + + + < + + + + + + + + 1 + 1 + + + + + 28 + + + + N + + + + + + + + 1 + 1 + + + + + 28 + + + + B + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + > + + + + + + + + 1 + 1 + + + + + 28 + + + + # + + + + + + + + 1 + 1 + + + + + 28 + + + + ] + + + + + + + + 1 + 1 + + + + + 28 + + + + $ + + + + + + + + 1 + 1 + + + + + 28 + + + + [ + + + + + + + + 1 + 1 + + + + + 28 + + + + ^ + + + + + + + + 1 + 1 + + + + + 28 + + + + % + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + button_1 + button_2 + button_3 + button_4 + button_5 + button_6 + button_7 + button_8 + button_9 + button_0 + button_minus + button_backspace + button_q + button_w + button_e + button_r + button_t + button_y + button_u + button_i + button_o + button_p + button_slash + button_return + button_a + button_s + button_d + button_f + button_g + button_h + button_j + button_k + button_l + button_colon + button_apostrophe + button_z + button_x + button_c + button_v + button_b + button_n + button_m + button_comma + button_dot + button_question + button_exclamation + button_ok + button_shift + button_space + button_hash + button_left_bracket + button_right_bracket + button_dollar + button_percent + button_circumflex + button_ampersand + button_asterisk + button_left_parenthesis + button_right_parenthesis + button_underscore + button_backspace_shift + button_q_shift + button_w_shift + button_e_shift + button_r_shift + button_t_shift + button_y_shift + button_u_shift + button_i_shift + button_o_shift + button_p_shift + button_at + button_return_shift + button_a_shift + button_s_shift + button_d_shift + button_f_shift + button_g_shift + button_h_shift + button_j_shift + button_k_shift + button_l_shift + button_semicolon + button_quotation + button_z_shift + button_x_shift + button_c_shift + button_v_shift + button_b_shift + button_n_shift + button_m_shift + button_less_than + button_greater_than + button_plus + button_equal + button_ok_shift + button_shift_shift + button_space_shift + button_1_num + button_2_num + button_3_num + button_backspace_num + button_4_num + button_5_num + button_6_num + button_ok_num + button_7_num + button_8_num + button_9_num + button_0_num + + + + + + diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp new file mode 100644 index 000000000..b112dd7b0 --- /dev/null +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -0,0 +1,417 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include + +#include +#include +#include +#include +#include +#endif + +#include "common/fs/path_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" +#include "yuzu/applets/qt_web_browser.h" +#include "yuzu/applets/qt_web_browser_scripts.h" +#include "yuzu/main.h" +#include "yuzu/util/url_request_interceptor.h" + +#ifdef YUZU_USE_QT_WEB_ENGINE + +namespace { + +constexpr int HIDButtonToKey(HIDButton button) { + switch (button) { + case HIDButton::DLeft: + case HIDButton::LStickLeft: + return Qt::Key_Left; + case HIDButton::DUp: + case HIDButton::LStickUp: + return Qt::Key_Up; + case HIDButton::DRight: + case HIDButton::LStickRight: + return Qt::Key_Right; + case HIDButton::DDown: + case HIDButton::LStickDown: + return Qt::Key_Down; + default: + return 0; + } +} + +} // Anonymous namespace + +QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_) + : QWebEngineView(parent), input_subsystem{input_subsystem_}, + url_interceptor(std::make_unique()), + input_interpreter(std::make_unique(system)), + default_profile{QWebEngineProfile::defaultProfile()}, + global_settings{QWebEngineSettings::globalSettings()} { + QWebEngineScript gamepad; + QWebEngineScript window_nx; + + gamepad.setName(QStringLiteral("gamepad_script.js")); + window_nx.setName(QStringLiteral("window_nx_script.js")); + + gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); + window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); + + gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); + window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); + + gamepad.setWorldId(QWebEngineScript::MainWorld); + window_nx.setWorldId(QWebEngineScript::MainWorld); + + gamepad.setRunsOnSubFrames(true); + window_nx.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(gamepad); + default_profile->scripts()->insert(window_nx); + + default_profile->setRequestInterceptor(url_interceptor.get()); + + global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); + global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); + global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); + + global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); + + connect( + page(), &QWebEnginePage::windowCloseRequested, page(), + [this] { + if (page()->url() == url_interceptor->GetRequestedURL()) { + SetFinished(true); + SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); + } + }, + Qt::QueuedConnection); +} + +QtNXWebEngineView::~QtNXWebEngineView() { + SetFinished(true); + StopInputThread(); +} + +void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url, + const std::string& additional_args) { + is_local = true; + + LoadExtractedFonts(); + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() + + QString::fromStdString(additional_args))); +} + +void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url, + const std::string& additional_args) { + is_local = false; + + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args))); +} + +void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { + const QString user_agent_str = [user_agent] { + switch (user_agent) { + case UserAgent::WebApplet: + default: + return QStringLiteral("WebApplet"); + case UserAgent::ShopN: + return QStringLiteral("ShopN"); + case UserAgent::LoginApplet: + return QStringLiteral("LoginApplet"); + case UserAgent::ShareApplet: + return QStringLiteral("ShareApplet"); + case UserAgent::LobbyApplet: + return QStringLiteral("LobbyApplet"); + case UserAgent::WifiWebAuthApplet: + return QStringLiteral("WifiWebAuthApplet"); + } + }(); + + QWebEngineProfile::defaultProfile()->setHttpUserAgent( + QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " + "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") + .arg(user_agent_str)); +} + +bool QtNXWebEngineView::IsFinished() const { + return finished; +} + +void QtNXWebEngineView::SetFinished(bool finished_) { + finished = finished_; +} + +Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { + return exit_reason; +} + +void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { + exit_reason = exit_reason_; +} + +const std::string& QtNXWebEngineView::GetLastURL() const { + return last_url; +} + +void QtNXWebEngineView::SetLastURL(std::string last_url_) { + last_url = std::move(last_url_); +} + +QString QtNXWebEngineView::GetCurrentURL() const { + return url_interceptor->GetRequestedURL().toString(); +} + +void QtNXWebEngineView::hide() { + SetFinished(true); + StopInputThread(); + + QWidget::hide(); +} + +void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->ReleaseKey(event->key()); + } +} + +template +void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + page()->runJavaScript( + QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast(button)), + [&](const QVariant& variant) { + if (variant.toBool()) { + switch (button) { + case HIDButton::A: + SendMultipleKeyPressEvents(); + break; + case HIDButton::B: + SendKeyPressEvent(Qt::Key_B); + break; + case HIDButton::X: + SendKeyPressEvent(Qt::Key_X); + break; + case HIDButton::Y: + SendKeyPressEvent(Qt::Key_Y); + break; + default: + break; + } + } + }); + + page()->runJavaScript( + QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") + .arg(static_cast(button))); + } + }; + + (f(T), ...); +} + +template +void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } + }; + + (f(T), ...); +} + +template +void QtNXWebEngineView::HandleWindowKeyButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } + }; + + (f(T), ...); +} + +void QtNXWebEngineView::SendKeyPressEvent(int key) { + if (key == 0) { + return; + } + + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); +} + +void QtNXWebEngineView::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + input_thread = std::thread(&QtNXWebEngineView::InputThread, this); +} + +void QtNXWebEngineView::StopInputThread() { + if (is_local) { + QWidget::releaseKeyboard(); + } + + input_thread_running = false; + if (input_thread.joinable()) { + input_thread.join(); + } +} + +void QtNXWebEngineView::InputThread() { + // Wait for 1 second before allowing any inputs to be processed. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (is_local) { + QWidget::grabKeyboard(); + } + + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleWindowFooterButtonPressedOnce(); + + HandleWindowKeyButtonPressedOnce(); + + HandleWindowKeyButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +void QtNXWebEngineView::LoadExtractedFonts() { + QWebEngineScript nx_font_css; + QWebEngineScript load_nx_font; + + auto fonts_dir_str = Common::FS::PathToUTF8String( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); + + std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); + + const auto fonts_dir = QString::fromStdString(fonts_dir_str); + + nx_font_css.setName(QStringLiteral("nx_font_css.js")); + load_nx_font.setName(QStringLiteral("load_nx_font.js")); + + nx_font_css.setSourceCode( + QString::fromStdString(NX_FONT_CSS) + .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) + .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); + load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); + + nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); + load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); + + nx_font_css.setWorldId(QWebEngineScript::MainWorld); + load_nx_font.setWorldId(QWebEngineScript::MainWorld); + + nx_font_css.setRunsOnSubFrames(true); + load_nx_font.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(nx_font_css); + default_profile->scripts()->insert(load_nx_font); + + connect( + url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), + [this] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); + }, + Qt::QueuedConnection); +} + +#endif + +QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { + connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, + &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, + &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserClosed, this, + &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); +} + +QtWebBrowser::~QtWebBrowser() = default; + +void QtWebBrowser::OpenLocalWebPage( + const std::string& local_url, std::function extract_romfs_callback_, + std::function callback_) const { + extract_romfs_callback = std::move(extract_romfs_callback_); + callback = std::move(callback_); + + const auto index = local_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(local_url, "", true); + } else { + emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); + } +} + +void QtWebBrowser::OpenExternalWebPage( + const std::string& external_url, + std::function callback_) const { + callback = std::move(callback_); + + const auto index = external_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(external_url, "", false); + } else { + emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), + false); + } +} + +void QtWebBrowser::MainWindowExtractOfflineRomFS() { + extract_romfs_callback(); +} + +void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url) { + callback(exit_reason, last_url); +} diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h new file mode 100644 index 000000000..7ad07409f --- /dev/null +++ b/src/yuzu/applets/qt_web_browser.h @@ -0,0 +1,218 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include +#endif + +#include "core/frontend/applets/web_browser.h" + +enum class HIDButton : u8; + +class GMainWindow; +class InputInterpreter; +class UrlRequestInterceptor; + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} + +#ifdef YUZU_USE_QT_WEB_ENGINE + +enum class UserAgent { + WebApplet, + ShopN, + LoginApplet, + ShareApplet, + LobbyApplet, + WifiWebAuthApplet, +}; + +class QWebEngineProfile; +class QWebEngineSettings; + +class QtNXWebEngineView : public QWebEngineView { + Q_OBJECT + +public: + explicit QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_); + ~QtNXWebEngineView() override; + + /** + * Loads a HTML document that exists locally. Cannot be used to load external websites. + * + * @param main_url The url to the file. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadLocalWebPage(const std::string& main_url, const std::string& additional_args); + + /** + * Loads an external website. Cannot be used to load local urls. + * + * @param main_url The url to the website. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadExternalWebPage(const std::string& main_url, const std::string& additional_args); + + /** + * Sets the background color of the web page. + * + * @param color The color to set. + */ + void SetBackgroundColor(QColor color); + + /** + * Sets the user agent of the web browser. + * + * @param user_agent The user agent enum. + */ + void SetUserAgent(UserAgent user_agent); + + [[nodiscard]] bool IsFinished() const; + void SetFinished(bool finished_); + + [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; + void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); + + [[nodiscard]] const std::string& GetLastURL() const; + void SetLastURL(std::string last_url_); + + /** + * This gets the current URL that has been requested by the webpage. + * This only applies to the main frame. Sub frames and other resources are ignored. + * + * @return Currently requested URL + */ + [[nodiscard]] QString GetCurrentURL() const; + +public slots: + void hide(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + +private: + /** + * Handles button presses to execute functions assigned in yuzu_key_callbacks. + * yuzu_key_callbacks contains specialized functions for the buttons in the window footer + * that can be overriden by games to achieve desired functionality. + * + * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks + */ + template + void HandleWindowFooterButtonPressedOnce(); + + /** + * Handles button presses and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleWindowKeyButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleWindowKeyButtonHold(); + + /** + * Sends a key press event to QWebEngineView. + * + * @param key Qt key code. + */ + void SendKeyPressEvent(int key); + + /** + * Sends multiple key press events to QWebEngineView. + * + * @tparam int Qt key code. + */ + template + void SendMultipleKeyPressEvents() { + (SendKeyPressEvent(T), ...); + } + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + /// Loads the extracted fonts using JavaScript. + void LoadExtractedFonts(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr url_interceptor; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; + + std::atomic finished{}; + + Service::AM::Applets::WebExitReason exit_reason{ + Service::AM::Applets::WebExitReason::EndButtonPressed}; + + std::string last_url{"http://localhost/"}; + + bool is_local{}; + + QWebEngineProfile* default_profile; + QWebEngineSettings* global_settings; +}; + +#endif + +class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { + Q_OBJECT + +public: + explicit QtWebBrowser(GMainWindow& parent); + ~QtWebBrowser() override; + + void OpenLocalWebPage(const std::string& local_url, + std::function extract_romfs_callback_, + std::function + callback_) const override; + + void OpenExternalWebPage(const std::string& external_url, + std::function + callback_) const override; + +signals: + void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args, + bool is_local) const; + +private: + void MainWindowExtractOfflineRomFS(); + + void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url); + + mutable std::function extract_romfs_callback; + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/qt_web_browser_scripts.h b/src/yuzu/applets/qt_web_browser_scripts.h new file mode 100644 index 000000000..992837a85 --- /dev/null +++ b/src/yuzu/applets/qt_web_browser_scripts.h @@ -0,0 +1,193 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +constexpr char NX_FONT_CSS[] = R"( +(function() { + css = document.createElement('style'); + css.type = 'text/css'; + css.id = 'nx_font'; + css.innerText = ` +/* FontStandard */ +@font-face { + font-family: 'FontStandard'; + src: url('%1') format('truetype'); +} + +/* FontChineseSimplified */ +@font-face { + font-family: 'FontChineseSimplified'; + src: url('%2') format('truetype'); +} + +/* FontExtendedChineseSimplified */ +@font-face { + font-family: 'FontExtendedChineseSimplified'; + src: url('%3') format('truetype'); +} + +/* FontChineseTraditional */ +@font-face { + font-family: 'FontChineseTraditional'; + src: url('%4') format('truetype'); +} + +/* FontKorean */ +@font-face { + font-family: 'FontKorean'; + src: url('%5') format('truetype'); +} + +/* FontNintendoExtended */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%6') format('truetype'); +} + +/* FontNintendoExtended2 */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%7') format('truetype'); +} +`; + + document.head.appendChild(css); +})(); +)"; + +constexpr char LOAD_NX_FONT[] = R"( +(function() { + var elements = document.querySelectorAll("*"); + + for (var i = 0; i < elements.length; i++) { + var style = window.getComputedStyle(elements[i], null); + if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || + style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { + elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } else { + elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } + } +})(); +)"; + +constexpr char GAMEPAD_SCRIPT[] = R"( +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); +}); + +window.addEventListener("gamepaddisconnected", function(e) { + console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); +}); +)"; + +constexpr char WINDOW_NX_SCRIPT[] = R"( +var end_applet = false; +var yuzu_key_callbacks = []; + +(function() { + class WindowNX { + constructor() { + yuzu_key_callbacks[1] = function() { window.history.back(); }; + yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; + } + + addEventListener(type, listener, options) { + console.log("nx.addEventListener called, type=%s", type); + + window.addEventListener(type, listener, options); + } + + endApplet() { + console.log("nx.endApplet called"); + + end_applet = true; + } + + playSystemSe(system_se) { + console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); + } + + sendMessage(message) { + console.log("nx.sendMessage is not implemented, message=%s", message); + } + + setCursorScrollSpeed(scroll_speed) { + console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); + } + } + + class WindowNXFooter { + setAssign(key, label, func, option) { + console.log("nx.footer.setAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = func; + break; + case "B": + yuzu_key_callbacks[1] = func; + break; + case "X": + yuzu_key_callbacks[2] = func; + break; + case "Y": + yuzu_key_callbacks[3] = func; + break; + case "L": + yuzu_key_callbacks[6] = func; + break; + case "R": + yuzu_key_callbacks[7] = func; + break; + } + } + + setFixed(kind) { + console.log("nx.footer.setFixed is not implemented, kind=%s", kind); + } + + unsetAssign(key) { + console.log("nx.footer.unsetAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = function() {}; + break; + case "B": + yuzu_key_callbacks[1] = function() {}; + break; + case "X": + yuzu_key_callbacks[2] = function() {}; + break; + case "Y": + yuzu_key_callbacks[3] = function() {}; + break; + case "L": + yuzu_key_callbacks[6] = function() {}; + break; + case "R": + yuzu_key_callbacks[7] = function() {}; + break; + } + } + } + + class WindowNXPlayReport { + incrementCounter(counter_id) { + console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); + } + + setCounterSetIdentifier(counter_id) { + console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); + } + } + + window.nx = new WindowNX(); + window.nx.footer = new WindowNXFooter(); + window.nx.playReport = new WindowNXPlayReport(); +})(); +)"; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp deleted file mode 100644 index aa453a79f..000000000 --- a/src/yuzu/applets/software_keyboard.cpp +++ /dev/null @@ -1,1620 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include - -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/string_util.h" -#include "core/core.h" -#include "core/frontend/input_interpreter.h" -#include "ui_software_keyboard.h" -#include "yuzu/applets/software_keyboard.h" -#include "yuzu/main.h" -#include "yuzu/util/overlay_dialog.h" - -namespace { - -using namespace Service::AM::Applets; - -constexpr float BASE_HEADER_FONT_SIZE = 23.0f; -constexpr float BASE_SUB_FONT_SIZE = 17.0f; -constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; -constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; -constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; -constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; -[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; -constexpr float BASE_HEIGHT = 720.0f; - -} // Anonymous namespace - -QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( - QWidget* parent, Core::System& system_, bool is_inline_, - Core::Frontend::KeyboardInitializeParameters initialize_parameters_) - : QDialog(parent), ui{std::make_unique()}, system{system_}, - is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { - ui->setupUi(this); - - setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose); - setAttribute(Qt::WA_TranslucentBackground); - - keyboard_buttons = {{ - {{ - { - ui->button_1, - ui->button_2, - ui->button_3, - ui->button_4, - ui->button_5, - ui->button_6, - ui->button_7, - ui->button_8, - ui->button_9, - ui->button_0, - ui->button_minus, - ui->button_backspace, - }, - { - ui->button_q, - ui->button_w, - ui->button_e, - ui->button_r, - ui->button_t, - ui->button_y, - ui->button_u, - ui->button_i, - ui->button_o, - ui->button_p, - ui->button_slash, - ui->button_return, - }, - { - ui->button_a, - ui->button_s, - ui->button_d, - ui->button_f, - ui->button_g, - ui->button_h, - ui->button_j, - ui->button_k, - ui->button_l, - ui->button_colon, - ui->button_apostrophe, - ui->button_return, - }, - { - ui->button_z, - ui->button_x, - ui->button_c, - ui->button_v, - ui->button_b, - ui->button_n, - ui->button_m, - ui->button_comma, - ui->button_dot, - ui->button_question, - ui->button_exclamation, - ui->button_ok, - }, - { - ui->button_shift, - ui->button_shift, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_space, - ui->button_ok, - }, - }}, - {{ - { - ui->button_hash, - ui->button_left_bracket, - ui->button_right_bracket, - ui->button_dollar, - ui->button_percent, - ui->button_circumflex, - ui->button_ampersand, - ui->button_asterisk, - ui->button_left_parenthesis, - ui->button_right_parenthesis, - ui->button_underscore, - ui->button_backspace_shift, - }, - { - ui->button_q_shift, - ui->button_w_shift, - ui->button_e_shift, - ui->button_r_shift, - ui->button_t_shift, - ui->button_y_shift, - ui->button_u_shift, - ui->button_i_shift, - ui->button_o_shift, - ui->button_p_shift, - ui->button_at, - ui->button_return_shift, - }, - { - ui->button_a_shift, - ui->button_s_shift, - ui->button_d_shift, - ui->button_f_shift, - ui->button_g_shift, - ui->button_h_shift, - ui->button_j_shift, - ui->button_k_shift, - ui->button_l_shift, - ui->button_semicolon, - ui->button_quotation, - ui->button_return_shift, - }, - { - ui->button_z_shift, - ui->button_x_shift, - ui->button_c_shift, - ui->button_v_shift, - ui->button_b_shift, - ui->button_n_shift, - ui->button_m_shift, - ui->button_less_than, - ui->button_greater_than, - ui->button_plus, - ui->button_equal, - ui->button_ok_shift, - }, - { - ui->button_shift_shift, - ui->button_shift_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_space_shift, - ui->button_ok_shift, - }, - }}, - }}; - - numberpad_buttons = {{ - { - ui->button_1_num, - ui->button_2_num, - ui->button_3_num, - ui->button_backspace_num, - }, - { - ui->button_4_num, - ui->button_5_num, - ui->button_6_num, - ui->button_ok_num, - }, - { - ui->button_7_num, - ui->button_8_num, - ui->button_9_num, - ui->button_ok_num, - }, - { - nullptr, - ui->button_0_num, - nullptr, - ui->button_ok_num, - }, - }}; - - all_buttons = { - ui->button_1, - ui->button_2, - ui->button_3, - ui->button_4, - ui->button_5, - ui->button_6, - ui->button_7, - ui->button_8, - ui->button_9, - ui->button_0, - ui->button_minus, - ui->button_backspace, - ui->button_q, - ui->button_w, - ui->button_e, - ui->button_r, - ui->button_t, - ui->button_y, - ui->button_u, - ui->button_i, - ui->button_o, - ui->button_p, - ui->button_slash, - ui->button_return, - ui->button_a, - ui->button_s, - ui->button_d, - ui->button_f, - ui->button_g, - ui->button_h, - ui->button_j, - ui->button_k, - ui->button_l, - ui->button_colon, - ui->button_apostrophe, - ui->button_z, - ui->button_x, - ui->button_c, - ui->button_v, - ui->button_b, - ui->button_n, - ui->button_m, - ui->button_comma, - ui->button_dot, - ui->button_question, - ui->button_exclamation, - ui->button_ok, - ui->button_shift, - ui->button_space, - ui->button_hash, - ui->button_left_bracket, - ui->button_right_bracket, - ui->button_dollar, - ui->button_percent, - ui->button_circumflex, - ui->button_ampersand, - ui->button_asterisk, - ui->button_left_parenthesis, - ui->button_right_parenthesis, - ui->button_underscore, - ui->button_backspace_shift, - ui->button_q_shift, - ui->button_w_shift, - ui->button_e_shift, - ui->button_r_shift, - ui->button_t_shift, - ui->button_y_shift, - ui->button_u_shift, - ui->button_i_shift, - ui->button_o_shift, - ui->button_p_shift, - ui->button_at, - ui->button_return_shift, - ui->button_a_shift, - ui->button_s_shift, - ui->button_d_shift, - ui->button_f_shift, - ui->button_g_shift, - ui->button_h_shift, - ui->button_j_shift, - ui->button_k_shift, - ui->button_l_shift, - ui->button_semicolon, - ui->button_quotation, - ui->button_z_shift, - ui->button_x_shift, - ui->button_c_shift, - ui->button_v_shift, - ui->button_b_shift, - ui->button_n_shift, - ui->button_m_shift, - ui->button_less_than, - ui->button_greater_than, - ui->button_plus, - ui->button_equal, - ui->button_ok_shift, - ui->button_shift_shift, - ui->button_space_shift, - ui->button_1_num, - ui->button_2_num, - ui->button_3_num, - ui->button_backspace_num, - ui->button_4_num, - ui->button_5_num, - ui->button_6_num, - ui->button_ok_num, - ui->button_7_num, - ui->button_8_num, - ui->button_9_num, - ui->button_0_num, - }; - - SetupMouseHover(); - - if (!initialize_parameters.ok_text.empty()) { - ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); - } - - ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); - ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); - - current_text = initialize_parameters.initial_text; - cursor_position = initialize_parameters.initial_cursor_position; - - SetTextDrawType(); - - for (auto* button : all_buttons) { - connect(button, &QPushButton::clicked, this, [this, button](bool) { - if (is_inline) { - InlineKeyboardButtonClicked(button); - } else { - NormalKeyboardButtonClicked(button); - } - }); - } - - // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend - if (system.IsPoweredOn()) { - input_interpreter = std::make_unique(system); - } -} - -QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { - StopInputThread(); -} - -void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { - if (isVisible()) { - return; - } - - MoveAndResizeWindow(pos, size); - - SetKeyboardType(); - SetPasswordMode(); - SetControllerImage(); - DisableKeyboardButtons(); - SetBackspaceOkEnabled(); - - open(); -} - -void QtSoftwareKeyboardDialog::ShowTextCheckDialog( - Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) { - switch (text_check_result) { - case SwkbdTextCheckResult::Success: - case SwkbdTextCheckResult::Silent: - default: - break; - case SwkbdTextCheckResult::Failure: { - StopInputThread(); - - OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), - QString{}, tr("OK"), Qt::AlignCenter); - dialog.exec(); - - StartInputThread(); - break; - } - case SwkbdTextCheckResult::Confirm: { - StopInputThread(); - - OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), - tr("Cancel"), tr("OK"), Qt::AlignCenter); - if (dialog.exec() != QDialog::Accepted) { - StartInputThread(); - break; - } - - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); - break; - } - } -} - -void QtSoftwareKeyboardDialog::ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { - MoveAndResizeWindow(pos, size); - - ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); - - ui->headerOSK->hide(); - ui->subOSK->hide(); - ui->inputOSK->hide(); - ui->charactersOSK->hide(); - ui->inputBoxOSK->hide(); - ui->charactersBoxOSK->hide(); - - initialize_parameters.max_text_length = appear_parameters.max_text_length; - initialize_parameters.min_text_length = appear_parameters.min_text_length; - initialize_parameters.type = appear_parameters.type; - initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; - initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; - initialize_parameters.enable_return_button = appear_parameters.enable_return_button; - initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; - - SetKeyboardType(); - SetControllerImage(); - DisableKeyboardButtons(); - SetBackspaceOkEnabled(); - - open(); -} - -void QtSoftwareKeyboardDialog::HideInlineKeyboard() { - StopInputThread(); - QDialog::hide(); -} - -void QtSoftwareKeyboardDialog::InlineTextChanged( - Core::Frontend::InlineTextParameters text_parameters) { - current_text = text_parameters.input_text; - cursor_position = text_parameters.cursor_position; - - SetBackspaceOkEnabled(); -} - -void QtSoftwareKeyboardDialog::ExitKeyboard() { - StopInputThread(); - QDialog::done(QDialog::Accepted); -} - -void QtSoftwareKeyboardDialog::open() { - QDialog::open(); - - row = 0; - column = 0; - - const auto* const curr_button = - keyboard_buttons[static_cast(bottom_osk_index)][row][column]; - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - - StartInputThread(); -} - -void QtSoftwareKeyboardDialog::reject() { - // Pressing the ESC key in a dialog calls QDialog::reject(). - // We will override this behavior to the "Cancel" action on the software keyboard. - TranslateButtonPress(HIDButton::X); -} - -void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { - if (!is_inline) { - QDialog::keyPressEvent(event); - return; - } - - const auto entered_key = event->key(); - - switch (entered_key) { - case Qt::Key_Escape: - QDialog::keyPressEvent(event); - return; - case Qt::Key_Backspace: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_backspace->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_backspace_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_backspace_num->click(); - break; - default: - break; - } - return; - case Qt::Key_Return: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_ok->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_ok_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_ok_num->click(); - break; - default: - break; - } - return; - case Qt::Key_Left: - MoveTextCursorDirection(Direction::Left); - return; - case Qt::Key_Right: - MoveTextCursorDirection(Direction::Right); - return; - default: - break; - } - - const auto entered_text = event->text(); - - if (entered_text.isEmpty()) { - return; - } - - InlineTextInsertString(entered_text.toStdU16String()); -} - -void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { - QDialog::move(pos); - QDialog::resize(size); - - // High DPI - const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; - - RescaleKeyboardElements(size.width(), size.height(), dpi_scale); -} - -void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { - const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto char_button_font_size = - BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - const auto label_button_font_size = - BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; - - QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); - QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); - QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); - QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); - QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, - QFont::Normal); - - ui->label_header->setFont(header_font); - ui->label_sub->setFont(sub_font); - ui->line_edit_osk->setFont(editor_font); - ui->text_edit_osk->setFont(editor_font); - ui->label_characters->setFont(sub_font); - ui->label_characters_box->setFont(sub_font); - - ui->label_shift->setFont(label_button_font); - ui->label_shift_shift->setFont(label_button_font); - ui->label_cancel->setFont(label_button_font); - ui->label_cancel_shift->setFont(label_button_font); - ui->label_cancel_num->setFont(label_button_font); - ui->label_enter->setFont(label_button_font); - ui->label_enter_shift->setFont(label_button_font); - ui->label_enter_num->setFont(label_button_font); - - for (auto* button : all_buttons) { - if (button == ui->button_return || button == ui->button_return_shift) { - button->setFont(label_button_font); - continue; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - button->setFont(label_button_font); - continue; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - button->setFont(label_button_font); - button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * - (height / BASE_HEIGHT)); - continue; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - button->setFont(label_button_font); - button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * - (height / BASE_HEIGHT)); - continue; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || - button == ui->button_ok_num) { - button->setFont(label_button_font); - continue; - } - - button->setFont(char_button_font); - } -} - -void QtSoftwareKeyboardDialog::SetKeyboardType() { - switch (initialize_parameters.type) { - case SwkbdType::Normal: - case SwkbdType::Qwerty: - case SwkbdType::Unknown3: - case SwkbdType::Latin: - case SwkbdType::SimplifiedChinese: - case SwkbdType::TraditionalChinese: - case SwkbdType::Korean: - default: { - bottom_osk_index = BottomOSKIndex::LowerCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->verticalLayout_2->setStretch(0, 320); - ui->verticalLayout_2->setStretch(1, 400); - - ui->gridLineOSK->setRowStretch(5, 94); - ui->gridBoxOSK->setRowStretch(2, 81); - break; - } - case SwkbdType::NumberPad: { - bottom_osk_index = BottomOSKIndex::NumberPad; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->verticalLayout_2->setStretch(0, 370); - ui->verticalLayout_2->setStretch(1, 350); - - ui->gridLineOSK->setRowStretch(5, 144); - ui->gridBoxOSK->setRowStretch(2, 131); - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetPasswordMode() { - switch (initialize_parameters.password_mode) { - case SwkbdPasswordMode::Disabled: - default: - ui->line_edit_osk->setEchoMode(QLineEdit::Normal); - break; - case SwkbdPasswordMode::Enabled: - ui->line_edit_osk->setEchoMode(QLineEdit::Password); - break; - } -} - -void QtSoftwareKeyboardDialog::SetTextDrawType() { - switch (initialize_parameters.text_draw_type) { - case SwkbdTextDrawType::Line: - case SwkbdTextDrawType::DownloadCode: { - ui->topOSK->setCurrentIndex(0); - - if (initialize_parameters.max_text_length <= 10) { - ui->gridLineOSK->setColumnStretch(0, 390); - ui->gridLineOSK->setColumnStretch(1, 500); - ui->gridLineOSK->setColumnStretch(2, 390); - } else { - ui->gridLineOSK->setColumnStretch(0, 130); - ui->gridLineOSK->setColumnStretch(1, 1020); - ui->gridLineOSK->setColumnStretch(2, 130); - } - - if (is_inline) { - return; - } - - connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { - const auto is_valid = ValidateInputText(changed_string); - - const auto text_length = static_cast(changed_string.length()); - - ui->label_characters->setText(QStringLiteral("%1/%2") - .arg(text_length) - .arg(initialize_parameters.max_text_length)); - - ui->button_ok->setEnabled(is_valid); - ui->button_ok_shift->setEnabled(is_valid); - ui->button_ok_num->setEnabled(is_valid); - - ui->line_edit_osk->setFocus(); - }); - - connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, - [this](int old_cursor_position, int new_cursor_position) { - ui->button_backspace->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - ui->button_backspace_shift->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - ui->button_backspace_num->setEnabled( - initialize_parameters.enable_backspace_button && new_cursor_position > 0); - - ui->line_edit_osk->setFocus(); - }); - - connect( - ui->line_edit_osk, &QLineEdit::returnPressed, this, - [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); - - ui->line_edit_osk->setPlaceholderText( - QString::fromStdU16String(initialize_parameters.guide_text)); - ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); - ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); - ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); - - ui->label_characters->setText(QStringLiteral("%1/%2") - .arg(initialize_parameters.initial_text.size()) - .arg(initialize_parameters.max_text_length)); - break; - } - case SwkbdTextDrawType::Box: - default: { - ui->topOSK->setCurrentIndex(1); - - if (is_inline) { - return; - } - - connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { - if (static_cast(ui->text_edit_osk->toPlainText().length()) > - initialize_parameters.max_text_length) { - auto text_cursor = ui->text_edit_osk->textCursor(); - ui->text_edit_osk->setTextCursor(text_cursor); - text_cursor.deletePreviousChar(); - } - - const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); - - const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); - - ui->label_characters_box->setText(QStringLiteral("%1/%2") - .arg(text_length) - .arg(initialize_parameters.max_text_length)); - - ui->button_ok->setEnabled(is_valid); - ui->button_ok_shift->setEnabled(is_valid); - ui->button_ok_num->setEnabled(is_valid); - - ui->text_edit_osk->setFocus(); - }); - - connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { - const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - new_cursor_position > 0); - - ui->text_edit_osk->setFocus(); - }); - - ui->text_edit_osk->setPlaceholderText( - QString::fromStdU16String(initialize_parameters.guide_text)); - ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); - ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 - ? QTextCursor::Start - : QTextCursor::End); - - ui->label_characters_box->setText(QStringLiteral("%1/%2") - .arg(initialize_parameters.initial_text.size()) - .arg(initialize_parameters.max_text_length)); - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetControllerImage() { - const auto controller_type = Settings::values.players.GetValue()[8].connected - ? Settings::values.players.GetValue()[8].controller_type - : Settings::values.players.GetValue()[0].controller_type; - - const QString theme = [] { - if (QIcon::themeName().contains(QStringLiteral("dark")) || - QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_dark"); - } else { - return QString{}; - } - }(); - - switch (controller_type) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::GameCube: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); - break; - case Settings::ControllerType::DualJoyconDetached: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); - break; - case Settings::ControllerType::LeftJoycon: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") - .arg(theme)); - break; - case Settings::ControllerType::RightJoycon: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") - .arg(theme)); - break; - case Settings::ControllerType::Handheld: - ui->icon_controller->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - ui->icon_controller_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - ui->icon_controller_num->setStyleSheet( - QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: - default: { - for (const auto& keys : keyboard_buttons) { - for (const auto& rows : keys) { - for (auto* button : rows) { - if (!button) { - continue; - } - - button->setEnabled(true); - } - } - } - - const auto& key_disable_flags = initialize_parameters.key_disable_flags; - - ui->button_space->setDisabled(key_disable_flags.space); - ui->button_space_shift->setDisabled(key_disable_flags.space); - - ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); - - ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); - - ui->button_slash->setDisabled(key_disable_flags.slash); - - ui->button_1->setDisabled(key_disable_flags.numbers); - ui->button_2->setDisabled(key_disable_flags.numbers); - ui->button_3->setDisabled(key_disable_flags.numbers); - ui->button_4->setDisabled(key_disable_flags.numbers); - ui->button_5->setDisabled(key_disable_flags.numbers); - ui->button_6->setDisabled(key_disable_flags.numbers); - ui->button_7->setDisabled(key_disable_flags.numbers); - ui->button_8->setDisabled(key_disable_flags.numbers); - ui->button_9->setDisabled(key_disable_flags.numbers); - ui->button_0->setDisabled(key_disable_flags.numbers); - - ui->button_return->setEnabled(initialize_parameters.enable_return_button); - ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); - break; - } - case BottomOSKIndex::NumberPad: { - for (const auto& rows : numberpad_buttons) { - for (auto* button : rows) { - if (!button) { - continue; - } - - button->setEnabled(true); - } - } - break; - } - } -} - -void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { - if (is_inline) { - ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); - ui->button_ok_shift->setEnabled(current_text.size() >= - initialize_parameters.min_text_length); - ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - cursor_position > 0); - } else { - const auto text_length = [this] { - if (ui->topOSK->currentIndex() == 1) { - return static_cast(ui->text_edit_osk->toPlainText().length()); - } else { - return static_cast(ui->line_edit_osk->text().length()); - } - }(); - - const auto normal_cursor_position = [this] { - if (ui->topOSK->currentIndex() == 1) { - return ui->text_edit_osk->textCursor().position(); - } else { - return ui->line_edit_osk->cursorPosition(); - } - }(); - - ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); - ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); - ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); - - ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && - normal_cursor_position > 0); - } -} - -bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { - const auto& key_disable_flags = initialize_parameters.key_disable_flags; - - const auto input_text_length = static_cast(input_text.length()); - - if (input_text_length < initialize_parameters.min_text_length || - input_text_length > initialize_parameters.max_text_length) { - return false; - } - - if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { - return false; - } - - if ((key_disable_flags.at || key_disable_flags.username) && - input_text.contains(QLatin1Char{'@'})) { - return false; - } - - if ((key_disable_flags.percent || key_disable_flags.username) && - input_text.contains(QLatin1Char{'%'})) { - return false; - } - - if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { - return false; - } - - if ((key_disable_flags.backslash || key_disable_flags.username) && - input_text.contains(QLatin1Char('\\'))) { - return false; - } - - if (key_disable_flags.numbers && - std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { - return false; - } - - if (bottom_osk_index == BottomOSKIndex::NumberPad && - std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { - return false; - } - - return true; -} - -void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - bottom_osk_index = BottomOSKIndex::UpperCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - break; - case BottomOSKIndex::UpperCase: - if (caps_lock_enabled) { - caps_lock_enabled = false; - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - - ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); - - bottom_osk_index = BottomOSKIndex::LowerCase; - ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); - } else { - caps_lock_enabled = true; - - ui->button_shift_shift->setStyleSheet( - QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);" - "\nimage-position: left;")); - - ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); - ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); - - ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); - } - break; - case BottomOSKIndex::NumberPad: - default: - break; - } -} - -void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { - if (button == ui->button_ampersand) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral("&")); - } else { - ui->line_edit_osk->insert(QStringLiteral("&")); - } - return; - } - - if (button == ui->button_return || button == ui->button_return_shift) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); - } else { - ui->line_edit_osk->insert(QStringLiteral("\n")); - } - return; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); - } else { - ui->line_edit_osk->insert(QStringLiteral(" ")); - } - return; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - ChangeBottomOSKIndex(); - return; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - if (ui->topOSK->currentIndex() == 1) { - auto text_cursor = ui->text_edit_osk->textCursor(); - ui->text_edit_osk->setTextCursor(text_cursor); - text_cursor.deletePreviousChar(); - } else { - ui->line_edit_osk->backspace(); - } - return; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); - return; - } - - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->insertPlainText(button->text()); - } else { - ui->line_edit_osk->insert(button->text()); - } - - // Revert the keyboard to lowercase if the shift key is active. - if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { - // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase - // if bottom_osk_index is UpperCase and caps_lock_enabled is true. - caps_lock_enabled = true; - ChangeBottomOSKIndex(); - } -} - -void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { - if (!button->isEnabled()) { - return; - } - - if (button == ui->button_ampersand) { - InlineTextInsertString(u"&"); - return; - } - - if (button == ui->button_return || button == ui->button_return_shift) { - InlineTextInsertString(u"\n"); - return; - } - - if (button == ui->button_space || button == ui->button_space_shift) { - InlineTextInsertString(u" "); - return; - } - - if (button == ui->button_shift || button == ui->button_shift_shift) { - ChangeBottomOSKIndex(); - return; - } - - if (button == ui->button_backspace || button == ui->button_backspace_shift || - button == ui->button_backspace_num) { - if (cursor_position <= 0 || current_text.empty()) { - cursor_position = 0; - return; - } - - --cursor_position; - - current_text.erase(cursor_position, 1); - - SetBackspaceOkEnabled(); - - emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); - return; - } - - if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { - emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); - return; - } - - InlineTextInsertString(button->text().toStdU16String()); - - // Revert the keyboard to lowercase if the shift key is active. - if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { - // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase - // if bottom_osk_index is UpperCase and caps_lock_enabled is true. - caps_lock_enabled = true; - ChangeBottomOSKIndex(); - } -} - -void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { - if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { - return; - } - - current_text.insert(cursor_position, string); - - cursor_position += static_cast(string.size()); - - SetBackspaceOkEnabled(); - - emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); -} - -void QtSoftwareKeyboardDialog::SetupMouseHover() { - // setFocus() has a bug where continuously changing focus will cause the focus UI to - // mysteriously disappear. A workaround we have found is using the mouse to hover over - // the buttons to act in place of the button focus. As a result, we will have to set - // a blank cursor when hovering over all the buttons and set a no focus policy so the - // buttons do not stay in focus in addition to the mouse hover. - for (auto* button : all_buttons) { - button->setCursor(QCursor(Qt::BlankCursor)); - button->setFocusPolicy(Qt::NoFocus); - } -} - -template -void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - TranslateButtonPress(button); - } - }; - - (f(T), ...); -} - -template -void QtSoftwareKeyboardDialog::HandleButtonHold() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonHeld(button)) { - TranslateButtonPress(button); - } - }; - - (f(T), ...); -} - -void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { - switch (button) { - case HIDButton::A: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: - keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); - break; - case BottomOSKIndex::NumberPad: - numberpad_buttons[row][column]->click(); - break; - default: - break; - } - break; - case HIDButton::B: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_backspace->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_backspace_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_backspace_num->click(); - break; - default: - break; - } - break; - case HIDButton::X: - if (is_inline) { - emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); - } else { - auto text = ui->topOSK->currentIndex() == 1 - ? ui->text_edit_osk->toPlainText().toStdU16String() - : ui->line_edit_osk->text().toStdU16String(); - - emit SubmitNormalText(SwkbdResult::Cancel, std::move(text)); - } - break; - case HIDButton::Y: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_space->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_space_shift->click(); - break; - case BottomOSKIndex::NumberPad: - default: - break; - } - break; - case HIDButton::LStick: - case HIDButton::RStick: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_shift->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_shift_shift->click(); - break; - case BottomOSKIndex::NumberPad: - default: - break; - } - break; - case HIDButton::L: - MoveTextCursorDirection(Direction::Left); - break; - case HIDButton::R: - MoveTextCursorDirection(Direction::Right); - break; - case HIDButton::Plus: - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_ok->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_ok_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_ok_num->click(); - break; - default: - break; - } - break; - case HIDButton::DLeft: - case HIDButton::LStickLeft: - case HIDButton::RStickLeft: - MoveButtonDirection(Direction::Left); - break; - case HIDButton::DUp: - case HIDButton::LStickUp: - case HIDButton::RStickUp: - MoveButtonDirection(Direction::Up); - break; - case HIDButton::DRight: - case HIDButton::LStickRight: - case HIDButton::RStickRight: - MoveButtonDirection(Direction::Right); - break; - case HIDButton::DDown: - case HIDButton::LStickDown: - case HIDButton::RStickDown: - MoveButtonDirection(Direction::Down); - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { - // Changes the row or column index depending on the direction. - auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { - switch (direction) { - case Direction::Left: - column = (column + max_columns - 1) % max_columns; - break; - case Direction::Up: - row = (row + max_rows - 1) % max_rows; - break; - case Direction::Right: - column = (column + 1) % max_columns; - break; - case Direction::Down: - row = (row + 1) % max_rows; - break; - default: - break; - } - }; - - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - case BottomOSKIndex::UpperCase: { - const auto index = static_cast(bottom_osk_index); - - const auto* const prev_button = keyboard_buttons[index][row][column]; - move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); - auto* curr_button = keyboard_buttons[index][row][column]; - - while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { - move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); - curr_button = keyboard_buttons[index][row][column]; - } - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - break; - } - case BottomOSKIndex::NumberPad: { - const auto* const prev_button = numberpad_buttons[row][column]; - move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); - auto* curr_button = numberpad_buttons[row][column]; - - while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { - move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); - curr_button = numberpad_buttons[row][column]; - } - - // This is a workaround for setFocus() randomly not showing focus in the UI - QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); - break; - } - default: - break; - } -} - -void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { - switch (direction) { - case Direction::Left: - if (is_inline) { - if (cursor_position <= 0) { - cursor_position = 0; - } else { - --cursor_position; - emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); - } - } else { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->moveCursor(QTextCursor::Left); - } else { - ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); - } - } - break; - case Direction::Right: - if (is_inline) { - if (cursor_position >= static_cast(current_text.size())) { - cursor_position = static_cast(current_text.size()); - } else { - ++cursor_position; - emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); - } - } else { - if (ui->topOSK->currentIndex() == 1) { - ui->text_edit_osk->moveCursor(QTextCursor::Right); - } else { - ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); - } - } - break; - default: - break; - } -} - -void QtSoftwareKeyboardDialog::StartInputThread() { - if (input_thread_running) { - return; - } - - input_thread_running = true; - - input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); -} - -void QtSoftwareKeyboardDialog::StopInputThread() { - input_thread_running = false; - - if (input_thread.joinable()) { - input_thread.join(); - } - - if (input_interpreter) { - input_interpreter->ResetButtonStates(); - } -} - -void QtSoftwareKeyboardDialog::InputThread() { - while (input_thread_running) { - input_interpreter->PollInput(); - - HandleButtonPressedOnce(); - - HandleButtonHold(); - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } -} - -QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { - connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, - &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, - &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, - &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, - &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, - &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); -} - -QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; - -void QtSoftwareKeyboard::InitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, - std::function submit_normal_callback_, - std::function - submit_inline_callback_) { - if (is_inline) { - submit_inline_callback = std::move(submit_inline_callback_); - } else { - submit_normal_callback = std::move(submit_normal_callback_); - } - - LOG_INFO(Service_AM, - "\nKeyboardInitializeParameters:" - "\nok_text={}" - "\nheader_text={}" - "\nsub_text={}" - "\nguide_text={}" - "\ninitial_text={}" - "\nmax_text_length={}" - "\nmin_text_length={}" - "\ninitial_cursor_position={}" - "\ntype={}" - "\npassword_mode={}" - "\ntext_draw_type={}" - "\nkey_disable_flags={}" - "\nuse_blur_background={}" - "\nenable_backspace_button={}" - "\nenable_return_button={}" - "\ndisable_cancel_button={}", - Common::UTF16ToUTF8(initialize_parameters.ok_text), - Common::UTF16ToUTF8(initialize_parameters.header_text), - Common::UTF16ToUTF8(initialize_parameters.sub_text), - Common::UTF16ToUTF8(initialize_parameters.guide_text), - Common::UTF16ToUTF8(initialize_parameters.initial_text), - initialize_parameters.max_text_length, initialize_parameters.min_text_length, - initialize_parameters.initial_cursor_position, initialize_parameters.type, - initialize_parameters.password_mode, initialize_parameters.text_draw_type, - initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, - initialize_parameters.enable_backspace_button, - initialize_parameters.enable_return_button, - initialize_parameters.disable_cancel_button); - - emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); -} - -void QtSoftwareKeyboard::ShowNormalKeyboard() const { - emit MainWindowShowNormalKeyboard(); -} - -void QtSoftwareKeyboard::ShowTextCheckDialog( - Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const { - emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message)); -} - -void QtSoftwareKeyboard::ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const { - LOG_INFO(Service_AM, - "\nInlineAppearParameters:" - "\nmax_text_length={}" - "\nmin_text_length={}" - "\nkey_top_scale_x={}" - "\nkey_top_scale_y={}" - "\nkey_top_translate_x={}" - "\nkey_top_translate_y={}" - "\ntype={}" - "\nkey_disable_flags={}" - "\nkey_top_as_floating={}" - "\nenable_backspace_button={}" - "\nenable_return_button={}" - "\ndisable_cancel_button={}", - appear_parameters.max_text_length, appear_parameters.min_text_length, - appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, - appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, - appear_parameters.type, appear_parameters.key_disable_flags.raw, - appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, - appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); - - emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); -} - -void QtSoftwareKeyboard::HideInlineKeyboard() const { - emit MainWindowHideInlineKeyboard(); -} - -void QtSoftwareKeyboard::InlineTextChanged( - Core::Frontend::InlineTextParameters text_parameters) const { - LOG_INFO(Service_AM, - "\nInlineTextParameters:" - "\ninput_text={}" - "\ncursor_position={}", - Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); - - emit MainWindowInlineTextChanged(std::move(text_parameters)); -} - -void QtSoftwareKeyboard::ExitKeyboard() const { - emit MainWindowExitKeyboard(); -} - -void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const { - submit_normal_callback(result, submitted_text); -} - -void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, - s32 cursor_position) const { - submit_inline_callback(reply_type, submitted_text, cursor_position); -} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h deleted file mode 100644 index 1a03c098c..000000000 --- a/src/yuzu/applets/software_keyboard.h +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include "core/frontend/applets/software_keyboard.h" - -enum class HIDButton : u8; - -class InputInterpreter; - -namespace Core { -class System; -} - -namespace Ui { -class QtSoftwareKeyboardDialog; -} - -class GMainWindow; - -class QtSoftwareKeyboardDialog final : public QDialog { - Q_OBJECT - -public: - QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, - Core::Frontend::KeyboardInitializeParameters initialize_parameters_); - ~QtSoftwareKeyboardDialog() override; - - void ShowNormalKeyboard(QPoint pos, QSize size); - - void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message); - - void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, - QSize size); - - void HideInlineKeyboard(); - - void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); - - void ExitKeyboard(); - -signals: - void SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const; - - void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, s32 cursor_position) const; - -public slots: - void open() override; - void reject() override; - -protected: - /// We override the keyPressEvent for inputting text into the inline software keyboard. - void keyPressEvent(QKeyEvent* event) override; - -private: - enum class Direction { - Left, - Up, - Right, - Down, - }; - - enum class BottomOSKIndex { - LowerCase, - UpperCase, - NumberPad, - }; - - /** - * Moves and resizes the window to a specified position and size. - * - * @param pos Top-left window position - * @param size Window size - */ - void MoveAndResizeWindow(QPoint pos, QSize size); - - /** - * Rescales all keyboard elements to account for High DPI displays. - * - * @param width Window width - * @param height Window height - * @param dpi_scale Display scaling factor - */ - void RescaleKeyboardElements(float width, float height, float dpi_scale); - - /// Sets the keyboard type based on initialize_parameters. - void SetKeyboardType(); - - /// Sets the password mode based on initialize_parameters. - void SetPasswordMode(); - - /// Sets the text draw type based on initialize_parameters. - void SetTextDrawType(); - - /// Sets the controller image at the bottom left of the software keyboard. - void SetControllerImage(); - - /// Disables buttons based on initialize_parameters. - void DisableKeyboardButtons(); - - /// Changes whether the backspace or/and ok buttons should be enabled or disabled. - void SetBackspaceOkEnabled(); - - /** - * Validates the input text sent in based on the parameters in initialize_parameters. - * - * @param input_text Input text - * - * @returns True if the input text is valid, false otherwise. - */ - bool ValidateInputText(const QString& input_text); - - /// Switches between LowerCase and UpperCase (Shift and Caps Lock) - void ChangeBottomOSKIndex(); - - /// Processes a keyboard button click from the UI as normal keyboard input. - void NormalKeyboardButtonClicked(QPushButton* button); - - /// Processes a keyboard button click from the UI as inline keyboard input. - void InlineKeyboardButtonClicked(QPushButton* button); - - /** - * Inserts a string of arbitrary length into the current_text at the current cursor position. - * This is only used for the inline software keyboard. - */ - void InlineTextInsertString(std::u16string_view string); - - /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. - void SetupMouseHover(); - - /** - * Handles button presses and converts them into keyboard input. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleButtonPressedOnce(); - - /** - * Handles button holds and converts them into keyboard input. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleButtonHold(); - - /** - * Translates a button press to focus or click a keyboard button. - * - * @param button The button press to process. - */ - void TranslateButtonPress(HIDButton button); - - /** - * Moves the focus of a button in a certain direction. - * - * @param direction The direction to move. - */ - void MoveButtonDirection(Direction direction); - - /** - * Moves the text cursor in a certain direction. - * - * @param direction The direction to move. - */ - void MoveTextCursorDirection(Direction direction); - - void StartInputThread(); - void StopInputThread(); - - /// The thread where input is being polled and processed. - void InputThread(); - - std::unique_ptr ui; - - Core::System& system; - - // True if it is the inline software keyboard. - bool is_inline; - - // Common software keyboard initialize parameters. - Core::Frontend::KeyboardInitializeParameters initialize_parameters; - - // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. - std::u16string current_text; - s32 cursor_position{0}; - - static constexpr std::size_t NUM_ROWS_NORMAL = 5; - static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; - static constexpr std::size_t NUM_ROWS_NUMPAD = 4; - static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; - - // Stores the normal keyboard layout. - std::array, NUM_ROWS_NORMAL>, 2> - keyboard_buttons; - // Stores the numberpad keyboard layout. - std::array, NUM_ROWS_NUMPAD> numberpad_buttons; - - // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. - std::array all_buttons; - - std::size_t row{0}; - std::size_t column{0}; - - BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; - std::atomic caps_lock_enabled{false}; - - std::unique_ptr input_interpreter; - - std::thread input_thread; - - std::atomic input_thread_running{}; -}; - -class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { - Q_OBJECT - -public: - explicit QtSoftwareKeyboard(GMainWindow& parent); - ~QtSoftwareKeyboard() override; - - void InitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, - std::function - submit_normal_callback_, - std::function - submit_inline_callback_) override; - - void ShowNormalKeyboard() const override; - - void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const override; - - void ShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const override; - - void HideInlineKeyboard() const override; - - void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; - - void ExitKeyboard() const override; - -signals: - void MainWindowInitializeKeyboard( - bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; - - void MainWindowShowNormalKeyboard() const; - - void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, - std::u16string text_check_message) const; - - void MainWindowShowInlineKeyboard( - Core::Frontend::InlineAppearParameters appear_parameters) const; - - void MainWindowHideInlineKeyboard() const; - - void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; - - void MainWindowExitKeyboard() const; - -private: - void SubmitNormalText(Service::AM::Applets::SwkbdResult result, - std::u16string submitted_text) const; - - void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, - std::u16string submitted_text, s32 cursor_position) const; - - mutable std::function - submit_normal_callback; - mutable std::function - submit_inline_callback; -}; diff --git a/src/yuzu/applets/software_keyboard.ui b/src/yuzu/applets/software_keyboard.ui deleted file mode 100644 index b0a1fcde9..000000000 --- a/src/yuzu/applets/software_keyboard.ui +++ /dev/null @@ -1,3503 +0,0 @@ - - - QtSoftwareKeyboardDialog - - - - 0 - 0 - 1280 - 720 - - - - Software Keyboard - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 0 - 100 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 17 - - - - 0/32 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - 26 - 50 - false - - - - Qt::StrongFocus - - - - - - 32 - - - Enter Text - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - 23 - - - - - - - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - 17 - - - - - - - - - - - Qt::Horizontal - - - - 127 - 20 - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 17 - - - - 0/500 - - - - - - - - - - - 0 - - - 14 - - - 9 - - - 14 - - - 9 - - - - - - 26 - - - - Qt::StrongFocus - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - - - - - - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 0 - - - 2 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Shift - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - ' - - - - - - - - 1 - 1 - - - - - 28 - - - - / - - - - - - - - 1 - 1 - - - - - 28 - - - - ! - - - - - - - - 1 - 1 - - - - - 28 - - - - 7 - - - - - - - - 1 - 1 - - - - - 28 - - - - 8 - - - - - - - - 1 - 1 - - - - - 28 - - - - 0 - - - - - - - - 1 - 1 - - - - - 28 - - - - 9 - - - - - - - - 1 - 1 - - - - - 28 - - - - w - - - - - - - - 1 - 1 - - - - - 28 - - - - r - - - - - - - - 1 - 1 - - - - - 28 - - - - e - - - - - - - - 1 - 1 - - - - - 28 - - - - q - - - - - - - - 1 - 1 - - - - - 28 - - - - u - - - - - - - - 1 - 1 - - - - - 28 - - - - y - - - - - - - - 1 - 1 - - - - - 28 - - - - t - - - - - - - - 1 - 1 - - - - - 28 - - - - o - - - - - - - - 1 - 1 - - - - - 28 - - - - p - - - - - - - - 1 - 1 - - - - - 28 - - - - i - - - - - - - - 1 - 1 - - - - - 28 - - - - a - - - - - - - - 1 - 1 - - - - - 28 - - - - s - - - - - - - - 1 - 1 - - - - - 28 - - - - d - - - - - - - - 1 - 1 - - - - - 28 - - - - f - - - - - - - - 1 - 1 - - - - - 28 - - - - h - - - - - - - - 1 - 1 - - - - - 28 - - - - j - - - - - - - - 1 - 1 - - - - - 28 - - - - g - - - - - - - - 1 - 1 - - - - - 28 - - - - k - - - - - - - - 1 - 1 - - - - - 28 - - - - l - - - - - - - - 1 - 1 - - - - - 28 - - - - : - - - - - - - - 1 - 1 - - - - - 18 - - - - Return - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - z - - - - - - - - 1 - 1 - - - - - 28 - - - - c - - - - - - - - 1 - 1 - - - - - 28 - - - - x - - - - - - - - 1 - 1 - - - - - 28 - - - - v - - - - - - - - 1 - 1 - - - - - 28 - - - - m - - - - - - - - 1 - 1 - - - - - 28 - - - - , - - - - - - - - 1 - 1 - - - - - 28 - - - - n - - - - - - - - 1 - 1 - - - - - 28 - - - - b - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - true - - - false - - - - - - - - 1 - 1 - - - - - 28 - - - - ? - - - - - - - - 1 - 1 - - - - - 28 - - - - . - - - - - - - - 1 - 1 - - - - - 28 - - - - 1 - - - - - - - - 1 - 1 - - - - - 28 - - - - 3 - - - - - - - - 1 - 1 - - - - - 28 - - - - 4 - - - - - - - - 1 - 1 - - - - - 28 - - - - 2 - - - - - - - - 1 - 1 - - - - - 28 - - - - 6 - - - - - - - - 1 - 1 - - - - - 28 - - - - 5 - - - - - - - - 1 - 1 - - - - - 18 - - - - Space - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 0 - - - 2 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Caps Lock - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - _ - - - - - - - - 1 - 1 - - - - - 28 - - - - " - - - - - - - - 1 - 1 - - - - - 28 - - - - @ - - - - - - - - 1 - 1 - - - - - 28 - - - - = - - - - - - - - 1 - 1 - - - - - 28 - - - - && - - - - - - - - 1 - 1 - - - - - 28 - - - - * - - - - - - - - 1 - 1 - - - - - 28 - - - - ) - - - - - - - - 1 - 1 - - - - - 28 - - - - ( - - - - - - - - 1 - 1 - - - - - 28 - - - - W - - - - - - - - 1 - 1 - - - - - 28 - - - - R - - - - - - - - 1 - 1 - - - - - 28 - - - - E - - - - - - - - 1 - 1 - - - - - 28 - - - - Q - - - - - - - - 1 - 1 - - - - - 28 - - - - U - - - - - - - - 1 - 1 - - - - - 28 - - - - Y - - - - - - - - 1 - 1 - - - - - 28 - - - - T - - - - - - - - 1 - 1 - - - - - 28 - - - - O - - - - - - - - 1 - 1 - - - - - 28 - - - - P - - - - - - - - 1 - 1 - - - - - 28 - - - - I - - - - - - - - 1 - 1 - - - - - 28 - - - - A - - - - - - - - 1 - 1 - - - - - 28 - - - - S - - - - - - - - 1 - 1 - - - - - 28 - - - - D - - - - - - - - 1 - 1 - - - - - 28 - - - - F - - - - - - - - 1 - 1 - - - - - 28 - - - - H - - - - - - - - 1 - 1 - - - - - 28 - - - - J - - - - - - - - 1 - 1 - - - - - 28 - - - - G - - - - - - - - 1 - 1 - - - - - 28 - - - - K - - - - - - - - 1 - 1 - - - - - 28 - - - - L - - - - - - - - 1 - 1 - - - - - 28 - - - - ; - - - - - - - - 1 - 1 - - - - - 18 - - - - Return - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - Z - - - - - - - - 1 - 1 - - - - - 28 - - - - C - - - - - - - - 1 - 1 - - - - - 28 - - - - X - - - - - - - - 1 - 1 - - - - - 28 - - - - V - - - - - - - - 1 - 1 - - - - - 28 - - - - M - - - - - - - - 1 - 1 - - - - - 28 - - - - < - - - - - - - - 1 - 1 - - - - - 28 - - - - N - - - - - - - - 1 - 1 - - - - - 28 - - - - B - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - true - - - false - - - - - - - - 1 - 1 - - - - - 28 - - - - + - - - - - - - - 1 - 1 - - - - - 28 - - - - > - - - - - - - - 1 - 1 - - - - - 28 - - - - # - - - - - - - - 1 - 1 - - - - - 28 - - - - ] - - - - - - - - 1 - 1 - - - - - 28 - - - - $ - - - - - - - - 1 - 1 - - - - - 28 - - - - [ - - - - - - - - 1 - 1 - - - - - 28 - - - - ^ - - - - - - - - 1 - 1 - - - - - 28 - - - - % - - - - - - - - 1 - 1 - - - - - 18 - - - - Space - - - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - - - - 1 - 1 - - - - - 18 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 18 - - - - Enter - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 1 - 1 - - - - - 28 - - - - 6 - - - - - - - - 1 - 1 - - - - - 28 - - - - 4 - - - - - - - - 1 - 1 - - - - - 28 - - - - 9 - - - - - - - - 1 - 1 - - - - - 28 - - - - 5 - - - - - - - - 1 - 1 - - - - - 18 - - - - OK - - - - - - - - 1 - 1 - - - - - 28 - - - - 7 - - - - - - - - 1 - 1 - - - - - 28 - - - - 8 - - - - - - - - 1 - 1 - - - - - 28 - - - - 2 - - - - - - - - 1 - 1 - - - - - 28 - - - - 1 - - - - - - - - 1 - 1 - - - - - 28 - - - - 0 - - - - - - - - 1 - 1 - - - - - 28 - - - - 3 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - - - - - - - - - button_1 - button_2 - button_3 - button_4 - button_5 - button_6 - button_7 - button_8 - button_9 - button_0 - button_minus - button_backspace - button_q - button_w - button_e - button_r - button_t - button_y - button_u - button_i - button_o - button_p - button_slash - button_return - button_a - button_s - button_d - button_f - button_g - button_h - button_j - button_k - button_l - button_colon - button_apostrophe - button_z - button_x - button_c - button_v - button_b - button_n - button_m - button_comma - button_dot - button_question - button_exclamation - button_ok - button_shift - button_space - button_hash - button_left_bracket - button_right_bracket - button_dollar - button_percent - button_circumflex - button_ampersand - button_asterisk - button_left_parenthesis - button_right_parenthesis - button_underscore - button_backspace_shift - button_q_shift - button_w_shift - button_e_shift - button_r_shift - button_t_shift - button_y_shift - button_u_shift - button_i_shift - button_o_shift - button_p_shift - button_at - button_return_shift - button_a_shift - button_s_shift - button_d_shift - button_f_shift - button_g_shift - button_h_shift - button_j_shift - button_k_shift - button_l_shift - button_semicolon - button_quotation - button_z_shift - button_x_shift - button_c_shift - button_v_shift - button_b_shift - button_n_shift - button_m_shift - button_less_than - button_greater_than - button_plus - button_equal - button_ok_shift - button_shift_shift - button_space_shift - button_1_num - button_2_num - button_3_num - button_backspace_num - button_4_num - button_5_num - button_6_num - button_ok_num - button_7_num - button_8_num - button_9_num - button_0_num - - - - - - diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp deleted file mode 100644 index 34d3feb55..000000000 --- a/src/yuzu/applets/web_browser.cpp +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifdef YUZU_USE_QT_WEB_ENGINE -#include - -#include -#include -#include -#include -#include -#endif - -#include "common/fs/path_util.h" -#include "core/core.h" -#include "core/frontend/input_interpreter.h" -#include "input_common/keyboard.h" -#include "input_common/main.h" -#include "yuzu/applets/web_browser.h" -#include "yuzu/applets/web_browser_scripts.h" -#include "yuzu/main.h" -#include "yuzu/util/url_request_interceptor.h" - -#ifdef YUZU_USE_QT_WEB_ENGINE - -namespace { - -constexpr int HIDButtonToKey(HIDButton button) { - switch (button) { - case HIDButton::DLeft: - case HIDButton::LStickLeft: - return Qt::Key_Left; - case HIDButton::DUp: - case HIDButton::LStickUp: - return Qt::Key_Up; - case HIDButton::DRight: - case HIDButton::LStickRight: - return Qt::Key_Right; - case HIDButton::DDown: - case HIDButton::LStickDown: - return Qt::Key_Down; - default: - return 0; - } -} - -} // Anonymous namespace - -QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, - InputCommon::InputSubsystem* input_subsystem_) - : QWebEngineView(parent), input_subsystem{input_subsystem_}, - url_interceptor(std::make_unique()), - input_interpreter(std::make_unique(system)), - default_profile{QWebEngineProfile::defaultProfile()}, - global_settings{QWebEngineSettings::globalSettings()} { - QWebEngineScript gamepad; - QWebEngineScript window_nx; - - gamepad.setName(QStringLiteral("gamepad_script.js")); - window_nx.setName(QStringLiteral("window_nx_script.js")); - - gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); - window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); - - gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); - window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); - - gamepad.setWorldId(QWebEngineScript::MainWorld); - window_nx.setWorldId(QWebEngineScript::MainWorld); - - gamepad.setRunsOnSubFrames(true); - window_nx.setRunsOnSubFrames(true); - - default_profile->scripts()->insert(gamepad); - default_profile->scripts()->insert(window_nx); - - default_profile->setRequestInterceptor(url_interceptor.get()); - - global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); - global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); - global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); - global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); - global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); - global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); - - global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); - - connect( - page(), &QWebEnginePage::windowCloseRequested, page(), - [this] { - if (page()->url() == url_interceptor->GetRequestedURL()) { - SetFinished(true); - SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); - } - }, - Qt::QueuedConnection); -} - -QtNXWebEngineView::~QtNXWebEngineView() { - SetFinished(true); - StopInputThread(); -} - -void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url, - const std::string& additional_args) { - is_local = true; - - LoadExtractedFonts(); - SetUserAgent(UserAgent::WebApplet); - SetFinished(false); - SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); - SetLastURL("http://localhost/"); - StartInputThread(); - - load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() + - QString::fromStdString(additional_args))); -} - -void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url, - const std::string& additional_args) { - is_local = false; - - SetUserAgent(UserAgent::WebApplet); - SetFinished(false); - SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); - SetLastURL("http://localhost/"); - StartInputThread(); - - load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args))); -} - -void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { - const QString user_agent_str = [user_agent] { - switch (user_agent) { - case UserAgent::WebApplet: - default: - return QStringLiteral("WebApplet"); - case UserAgent::ShopN: - return QStringLiteral("ShopN"); - case UserAgent::LoginApplet: - return QStringLiteral("LoginApplet"); - case UserAgent::ShareApplet: - return QStringLiteral("ShareApplet"); - case UserAgent::LobbyApplet: - return QStringLiteral("LobbyApplet"); - case UserAgent::WifiWebAuthApplet: - return QStringLiteral("WifiWebAuthApplet"); - } - }(); - - QWebEngineProfile::defaultProfile()->setHttpUserAgent( - QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " - "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") - .arg(user_agent_str)); -} - -bool QtNXWebEngineView::IsFinished() const { - return finished; -} - -void QtNXWebEngineView::SetFinished(bool finished_) { - finished = finished_; -} - -Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { - return exit_reason; -} - -void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { - exit_reason = exit_reason_; -} - -const std::string& QtNXWebEngineView::GetLastURL() const { - return last_url; -} - -void QtNXWebEngineView::SetLastURL(std::string last_url_) { - last_url = std::move(last_url_); -} - -QString QtNXWebEngineView::GetCurrentURL() const { - return url_interceptor->GetRequestedURL().toString(); -} - -void QtNXWebEngineView::hide() { - SetFinished(true); - StopInputThread(); - - QWidget::hide(); -} - -void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { - if (is_local) { - input_subsystem->GetKeyboard()->PressKey(event->key()); - } -} - -void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { - if (is_local) { - input_subsystem->GetKeyboard()->ReleaseKey(event->key()); - } -} - -template -void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast(button)), - [&](const QVariant& variant) { - if (variant.toBool()) { - switch (button) { - case HIDButton::A: - SendMultipleKeyPressEvents(); - break; - case HIDButton::B: - SendKeyPressEvent(Qt::Key_B); - break; - case HIDButton::X: - SendKeyPressEvent(Qt::Key_X); - break; - case HIDButton::Y: - SendKeyPressEvent(Qt::Key_Y); - break; - default: - break; - } - } - }); - - page()->runJavaScript( - QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") - .arg(static_cast(button))); - } - }; - - (f(T), ...); -} - -template -void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonPressedOnce(button)) { - SendKeyPressEvent(HIDButtonToKey(button)); - } - }; - - (f(T), ...); -} - -template -void QtNXWebEngineView::HandleWindowKeyButtonHold() { - const auto f = [this](HIDButton button) { - if (input_interpreter->IsButtonHeld(button)) { - SendKeyPressEvent(HIDButtonToKey(button)); - } - }; - - (f(T), ...); -} - -void QtNXWebEngineView::SendKeyPressEvent(int key) { - if (key == 0) { - return; - } - - QCoreApplication::postEvent(focusProxy(), - new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); - QCoreApplication::postEvent(focusProxy(), - new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); -} - -void QtNXWebEngineView::StartInputThread() { - if (input_thread_running) { - return; - } - - input_thread_running = true; - input_thread = std::thread(&QtNXWebEngineView::InputThread, this); -} - -void QtNXWebEngineView::StopInputThread() { - if (is_local) { - QWidget::releaseKeyboard(); - } - - input_thread_running = false; - if (input_thread.joinable()) { - input_thread.join(); - } -} - -void QtNXWebEngineView::InputThread() { - // Wait for 1 second before allowing any inputs to be processed. - std::this_thread::sleep_for(std::chrono::seconds(1)); - - if (is_local) { - QWidget::grabKeyboard(); - } - - while (input_thread_running) { - input_interpreter->PollInput(); - - HandleWindowFooterButtonPressedOnce(); - - HandleWindowKeyButtonPressedOnce(); - - HandleWindowKeyButtonHold(); - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } -} - -void QtNXWebEngineView::LoadExtractedFonts() { - QWebEngineScript nx_font_css; - QWebEngineScript load_nx_font; - - auto fonts_dir_str = Common::FS::PathToUTF8String( - Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); - - std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); - - const auto fonts_dir = QString::fromStdString(fonts_dir_str); - - nx_font_css.setName(QStringLiteral("nx_font_css.js")); - load_nx_font.setName(QStringLiteral("load_nx_font.js")); - - nx_font_css.setSourceCode( - QString::fromStdString(NX_FONT_CSS) - .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) - .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) - .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) - .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) - .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); - load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); - - nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); - load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); - - nx_font_css.setWorldId(QWebEngineScript::MainWorld); - load_nx_font.setWorldId(QWebEngineScript::MainWorld); - - nx_font_css.setRunsOnSubFrames(true); - load_nx_font.setRunsOnSubFrames(true); - - default_profile->scripts()->insert(nx_font_css); - default_profile->scripts()->insert(load_nx_font); - - connect( - url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), - [this] { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); - }, - Qt::QueuedConnection); -} - -#endif - -QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { - connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, - &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, - &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserClosed, this, - &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); -} - -QtWebBrowser::~QtWebBrowser() = default; - -void QtWebBrowser::OpenLocalWebPage( - const std::string& local_url, std::function extract_romfs_callback_, - std::function callback_) const { - extract_romfs_callback = std::move(extract_romfs_callback_); - callback = std::move(callback_); - - const auto index = local_url.find('?'); - - if (index == std::string::npos) { - emit MainWindowOpenWebPage(local_url, "", true); - } else { - emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); - } -} - -void QtWebBrowser::OpenExternalWebPage( - const std::string& external_url, - std::function callback_) const { - callback = std::move(callback_); - - const auto index = external_url.find('?'); - - if (index == std::string::npos) { - emit MainWindowOpenWebPage(external_url, "", false); - } else { - emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), - false); - } -} - -void QtWebBrowser::MainWindowExtractOfflineRomFS() { - extract_romfs_callback(); -} - -void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, - std::string last_url) { - callback(exit_reason, last_url); -} diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h deleted file mode 100644 index 7ad07409f..000000000 --- a/src/yuzu/applets/web_browser.h +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include - -#ifdef YUZU_USE_QT_WEB_ENGINE -#include -#endif - -#include "core/frontend/applets/web_browser.h" - -enum class HIDButton : u8; - -class GMainWindow; -class InputInterpreter; -class UrlRequestInterceptor; - -namespace Core { -class System; -} - -namespace InputCommon { -class InputSubsystem; -} - -#ifdef YUZU_USE_QT_WEB_ENGINE - -enum class UserAgent { - WebApplet, - ShopN, - LoginApplet, - ShareApplet, - LobbyApplet, - WifiWebAuthApplet, -}; - -class QWebEngineProfile; -class QWebEngineSettings; - -class QtNXWebEngineView : public QWebEngineView { - Q_OBJECT - -public: - explicit QtNXWebEngineView(QWidget* parent, Core::System& system, - InputCommon::InputSubsystem* input_subsystem_); - ~QtNXWebEngineView() override; - - /** - * Loads a HTML document that exists locally. Cannot be used to load external websites. - * - * @param main_url The url to the file. - * @param additional_args Additional arguments appended to the main url. - */ - void LoadLocalWebPage(const std::string& main_url, const std::string& additional_args); - - /** - * Loads an external website. Cannot be used to load local urls. - * - * @param main_url The url to the website. - * @param additional_args Additional arguments appended to the main url. - */ - void LoadExternalWebPage(const std::string& main_url, const std::string& additional_args); - - /** - * Sets the background color of the web page. - * - * @param color The color to set. - */ - void SetBackgroundColor(QColor color); - - /** - * Sets the user agent of the web browser. - * - * @param user_agent The user agent enum. - */ - void SetUserAgent(UserAgent user_agent); - - [[nodiscard]] bool IsFinished() const; - void SetFinished(bool finished_); - - [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; - void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); - - [[nodiscard]] const std::string& GetLastURL() const; - void SetLastURL(std::string last_url_); - - /** - * This gets the current URL that has been requested by the webpage. - * This only applies to the main frame. Sub frames and other resources are ignored. - * - * @return Currently requested URL - */ - [[nodiscard]] QString GetCurrentURL() const; - -public slots: - void hide(); - -protected: - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - -private: - /** - * Handles button presses to execute functions assigned in yuzu_key_callbacks. - * yuzu_key_callbacks contains specialized functions for the buttons in the window footer - * that can be overriden by games to achieve desired functionality. - * - * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks - */ - template - void HandleWindowFooterButtonPressedOnce(); - - /** - * Handles button presses and converts them into keyboard input. - * This should only be used to convert D-Pad or Analog Stick input into arrow keys. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleWindowKeyButtonPressedOnce(); - - /** - * Handles button holds and converts them into keyboard input. - * This should only be used to convert D-Pad or Analog Stick input into arrow keys. - * - * @tparam HIDButton The list of buttons that can be converted into keyboard input. - */ - template - void HandleWindowKeyButtonHold(); - - /** - * Sends a key press event to QWebEngineView. - * - * @param key Qt key code. - */ - void SendKeyPressEvent(int key); - - /** - * Sends multiple key press events to QWebEngineView. - * - * @tparam int Qt key code. - */ - template - void SendMultipleKeyPressEvents() { - (SendKeyPressEvent(T), ...); - } - - void StartInputThread(); - void StopInputThread(); - - /// The thread where input is being polled and processed. - void InputThread(); - - /// Loads the extracted fonts using JavaScript. - void LoadExtractedFonts(); - - InputCommon::InputSubsystem* input_subsystem; - - std::unique_ptr url_interceptor; - - std::unique_ptr input_interpreter; - - std::thread input_thread; - - std::atomic input_thread_running{}; - - std::atomic finished{}; - - Service::AM::Applets::WebExitReason exit_reason{ - Service::AM::Applets::WebExitReason::EndButtonPressed}; - - std::string last_url{"http://localhost/"}; - - bool is_local{}; - - QWebEngineProfile* default_profile; - QWebEngineSettings* global_settings; -}; - -#endif - -class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { - Q_OBJECT - -public: - explicit QtWebBrowser(GMainWindow& parent); - ~QtWebBrowser() override; - - void OpenLocalWebPage(const std::string& local_url, - std::function extract_romfs_callback_, - std::function - callback_) const override; - - void OpenExternalWebPage(const std::string& external_url, - std::function - callback_) const override; - -signals: - void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args, - bool is_local) const; - -private: - void MainWindowExtractOfflineRomFS(); - - void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, - std::string last_url); - - mutable std::function extract_romfs_callback; - - mutable std::function callback; -}; diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h deleted file mode 100644 index 992837a85..000000000 --- a/src/yuzu/applets/web_browser_scripts.h +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -constexpr char NX_FONT_CSS[] = R"( -(function() { - css = document.createElement('style'); - css.type = 'text/css'; - css.id = 'nx_font'; - css.innerText = ` -/* FontStandard */ -@font-face { - font-family: 'FontStandard'; - src: url('%1') format('truetype'); -} - -/* FontChineseSimplified */ -@font-face { - font-family: 'FontChineseSimplified'; - src: url('%2') format('truetype'); -} - -/* FontExtendedChineseSimplified */ -@font-face { - font-family: 'FontExtendedChineseSimplified'; - src: url('%3') format('truetype'); -} - -/* FontChineseTraditional */ -@font-face { - font-family: 'FontChineseTraditional'; - src: url('%4') format('truetype'); -} - -/* FontKorean */ -@font-face { - font-family: 'FontKorean'; - src: url('%5') format('truetype'); -} - -/* FontNintendoExtended */ -@font-face { - font-family: 'NintendoExt003'; - src: url('%6') format('truetype'); -} - -/* FontNintendoExtended2 */ -@font-face { - font-family: 'NintendoExt003'; - src: url('%7') format('truetype'); -} -`; - - document.head.appendChild(css); -})(); -)"; - -constexpr char LOAD_NX_FONT[] = R"( -(function() { - var elements = document.querySelectorAll("*"); - - for (var i = 0; i < elements.length; i++) { - var style = window.getComputedStyle(elements[i], null); - if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || - style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { - elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; - } else { - elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; - } - } -})(); -)"; - -constexpr char GAMEPAD_SCRIPT[] = R"( -window.addEventListener("gamepadconnected", function(e) { - console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", - e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); -}); - -window.addEventListener("gamepaddisconnected", function(e) { - console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); -}); -)"; - -constexpr char WINDOW_NX_SCRIPT[] = R"( -var end_applet = false; -var yuzu_key_callbacks = []; - -(function() { - class WindowNX { - constructor() { - yuzu_key_callbacks[1] = function() { window.history.back(); }; - yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; - } - - addEventListener(type, listener, options) { - console.log("nx.addEventListener called, type=%s", type); - - window.addEventListener(type, listener, options); - } - - endApplet() { - console.log("nx.endApplet called"); - - end_applet = true; - } - - playSystemSe(system_se) { - console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); - } - - sendMessage(message) { - console.log("nx.sendMessage is not implemented, message=%s", message); - } - - setCursorScrollSpeed(scroll_speed) { - console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); - } - } - - class WindowNXFooter { - setAssign(key, label, func, option) { - console.log("nx.footer.setAssign called, key=%s", key); - - switch (key) { - case "A": - yuzu_key_callbacks[0] = func; - break; - case "B": - yuzu_key_callbacks[1] = func; - break; - case "X": - yuzu_key_callbacks[2] = func; - break; - case "Y": - yuzu_key_callbacks[3] = func; - break; - case "L": - yuzu_key_callbacks[6] = func; - break; - case "R": - yuzu_key_callbacks[7] = func; - break; - } - } - - setFixed(kind) { - console.log("nx.footer.setFixed is not implemented, kind=%s", kind); - } - - unsetAssign(key) { - console.log("nx.footer.unsetAssign called, key=%s", key); - - switch (key) { - case "A": - yuzu_key_callbacks[0] = function() {}; - break; - case "B": - yuzu_key_callbacks[1] = function() {}; - break; - case "X": - yuzu_key_callbacks[2] = function() {}; - break; - case "Y": - yuzu_key_callbacks[3] = function() {}; - break; - case "L": - yuzu_key_callbacks[6] = function() {}; - break; - case "R": - yuzu_key_callbacks[7] = function() {}; - break; - } - } - } - - class WindowNXPlayReport { - incrementCounter(counter_id) { - console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); - } - - setCounterSetIdentifier(counter_id) { - console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); - } - } - - window.nx = new WindowNX(); - window.nx.footer = new WindowNXFooter(); - window.nx.playReport = new WindowNXPlayReport(); -})(); -)"; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fbd5001e9..b7fd33ae7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,11 +11,11 @@ #endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. -#include "applets/controller.h" -#include "applets/error.h" -#include "applets/profile_select.h" -#include "applets/software_keyboard.h" -#include "applets/web_browser.h" +#include "applets/qt_controller.h" +#include "applets/qt_error.h" +#include "applets/qt_profile_select.h" +#include "applets/qt_software_keyboard.h" +#include "applets/qt_web_browser.h" #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" -- cgit v1.2.3