summaryrefslogtreecommitdiff
path: root/src/citron/applets
diff options
context:
space:
mode:
authorZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
committerZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
commit9427e27e24a7135880ee2881c3c44988e174b41a (patch)
tree83f0062a35be144f6b162eaa823c5b3c7620146e /src/citron/applets
parentb35ae725d20960411e8588b11c12a2d55f86c9d0 (diff)
chore: update project branding to citron
Diffstat (limited to 'src/citron/applets')
-rw-r--r--src/citron/applets/qt_amiibo_settings.cpp274
-rw-r--r--src/citron/applets/qt_amiibo_settings.h85
-rw-r--r--src/citron/applets/qt_amiibo_settings.ui494
-rw-r--r--src/citron/applets/qt_controller.cpp778
-rw-r--r--src/citron/applets/qt_controller.h183
-rw-r--r--src/citron/applets/qt_controller.ui2699
-rw-r--r--src/citron/applets/qt_error.cpp68
-rw-r--r--src/citron/applets/qt_error.h34
-rw-r--r--src/citron/applets/qt_profile_select.cpp260
-rw-r--r--src/citron/applets/qt_profile_select.h87
-rw-r--r--src/citron/applets/qt_software_keyboard.cpp1674
-rw-r--r--src/citron/applets/qt_software_keyboard.h287
-rw-r--r--src/citron/applets/qt_software_keyboard.ui3541
-rw-r--r--src/citron/applets/qt_web_browser.cpp449
-rw-r--r--src/citron/applets/qt_web_browser.h220
-rw-r--r--src/citron/applets/qt_web_browser_scripts.h198
16 files changed, 11331 insertions, 0 deletions
diff --git a/src/citron/applets/qt_amiibo_settings.cpp b/src/citron/applets/qt_amiibo_settings.cpp
new file mode 100644
index 000000000..b91796dde
--- /dev/null
+++ b/src/citron/applets/qt_amiibo_settings.cpp
@@ -0,0 +1,274 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <thread>
+#include <fmt/format.h>
+#include <nlohmann/json.hpp>
+
+#include "common/assert.h"
+#include "common/string_util.h"
+#include "core/hle/service/nfc/common/device.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "input_common/drivers/virtual_amiibo.h"
+#include "input_common/main.h"
+#include "ui_qt_amiibo_settings.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/web_backend.h"
+#endif
+#include "yuzu/applets/qt_amiibo_settings.h"
+#include "yuzu/main.h"
+
+QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
+ Core::Frontend::CabinetParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_,
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device_)
+ : QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()),
+ input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)},
+ parameters(std::move(parameters_)) {
+ ui->setupUi(this);
+
+ LoadInfo();
+
+ resize(0, 0);
+}
+
+QtAmiiboSettingsDialog::~QtAmiiboSettingsDialog() = default;
+
+int QtAmiiboSettingsDialog::exec() {
+ if (!is_initialized) {
+ return QDialog::Rejected;
+ }
+ return QDialog::exec();
+}
+
+std::string QtAmiiboSettingsDialog::GetName() const {
+ return ui->amiiboCustomNameValue->text().toStdString();
+}
+
+void QtAmiiboSettingsDialog::LoadInfo() {
+ if (input_subsystem->GetVirtualAmiibo()->ReloadAmiibo() !=
+ InputCommon::VirtualAmiibo::Info::Success) {
+ return;
+ }
+
+ if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound &&
+ nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) {
+ return;
+ }
+ nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All);
+
+ LoadAmiiboInfo();
+ LoadAmiiboData();
+ LoadAmiiboGameInfo();
+
+ ui->amiiboDirectoryValue->setText(
+ QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath()));
+
+ SetSettingsDescription();
+ is_initialized = true;
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboInfo() {
+ Service::NFP::ModelInfo model_info{};
+ const auto model_result = nfp_device->GetModelInfo(model_info);
+
+ if (model_result.IsFailure()) {
+ ui->amiiboImageLabel->setVisible(false);
+ ui->amiiboInfoGroup->setVisible(false);
+ return;
+ }
+
+ const auto amiibo_id =
+ fmt::format("{:04x}{:02x}{:02x}{:04x}{:02x}02", Common::swap16(model_info.character_id),
+ model_info.character_variant, model_info.amiibo_type, model_info.model_number,
+ model_info.series);
+
+ LOG_DEBUG(Frontend, "Loading amiibo id {}", amiibo_id);
+ // Note: This function is not being used until we host the images on our server
+ // LoadAmiiboApiInfo(amiibo_id);
+ ui->amiiboImageLabel->setVisible(false);
+ ui->amiiboInfoGroup->setVisible(false);
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboApiInfo(std::string_view amiibo_id) {
+#ifdef ENABLE_WEB_SERVICE
+ // TODO: Host this data on our website
+ WebService::Client client{"https://amiiboapi.com", {}, {}};
+ WebService::Client image_client{"https://raw.githubusercontent.com", {}, {}};
+ const auto url_path = fmt::format("/api/amiibo/?id={}", amiibo_id);
+
+ const auto amiibo_json = client.GetJson(url_path, true).returned_data;
+ if (amiibo_json.empty()) {
+ ui->amiiboImageLabel->setVisible(false);
+ ui->amiiboInfoGroup->setVisible(false);
+ return;
+ }
+
+ std::string amiibo_series{};
+ std::string amiibo_name{};
+ std::string amiibo_image_url{};
+ std::string amiibo_type{};
+
+ const auto parsed_amiibo_json_json = nlohmann::json::parse(amiibo_json).at("amiibo");
+ parsed_amiibo_json_json.at("amiiboSeries").get_to(amiibo_series);
+ parsed_amiibo_json_json.at("name").get_to(amiibo_name);
+ parsed_amiibo_json_json.at("image").get_to(amiibo_image_url);
+ parsed_amiibo_json_json.at("type").get_to(amiibo_type);
+
+ ui->amiiboSeriesValue->setText(QString::fromStdString(amiibo_series));
+ ui->amiiboNameValue->setText(QString::fromStdString(amiibo_name));
+ ui->amiiboTypeValue->setText(QString::fromStdString(amiibo_type));
+
+ if (amiibo_image_url.size() < 34) {
+ ui->amiiboImageLabel->setVisible(false);
+ }
+
+ const auto image_url_path = amiibo_image_url.substr(34, amiibo_image_url.size() - 34);
+ const auto image_data = image_client.GetImage(image_url_path, true).returned_data;
+
+ if (image_data.empty()) {
+ ui->amiiboImageLabel->setVisible(false);
+ }
+
+ QPixmap pixmap;
+ pixmap.loadFromData(reinterpret_cast<const u8*>(image_data.data()),
+ static_cast<uint>(image_data.size()));
+ pixmap = pixmap.scaled(250, 350, Qt::AspectRatioMode::KeepAspectRatio,
+ Qt::TransformationMode::SmoothTransformation);
+ ui->amiiboImageLabel->setPixmap(pixmap);
+#endif
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboData() {
+ Service::NFP::RegisterInfo register_info{};
+ Service::NFP::CommonInfo common_info{};
+ const auto register_result = nfp_device->GetRegisterInfo(register_info);
+ const auto common_result = nfp_device->GetCommonInfo(common_info);
+
+ if (register_result.IsFailure()) {
+ ui->creationDateValue->setDisabled(true);
+ ui->modificationDateValue->setDisabled(true);
+ ui->amiiboCustomNameValue->setReadOnly(false);
+ ui->amiiboOwnerValue->setReadOnly(false);
+ return;
+ }
+
+ if (parameters.mode == Service::NFP::CabinetMode::StartNicknameAndOwnerSettings) {
+ ui->creationDateValue->setDisabled(true);
+ ui->modificationDateValue->setDisabled(true);
+ }
+
+ const auto amiibo_name = std::string(register_info.amiibo_name.data());
+ const auto owner_name =
+ Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
+ const auto creation_date =
+ QDate(register_info.creation_date.year, register_info.creation_date.month,
+ register_info.creation_date.day);
+
+ ui->amiiboCustomNameValue->setText(QString::fromStdString(amiibo_name));
+ ui->amiiboOwnerValue->setText(QString::fromStdString(owner_name));
+ ui->amiiboCustomNameValue->setReadOnly(true);
+ ui->amiiboOwnerValue->setReadOnly(true);
+ ui->creationDateValue->setDate(creation_date);
+
+ if (common_result.IsFailure()) {
+ ui->modificationDateValue->setDisabled(true);
+ return;
+ }
+
+ const auto modification_date =
+ QDate(common_info.last_write_date.year, common_info.last_write_date.month,
+ common_info.last_write_date.day);
+ ui->modificationDateValue->setDate(modification_date);
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboGameInfo() {
+ u32 application_area_id{};
+ const auto application_result = nfp_device->GetApplicationAreaId(application_area_id);
+
+ if (application_result.IsFailure()) {
+ ui->gameIdValue->setVisible(false);
+ ui->gameIdLabel->setText(tr("No game data present"));
+ return;
+ }
+
+ SetGameDataName(application_area_id);
+}
+
+void QtAmiiboSettingsDialog::SetGameDataName(u32 application_area_id) {
+ static constexpr std::array<std::pair<u32, const char*>, 12> game_name_list = {
+ // 3ds, wii u
+ std::pair<u32, const char*>{0x10110E00, "Super Smash Bros (3DS/WiiU)"},
+ {0x00132600, "Mario & Luigi: Paper Jam"},
+ {0x0014F000, "Animal Crossing: Happy Home Designer"},
+ {0x00152600, "Chibi-Robo!: Zip Lash"},
+ {0x10161f00, "Mario Party 10"},
+ {0x1019C800, "The Legend of Zelda: Twilight Princess HD"},
+ // switch
+ {0x10162B00, "Splatoon 2"},
+ {0x1016e100, "Shovel Knight: Treasure Trove"},
+ {0x1019C800, "The Legend of Zelda: Breath of the Wild"},
+ {0x34F80200, "Super Smash Bros. Ultimate"},
+ {0x38600500, "Splatoon 3"},
+ {0x3B440400, "The Legend of Zelda: Link's Awakening"},
+ };
+
+ for (const auto& [game_id, game_name] : game_name_list) {
+ if (application_area_id == game_id) {
+ ui->gameIdValue->setText(QString::fromStdString(game_name));
+ return;
+ }
+ }
+
+ const auto application_area_string = fmt::format("{:016x}", application_area_id);
+ ui->gameIdValue->setText(QString::fromStdString(application_area_string));
+}
+
+void QtAmiiboSettingsDialog::SetSettingsDescription() {
+ switch (parameters.mode) {
+ case Service::NFP::CabinetMode::StartFormatter:
+ ui->cabinetActionDescriptionLabel->setText(
+ tr("The following amiibo data will be formatted:"));
+ break;
+ case Service::NFP::CabinetMode::StartGameDataEraser:
+ ui->cabinetActionDescriptionLabel->setText(tr("The following game data will removed:"));
+ break;
+ case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
+ ui->cabinetActionDescriptionLabel->setText(tr("Set nickname and owner:"));
+ break;
+ case Service::NFP::CabinetMode::StartRestorer:
+ ui->cabinetActionDescriptionLabel->setText(tr("Do you wish to restore this amiibo?"));
+ break;
+ }
+}
+
+QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) {
+ connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent,
+ &GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
+ connect(this, &QtAmiiboSettings::MainWindowRequestExit, &parent,
+ &GMainWindow::AmiiboSettingsRequestExit, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::AmiiboSettingsFinished, this,
+ &QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection);
+}
+
+QtAmiiboSettings::~QtAmiiboSettings() = default;
+
+void QtAmiiboSettings::Close() const {
+ callback = {};
+ emit MainWindowRequestExit();
+}
+
+void QtAmiiboSettings::ShowCabinetApplet(
+ const Core::Frontend::CabinetCallback& callback_,
+ const Core::Frontend::CabinetParameters& parameters,
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const {
+ callback = std::move(callback_);
+ emit MainWindowShowAmiiboSettings(parameters, nfp_device);
+}
+
+void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) {
+ if (callback) {
+ callback(is_success, name);
+ }
+}
diff --git a/src/citron/applets/qt_amiibo_settings.h b/src/citron/applets/qt_amiibo_settings.h
new file mode 100644
index 000000000..3833cf6f2
--- /dev/null
+++ b/src/citron/applets/qt_amiibo_settings.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#include "core/frontend/applets/cabinet.h"
+
+class GMainWindow;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class QtAmiiboSettingsDialog;
+}
+
+namespace Service::NFC {
+class NfcDevice;
+} // namespace Service::NFC
+
+class QtAmiiboSettingsDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_,
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device_);
+ ~QtAmiiboSettingsDialog() override;
+
+ int exec() override;
+
+ std::string GetName() const;
+
+private:
+ void LoadInfo();
+ void LoadAmiiboInfo();
+ void LoadAmiiboApiInfo(std::string_view amiibo_id);
+ void LoadAmiiboData();
+ void LoadAmiiboGameInfo();
+ void SetGameDataName(u32 application_area_id);
+ void SetSettingsDescription();
+
+ std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui;
+
+ InputCommon::InputSubsystem* input_subsystem;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device;
+
+ // Parameters sent in from the backend HLE applet.
+ Core::Frontend::CabinetParameters parameters;
+
+ // If false amiibo settings failed to load
+ bool is_initialized{};
+};
+
+class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApplet {
+ Q_OBJECT
+
+public:
+ explicit QtAmiiboSettings(GMainWindow& parent);
+ ~QtAmiiboSettings() override;
+
+ void Close() const override;
+ void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_,
+ const Core::Frontend::CabinetParameters& parameters,
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override;
+
+signals:
+ void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters,
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const;
+ void MainWindowRequestExit() const;
+
+private:
+ void MainWindowFinished(bool is_success, const std::string& name);
+
+ mutable Core::Frontend::CabinetCallback callback;
+};
diff --git a/src/citron/applets/qt_amiibo_settings.ui b/src/citron/applets/qt_amiibo_settings.ui
new file mode 100644
index 000000000..f377a6e61
--- /dev/null
+++ b/src/citron/applets/qt_amiibo_settings.ui
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtAmiiboSettingsDialog</class>
+ <widget class="QDialog" name="QtAmiiboSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>839</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Amiibo Settings</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_1" stretch="0,3,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="leftMargin">
+ <number>20</number>
+ </property>
+ <property name="topMargin">
+ <number>15</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="cabinetActionDescriptionLabel">
+ <property name="font">
+ <font>
+ <pointsize>12</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="middleControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>20</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="rightMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="amiiboImageLabel">
+ <property name="minimumSize">
+ <size>
+ <width>250</width>
+ <height>350</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>236</width>
+ <height>350</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="amiiboInfoGroup">
+ <property name="title">
+ <string>Amiibo Info</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="0" column="0">
+ <widget class="QLabel" name="amiiboSeriesLabel">
+ <property name="text">
+ <string>Series</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="amiiboSeriesValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="amiiboTypeLabel">
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="amiiboTypeValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="amiiboNameLabel">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="amiiboNameValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="amiiboDataGroup">
+ <property name="title">
+ <string>Amiibo Data</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="amiiboCustomNameLabel">
+ <property name="text">
+ <string>Custom Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="amiiboCustomNameValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maxLength">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="amiiboOwnerLabel">
+ <property name="text">
+ <string>Owner</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="amiiboOwnerValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maxLength">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="creationDateLabel">
+ <property name="text">
+ <string>Creation Date</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QDateTimeEdit" name="creationDateValue">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="minimumDate">
+ <date>
+ <year>1970</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
+ <property name="displayFormat">
+ <string>dd/MM/yyyy</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="modificationDateLabel">
+ <property name="text">
+ <string>Modification Date</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QDateTimeEdit" name="modificationDateValue">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="minimumDate">
+ <date>
+ <year>1970</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
+ <property name="displayFormat">
+ <string>dd/MM/yyyy </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="gameDataGroup">
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Game Data</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="gameIdLabel">
+ <property name="text">
+ <string>Game Id</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="gameIdValue">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="MountAmiiboGroup">
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Mount Amiibo</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="3">
+ <widget class="QToolButton" name="amiiboDirectoryButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="amiiboDirectoryLabel">
+ <property name="text">
+ <string>File Path</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="amiiboDirectoryValue"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="rightMargin">
+ <number>20</number>
+ </property>
+ <property name="bottomMargin">
+ <number>8</number>
+ </property>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtAmiiboSettingsDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QtAmiiboSettingsDialog</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/citron/applets/qt_controller.cpp b/src/citron/applets/qt_controller.cpp
new file mode 100644
index 000000000..48ce860ad
--- /dev/null
+++ b/src/citron/applets/qt_controller.cpp
@@ -0,0 +1,778 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <thread>
+
+#include "common/assert.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/service/sm/sm.h"
+#include "hid_core/frontend/emulated_controller.h"
+#include "hid_core/hid_core.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad.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"
+#include "yuzu/util/controller_navigation.h"
+
+namespace {
+
+void UpdateController(Core::HID::EmulatedController* controller,
+ Core::HID::NpadStyleIndex controller_type, bool connected) {
+ if (controller->IsConnected(true)) {
+ controller->Disconnect();
+ }
+ controller->SetNpadStyleIndex(controller_type);
+ if (connected) {
+ controller->Connect(true);
+ }
+}
+
+// Returns true if the given controller type is compatible with the given parameters.
+bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type,
+ Core::Frontend::ControllerParameters parameters) {
+ switch (controller_type) {
+ case Core::HID::NpadStyleIndex::Fullkey:
+ return parameters.allow_pro_controller;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ return parameters.allow_dual_joycons;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return parameters.allow_left_joycon;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return parameters.allow_right_joycon;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return parameters.enable_single_mode && parameters.allow_handheld;
+ case Core::HID::NpadStyleIndex::GameCube:
+ return parameters.allow_gamecube_controller;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+QtControllerSelectorDialog::QtControllerSelectorDialog(
+ QWidget* parent, Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
+ : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
+ parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
+ input_profiles(std::make_unique<InputProfiles>()), system{system_} {
+ 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,
+ };
+
+ ui->labelError->setVisible(false);
+
+ // 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();
+
+ controller_navigation = new ControllerNavigation(system.HIDCore(), this);
+
+ 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) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
+ player_groupboxes[i + 1]->isChecked();
+
+ // Ensures that connecting a controller changes the number of players
+ if (connected_controller_checkboxes[i]->isChecked() != checked) {
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
+ }
+ });
+ connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked &&
+ i < connected_controller_checkboxes.size() - 1 &&
+ connected_controller_checkboxes[i + 1]->isChecked();
+
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
+ });
+
+ connect(emulated_controllers[i], qOverload<int>(&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<int>(&QComboBox::currentIndexChanged),
+ [this, i](int index) {
+ UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
+ Core::HID::NpadStyleIndex::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);
+
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [this](Qt::Key key) {
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(this, event);
+ });
+
+ // 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() {
+ controller_navigation->UnloadController();
+ system.HIDCore().DisableAllControllerConfiguration();
+}
+
+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::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
+
+ Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+ Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
+}
+
+void QtControllerSelectorDialog::LoadConfiguration() {
+ system.HIDCore().EnableAllControllerConfiguration();
+
+ const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
+ const auto connected =
+ controller->IsConnected(true) || (index == 0 && handheld->IsConnected(true));
+ player_groupboxes[index]->setChecked(connected);
+ connected_controller_checkboxes[index]->setChecked(connected);
+ emulated_controllers[index]->setCurrentIndex(
+ GetIndexFromControllerType(controller->GetNpadStyleIndex(true), index));
+ }
+
+ UpdateDockedState(handheld->IsConnected(true));
+
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+ ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
+}
+
+void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
+ ConfigureVibration dialog(this, system.HIDCore());
+
+ 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(), system);
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+}
+
+void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [](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;
+
+ if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
+ // Display error message when trying to validate using "Enter" and "OK" button is disabled
+ ui->labelError->setVisible(true);
+ return;
+ } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
+ // Remove a player if possible
+ connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
+ return;
+ } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
+ // Add a player, if possible
+ ui->labelError->setVisible(false);
+ connected_controller_checkboxes[num_connected_players]->setChecked(true);
+ return;
+ }
+ QDialog::keyPressEvent(evt);
+}
+
+bool QtControllerSelectorDialog::CheckIfParametersMet() {
+ // Here, we check and validate the current configuration against all applicable parameters.
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [](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->button(QDialogButtonBox::Ok)->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->button(QDialogButtonBox::Ok)->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) {
+ const auto npad_style_set = system.HIDCore().GetSupportedStyleTag();
+ auto& pairs = index_controller_type_pairs[player_index];
+
+ pairs.clear();
+ emulated_controllers[player_index]->clear();
+
+ const auto add_item = [&](Core::HID::NpadStyleIndex controller_type,
+ const QString& controller_name) {
+ pairs.emplace_back(emulated_controllers[player_index]->count(), controller_type);
+ emulated_controllers[player_index]->addItem(controller_name);
+ };
+
+ if (npad_style_set.fullkey == 1) {
+ add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
+ }
+
+ if (npad_style_set.joycon_dual == 1) {
+ add_item(Core::HID::NpadStyleIndex::JoyconDual, tr("Dual Joycons"));
+ }
+
+ if (npad_style_set.joycon_left == 1) {
+ add_item(Core::HID::NpadStyleIndex::JoyconLeft, tr("Left Joycon"));
+ }
+
+ if (npad_style_set.joycon_right == 1) {
+ add_item(Core::HID::NpadStyleIndex::JoyconRight, tr("Right Joycon"));
+ }
+
+ if (player_index == 0 && npad_style_set.handheld == 1) {
+ add_item(Core::HID::NpadStyleIndex::Handheld, tr("Handheld"));
+ }
+
+ if (npad_style_set.gamecube == 1) {
+ add_item(Core::HID::NpadStyleIndex::GameCube, tr("GameCube Controller"));
+ }
+
+ // Disable all unsupported controllers
+ if (!Settings::values.enable_all_controllers) {
+ return;
+ }
+
+ if (npad_style_set.palma == 1) {
+ add_item(Core::HID::NpadStyleIndex::Pokeball, tr("Poke Ball Plus"));
+ }
+
+ if (npad_style_set.lark == 1) {
+ add_item(Core::HID::NpadStyleIndex::NES, tr("NES Controller"));
+ }
+
+ if (npad_style_set.lucia == 1) {
+ add_item(Core::HID::NpadStyleIndex::SNES, tr("SNES Controller"));
+ }
+
+ if (npad_style_set.lagoon == 1) {
+ add_item(Core::HID::NpadStyleIndex::N64, tr("N64 Controller"));
+ }
+
+ if (npad_style_set.lager == 1) {
+ add_item(Core::HID::NpadStyleIndex::SegaGenesis, tr("Sega Genesis"));
+ }
+}
+
+Core::HID::NpadStyleIndex 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 Core::HID::NpadStyleIndex::Fullkey;
+ }
+
+ return it->second;
+}
+
+int QtControllerSelectorDialog::GetIndexFromControllerType(Core::HID::NpadStyleIndex 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 Core::HID::NpadStyleIndex::Fullkey:
+ case Core::HID::NpadStyleIndex::GameCube:
+ return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
+ case Core::HID::NpadStyleIndex::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* controller = system.HIDCore().GetEmulatedControllerByIndex(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 != Core::HID::NpadStyleIndex::Handheld;
+
+ if (controller->GetNpadStyleIndex(true) == controller_type &&
+ controller->IsConnected(true) == player_connected) {
+ return;
+ }
+
+ // Disconnect the controller first.
+ UpdateController(controller, controller_type, false);
+
+ // Handheld
+ if (player_index == 0) {
+ if (controller_type == Core::HID::NpadStyleIndex::Handheld) {
+ auto* handheld =
+ system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ UpdateController(handheld, Core::HID::NpadStyleIndex::Handheld,
+ player_groupboxes[player_index]->isChecked());
+ }
+ }
+
+ UpdateController(controller, controller_type, player_connected);
+}
+
+void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked() ||
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
+ player_index) == Core::HID::NpadStyleIndex::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;
+ }
+
+ const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
+ const auto led_pattern = controller->GetLedPattern();
+ led_patterns_boxes[player_index][0]->setChecked(led_pattern.position1);
+ led_patterns_boxes[player_index][1]->setChecked(led_pattern.position2);
+ led_patterns_boxes[player_index][2]->setChecked(led_pattern.position3);
+ led_patterns_boxes[player_index][3]->setChecked(led_pattern.position4);
+}
+
+void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
+ if (!parameters.enable_border_color ||
+ player_index >= static_cast<std::size_t>(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<std::size_t>(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::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
+
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
+ }
+}
+
+void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current) {
+ connected_controller_checkboxes[player_index]->setChecked(checked);
+ // Hide eventual error message about number of controllers
+ ui->labelError->setVisible(false);
+
+ if (checked) {
+ // Check all previous buttons when checked
+ if (player_index > 0) {
+ PropagatePlayerNumberChanged(player_index - 1, checked);
+ }
+ } else {
+ // Unchecked all following buttons when unchecked
+ if (player_index < connected_controller_checkboxes.size() - 1) {
+ PropagatePlayerNumberChanged(player_index + 1, checked);
+ }
+ }
+
+ if (reconnect_current) {
+ connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
+ }
+}
+
+void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ switch (max_supported_players) {
+ case 0:
+ default:
+ ASSERT(false);
+ 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) {
+ auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
+ // Disconnect any unsupported players here and disable or hide them if applicable.
+ UpdateController(controller, controller->GetNpadStyleIndex(true), 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(this, &QtControllerSelector::MainWindowRequestExit, &parent,
+ &GMainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
+ &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
+}
+
+QtControllerSelector::~QtControllerSelector() = default;
+
+void QtControllerSelector::Close() const {
+ callback = {};
+ emit MainWindowRequestExit();
+}
+
+void QtControllerSelector::ReconfigureControllers(
+ ReconfigureCallback callback_, const Core::Frontend::ControllerParameters& parameters) const {
+ callback = std::move(callback_);
+ emit MainWindowReconfigureControllers(parameters);
+}
+
+void QtControllerSelector::MainWindowReconfigureFinished(bool is_success) {
+ if (callback) {
+ callback(is_success);
+ }
+}
diff --git a/src/citron/applets/qt_controller.h b/src/citron/applets/qt_controller.h
new file mode 100644
index 000000000..e5372495d
--- /dev/null
+++ b/src/citron/applets/qt_controller.h
@@ -0,0 +1,183 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#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 Ui {
+class QtControllerSelectorDialog;
+}
+
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+class HIDCore;
+enum class NpadStyleIndex : u8;
+} // namespace Core::HID
+
+class ControllerNavigation;
+
+class QtControllerSelectorDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelectorDialog(QWidget* parent,
+ Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_,
+ Core::System& system_);
+ ~QtControllerSelectorDialog() override;
+
+ int exec() override;
+
+ void keyPressEvent(QKeyEvent* evt) 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.
+ Core::HID::NpadStyleIndex GetControllerTypeFromIndex(int index, std::size_t player_index) const;
+
+ // Gets the controller combobox index for a given Controller Type per player.
+ int GetIndexFromControllerType(Core::HID::NpadStyleIndex 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);
+
+ // Enable preceding controllers or disable following ones
+ void PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current = false);
+
+ // Disables and disconnects unsupported players based on the given parameters.
+ void DisableUnsupportedPlayers();
+
+ std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
+
+ // Parameters sent in from the backend HLE applet.
+ Core::Frontend::ControllerParameters parameters;
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<InputProfiles> input_profiles;
+
+ Core::System& system;
+
+ ControllerNavigation* controller_navigation = nullptr;
+
+ // 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<QWidget*, NUM_PLAYERS> player_widgets;
+
+ // Groupboxes encapsulating the controller icons and LED patterns per player.
+ std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
+
+ // Icons for currently connected controllers/players.
+ std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
+
+ // Labels that represent the player numbers in place of the controller icons.
+ std::array<QLabel*, NUM_PLAYERS> player_labels;
+
+ // LED patterns for currently connected controllers/players.
+ std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
+
+ // Labels representing additional information known as "Explain Text" per player.
+ std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
+
+ // Comboboxes with a list of emulated controllers per player.
+ std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
+
+ /// Pairs of emulated controller index and Controller Type enum per player.
+ std::array<std::vector<std::pair<int, Core::HID::NpadStyleIndex>>, NUM_PLAYERS>
+ index_controller_type_pairs;
+
+ // Labels representing the number of connected controllers
+ // above the "Connected Controllers" checkboxes.
+ std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
+
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
+};
+
+class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelector(GMainWindow& parent);
+ ~QtControllerSelector() override;
+
+ void Close() const override;
+ void ReconfigureControllers(
+ ReconfigureCallback callback_,
+ const Core::Frontend::ControllerParameters& parameters) const override;
+
+signals:
+ void MainWindowReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) const;
+ void MainWindowRequestExit() const;
+
+private:
+ void MainWindowReconfigureFinished(bool is_success);
+
+ mutable ReconfigureCallback callback;
+};
diff --git a/src/citron/applets/qt_controller.ui b/src/citron/applets/qt_controller.ui
new file mode 100644
index 000000000..6f7cb3c13
--- /dev/null
+++ b/src/citron/applets/qt_controller.ui
@@ -0,0 +1,2699 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtControllerSelectorDialog</class>
+ <widget class="QDialog" name="QtControllerSelectorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>839</width>
+ <height>630</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Controller Applet</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="controllersSupportedLabel">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Supported Controller Types:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported1" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported3" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported4" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="playersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>16</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="maxSupportedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Players:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="numberSupportedLabel">
+ <property name="font">
+ <font>
+ <pointsize>14</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>1 - 8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="middleControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item row="1" column="7">
+ <widget class="QWidget" name="widgetPlayer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer4Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer4" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer4">
+ <property name="text">
+ <string>P4</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player4LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player4Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_39">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer4Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QWidget" name="widgetPlayer2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer2Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer2" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer2">
+ <property name="text">
+ <string>P2</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player2LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player2Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_37">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer2Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QWidget" name="widgetPlayer1" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_30">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer1Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer1" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer1">
+ <property name="text">
+ <string>P1</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player1LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player1Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_36">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer1Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QWidget" name="widgetSpacer2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_31">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QWidget" name="widgetSpacer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_33">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QWidget" name="widgetSpacer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_32">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QWidget" name="widgetPlayer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer3Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer3" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer3">
+ <property name="text">
+ <string>P3</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player3LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player3Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_38">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer3Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="widgetSpacer5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_34">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="5">
+ <widget class="QWidget" name="widgetPlayer7" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_25">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer7Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer7" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer7">
+ <property name="text">
+ <string>P7</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player7LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player7Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_42">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer7Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="7">
+ <widget class="QWidget" name="widgetPlayer8" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer8Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer8" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer8">
+ <property name="text">
+ <string>P8</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player8LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player8Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_35">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer8Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QWidget" name="widgetPlayer5" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer5Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer5" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer5">
+ <property name="text">
+ <string>P5</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player5LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player5Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_40">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer5Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QWidget" name="widgetPlayer6" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer6Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer6" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer6">
+ <property name="text">
+ <string>P6</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player6LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player6Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_41">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer6Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="QWidget" name="widgetSpacer" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QWidget" name="widgetSpacer6" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QWidget" name="widgetSpacer7" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="widgetSpacer9" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_17">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="rightMargin">
+ <number>15</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>8</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Handheld</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="vibrationButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="inputConfigGroup">
+ <property name="title">
+ <string>Profiles</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="inputConfigButton">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Create</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="connectedControllers" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelControllers">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="labelConnectedPlayer1">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="labelConnectedPlayer2">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="labelConnectedPlayer4">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="labelConnectedPlayer3">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelConnected">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="labelConnectedPlayer5">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="labelConnectedPlayer7">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="labelConnectedPlayer6">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="labelConnectedPlayer8">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QWidget" name="closeButtons" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_46">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelError">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QLabel { color : red; }</string>
+ </property>
+ <property name="text">
+ <string>Not enough controllers</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtControllerSelectorDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QtControllerSelectorDialog</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/citron/applets/qt_error.cpp b/src/citron/applets/qt_error.cpp
new file mode 100644
index 000000000..ad35f4126
--- /dev/null
+++ b/src/citron/applets/qt_error.cpp
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QDateTime>
+#include "yuzu/applets/qt_error.h"
+#include "yuzu/main.h"
+
+QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
+ connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent,
+ &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
+ connect(this, &QtErrorDisplay::MainWindowRequestExit, &parent,
+ &GMainWindow::ErrorDisplayRequestExit, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ErrorDisplayFinished, this,
+ &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
+}
+
+QtErrorDisplay::~QtErrorDisplay() = default;
+
+void QtErrorDisplay::Close() const {
+ callback = {};
+ emit MainWindowRequestExit();
+}
+
+void QtErrorDisplay::ShowError(Result error, FinishedCallback finished) const {
+ callback = std::move(finished);
+ emit MainWindowDisplayError(
+ tr("Error Code: %1-%2 (0x%3)")
+ .arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+ .arg(error.GetDescription(), 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(Result error, std::chrono::seconds time,
+ FinishedCallback 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<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+ .arg(error.GetDescription(), 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(Result error, std::string dialog_text,
+ std::string fullscreen_text,
+ FinishedCallback finished) const {
+ callback = std::move(finished);
+ emit MainWindowDisplayError(
+ tr("Error Code: %1-%2 (0x%3)")
+ .arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+ .arg(error.GetDescription(), 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() {
+ if (callback) {
+ callback();
+ }
+}
diff --git a/src/citron/applets/qt_error.h b/src/citron/applets/qt_error.h
new file mode 100644
index 000000000..957f170ad
--- /dev/null
+++ b/src/citron/applets/qt_error.h
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QObject>
+
+#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 Close() const override;
+ void ShowError(Result error, FinishedCallback finished) const override;
+ void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
+ FinishedCallback finished) const override;
+ void ShowCustomErrorText(Result error, std::string dialog_text, std::string fullscreen_text,
+ FinishedCallback finished) const override;
+
+signals:
+ void MainWindowDisplayError(QString error_code, QString error_text) const;
+ void MainWindowRequestExit() const;
+
+private:
+ void MainWindowFinishedError();
+
+ mutable FinishedCallback callback;
+};
diff --git a/src/citron/applets/qt_profile_select.cpp b/src/citron/applets/qt_profile_select.cpp
new file mode 100644
index 000000000..66edd6acd
--- /dev/null
+++ b/src/citron/applets/qt_profile_select.cpp
@@ -0,0 +1,260 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <mutex>
+#include <QApplication>
+#include <QDialogButtonBox>
+#include <QHeaderView>
+#include <QLabel>
+#include <QLineEdit>
+#include <QScrollArea>
+#include <QStandardItemModel>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/fs/path_util.h"
+#include "common/string_util.h"
+#include "core/constants.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "yuzu/applets/qt_profile_select.h"
+#include "yuzu/main.h"
+#include "yuzu/util/controller_navigation.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.FormattedString()));
+}
+
+QString GetImagePath(Common::UUID uuid) {
+ const auto path =
+ Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
+ fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormattedString());
+ 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<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
+ }
+
+ return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+}
+} // Anonymous namespace
+
+QtProfileSelectionDialog::QtProfileSelectionDialog(
+ Core::System& system, QWidget* parent,
+ const Core::Frontend::ProfileSelectParameters& parameters)
+ : QDialog(parent), profile_manager{system.GetProfileManager()} {
+ outer_layout = new QVBoxLayout;
+
+ instruction_label = new QLabel();
+
+ 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);
+ controller_navigation = new ControllerNavigation(system.HIDCore(), this);
+
+ 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<QStandardItem*>>("QList<QStandardItem*>");
+
+ 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);
+ connect(tree_view, &QTreeView::doubleClicked, this, &QtProfileSelectionDialog::accept);
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [this](Qt::Key key) {
+ if (!this->isActiveWindow()) {
+ return;
+ }
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(tree_view, event);
+ SelectUser(tree_view->currentIndex());
+ });
+
+ 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<const char*>(profile.username.data()), profile.username.size());
+
+ list_items.push_back(QList<QStandardItem*>{new QStandardItem{
+ GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
+ }
+
+ for (const auto& item : list_items)
+ item_model->appendRow(item);
+
+ setLayout(outer_layout);
+ SetWindowTitle(parameters);
+ SetDialogPurpose(parameters);
+ resize(550, 400);
+}
+
+QtProfileSelectionDialog::~QtProfileSelectionDialog() {
+ controller_navigation->UnloadController();
+};
+
+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();
+}
+
+void QtProfileSelectionDialog::SetWindowTitle(
+ const Core::Frontend::ProfileSelectParameters& parameters) {
+ using Service::AM::Frontend::UiMode;
+ switch (parameters.mode) {
+ case UiMode::UserCreator:
+ case UiMode::UserCreatorForStarter:
+ setWindowTitle(tr("Profile Creator"));
+ return;
+ case UiMode::EnsureNetworkServiceAccountAvailable:
+ setWindowTitle(tr("Profile Selector"));
+ return;
+ case UiMode::UserIconEditor:
+ setWindowTitle(tr("Profile Icon Editor"));
+ return;
+ case UiMode::UserNicknameEditor:
+ setWindowTitle(tr("Profile Nickname Editor"));
+ return;
+ case UiMode::NintendoAccountAuthorizationRequestContext:
+ case UiMode::IntroduceExternalNetworkServiceAccount:
+ case UiMode::IntroduceExternalNetworkServiceAccountForRegistration:
+ case UiMode::NintendoAccountNnidLinker:
+ case UiMode::LicenseRequirementsForNetworkService:
+ case UiMode::LicenseRequirementsForNetworkServiceWithUserContextImpl:
+ case UiMode::UserCreatorForImmediateNaLoginTest:
+ case UiMode::UserQualificationPromoter:
+ case UiMode::UserSelector:
+ default:
+ setWindowTitle(tr("Profile Selector"));
+ }
+}
+
+void QtProfileSelectionDialog::SetDialogPurpose(
+ const Core::Frontend::ProfileSelectParameters& parameters) {
+ using Service::AM::Frontend::UserSelectionPurpose;
+
+ switch (parameters.purpose) {
+ case UserSelectionPurpose::GameCardRegistration:
+ instruction_label->setText(tr("Who will receive the points?"));
+ return;
+ case UserSelectionPurpose::EShopLaunch:
+ instruction_label->setText(tr("Who is using Nintendo eShop?"));
+ return;
+ case UserSelectionPurpose::EShopItemShow:
+ instruction_label->setText(tr("Who is making this purchase?"));
+ return;
+ case UserSelectionPurpose::PicturePost:
+ instruction_label->setText(tr("Who is posting?"));
+ return;
+ case UserSelectionPurpose::NintendoAccountLinkage:
+ instruction_label->setText(tr("Select a user to link to a Nintendo Account."));
+ return;
+ case UserSelectionPurpose::SettingsUpdate:
+ instruction_label->setText(tr("Change settings for which user?"));
+ return;
+ case UserSelectionPurpose::SaveDataDeletion:
+ instruction_label->setText(tr("Format data for which user?"));
+ return;
+ case UserSelectionPurpose::UserMigration:
+ instruction_label->setText(tr("Which user will be transferred to another console?"));
+ return;
+ case UserSelectionPurpose::SaveDataTransfer:
+ instruction_label->setText(tr("Send save data for which user?"));
+ return;
+ case UserSelectionPurpose::General:
+ default:
+ instruction_label->setText(tr("Select a user:"));
+ return;
+ }
+}
+
+QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
+ connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
+ &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
+ connect(this, &QtProfileSelector::MainWindowRequestExit, &parent,
+ &GMainWindow::ProfileSelectorRequestExit, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
+ &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
+}
+
+QtProfileSelector::~QtProfileSelector() = default;
+
+void QtProfileSelector::Close() const {
+ callback = {};
+ emit MainWindowRequestExit();
+}
+
+void QtProfileSelector::SelectProfile(
+ SelectProfileCallback callback_,
+ const Core::Frontend::ProfileSelectParameters& parameters) const {
+ callback = std::move(callback_);
+ emit MainWindowSelectProfile(parameters);
+}
+
+void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
+ if (callback) {
+ callback(uuid);
+ }
+}
diff --git a/src/citron/applets/qt_profile_select.h b/src/citron/applets/qt_profile_select.h
new file mode 100644
index 000000000..607f1777c
--- /dev/null
+++ b/src/citron/applets/qt_profile_select.h
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <vector>
+#include <QDialog>
+#include <QList>
+#include "core/frontend/applets/profile_select.h"
+
+class ControllerNavigation;
+class GMainWindow;
+class QDialogButtonBox;
+class QGraphicsScene;
+class QLabel;
+class QScrollArea;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Core {
+class System;
+}
+
+namespace Service::Account {
+class ProfileManager;
+}
+
+class QtProfileSelectionDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtProfileSelectionDialog(Core::System& system, QWidget* parent,
+ const Core::Frontend::ProfileSelectParameters& parameters);
+ ~QtProfileSelectionDialog() override;
+
+ int exec() override;
+ void accept() override;
+ void reject() override;
+
+ int GetIndex() const;
+
+private:
+ void SelectUser(const QModelIndex& index);
+
+ void SetWindowTitle(const Core::Frontend::ProfileSelectParameters& parameters);
+ void SetDialogPurpose(const Core::Frontend::ProfileSelectParameters& parameters);
+
+ int user_index = 0;
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+ QGraphicsScene* scene;
+
+ std::vector<QList<QStandardItem*>> list_items;
+
+ QVBoxLayout* outer_layout;
+ QLabel* instruction_label;
+ QScrollArea* scroll_area;
+ QDialogButtonBox* buttons;
+
+ Service::Account::ProfileManager& profile_manager;
+ ControllerNavigation* controller_navigation = nullptr;
+};
+
+class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
+ Q_OBJECT
+
+public:
+ explicit QtProfileSelector(GMainWindow& parent);
+ ~QtProfileSelector() override;
+
+ void Close() const override;
+ void SelectProfile(SelectProfileCallback callback_,
+ const Core::Frontend::ProfileSelectParameters& parameters) const override;
+
+signals:
+ void MainWindowSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters) const;
+ void MainWindowRequestExit() const;
+
+private:
+ void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
+
+ mutable SelectProfileCallback callback;
+};
diff --git a/src/citron/applets/qt_software_keyboard.cpp b/src/citron/applets/qt_software_keyboard.cpp
new file mode 100644
index 000000000..2749e6ed3
--- /dev/null
+++ b/src/citron/applets/qt_software_keyboard.cpp
@@ -0,0 +1,1674 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QCursor>
+#include <QKeyEvent>
+#include <QScreen>
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "hid_core/frontend/emulated_controller.h"
+#include "hid_core/frontend/input_interpreter.h"
+#include "hid_core/hid_core.h"
+#include "hid_core/hid_types.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::Frontend;
+
+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<Ui::QtSoftwareKeyboardDialog>()}, 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,
+ },
+ {
+ ui->button_left_optional_num,
+ ui->button_0_num,
+ ui->button_right_optional_num,
+ 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_left_optional_num,
+ ui->button_0_num,
+ ui->button_right_optional_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));
+
+ ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key});
+ ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key});
+
+ 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<InputInterpreter>(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::Frontend::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;
+ }
+
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
+
+ emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str), true);
+ 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 = appear_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;
+
+ switch (bottom_osk_index) {
+ case BottomOSKIndex::LowerCase:
+ case BottomOSKIndex::UpperCase: {
+ const auto* const curr_button =
+ keyboard_buttons[static_cast<std::size_t>(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()));
+ break;
+ }
+ case BottomOSKIndex::NumberPad: {
+ const auto* const 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;
+ }
+
+ 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(Core::HID::NpadButton::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(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size()));
+}
+
+void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) {
+ QDialog::move(pos);
+ QDialog::resize(size);
+
+ // High DPI
+ const float dpi_scale = screen()->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<int>(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<int>(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<u32>(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(Core::HID::NpadButton::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<u32>(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<u32>(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* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+ const auto controller_type =
+ handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex();
+
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark")) ||
+ QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_dark");
+ } else {
+ return QString{};
+ }
+ }();
+
+ switch (controller_type) {
+ case Core::HID::NpadStyleIndex::Fullkey:
+ case Core::HID::NpadStyleIndex::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 Core::HID::NpadStyleIndex::JoyconDual:
+ 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 Core::HID::NpadStyleIndex::JoyconLeft:
+ 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 Core::HID::NpadStyleIndex::JoyconRight:
+ 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 Core::HID::NpadStyleIndex::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);
+ }
+ }
+
+ const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0';
+ const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0';
+
+ ui->button_left_optional_num->setEnabled(enable_left_optional);
+ ui->button_left_optional_num->setVisible(enable_left_optional);
+
+ ui->button_right_optional_num->setEnabled(enable_right_optional);
+ ui->button_right_optional_num->setVisible(enable_right_optional);
+ 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<u32>(ui->text_edit_osk->toPlainText().length());
+ } else {
+ return static_cast<u32>(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<u32>(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(), [this](QChar c) {
+ return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} &&
+ c != QChar{initialize_parameters.right_optional_symbol_key};
+ })) {
+ return false;
+ }
+
+ return true;
+}
+
+void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() {
+ switch (bottom_osk_index) {
+ case BottomOSKIndex::LowerCase:
+ bottom_osk_index = BottomOSKIndex::UpperCase;
+ ui->bottomOSK->setCurrentIndex(static_cast<int>(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<int>(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) {
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
+
+ emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str));
+ 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;
+ }
+
+ const auto button_text = button->text();
+ InlineTextInsertString(Common::U16StringFromBuffer(button_text.utf16(), button_text.size()));
+
+ // 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<s32>(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 <Core::HID::NpadButton... T>
+void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
+ const auto f = [this](Core::HID::NpadButton button) {
+ if (input_interpreter->IsButtonPressedOnce(button)) {
+ TranslateButtonPress(button);
+ }
+ };
+
+ (f(T), ...);
+}
+
+template <Core::HID::NpadButton... T>
+void QtSoftwareKeyboardDialog::HandleButtonHold() {
+ const auto f = [this](Core::HID::NpadButton button) {
+ if (input_interpreter->IsButtonHeld(button)) {
+ TranslateButtonPress(button);
+ }
+ };
+
+ (f(T), ...);
+}
+
+void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) {
+ switch (button) {
+ case Core::HID::NpadButton::A:
+ switch (bottom_osk_index) {
+ case BottomOSKIndex::LowerCase:
+ case BottomOSKIndex::UpperCase:
+ keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column]->click();
+ break;
+ case BottomOSKIndex::NumberPad:
+ numberpad_buttons[row][column]->click();
+ break;
+ default:
+ break;
+ }
+ break;
+ case Core::HID::NpadButton::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 Core::HID::NpadButton::X:
+ if (is_inline) {
+ emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
+ } else {
+ const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
+ : ui->line_edit_osk->text();
+ auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());
+
+ emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_str));
+ }
+ break;
+ case Core::HID::NpadButton::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 Core::HID::NpadButton::StickL:
+ case Core::HID::NpadButton::StickR:
+ 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 Core::HID::NpadButton::L:
+ MoveTextCursorDirection(Direction::Left);
+ break;
+ case Core::HID::NpadButton::R:
+ MoveTextCursorDirection(Direction::Right);
+ break;
+ case Core::HID::NpadButton::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 Core::HID::NpadButton::Left:
+ case Core::HID::NpadButton::StickLLeft:
+ case Core::HID::NpadButton::StickRLeft:
+ MoveButtonDirection(Direction::Left);
+ break;
+ case Core::HID::NpadButton::Up:
+ case Core::HID::NpadButton::StickLUp:
+ case Core::HID::NpadButton::StickRUp:
+ MoveButtonDirection(Direction::Up);
+ break;
+ case Core::HID::NpadButton::Right:
+ case Core::HID::NpadButton::StickLRight:
+ case Core::HID::NpadButton::StickRRight:
+ MoveButtonDirection(Direction::Right);
+ break;
+ case Core::HID::NpadButton::Down:
+ case Core::HID::NpadButton::StickLDown:
+ case Core::HID::NpadButton::StickRDown:
+ 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;
+ }
+ };
+
+ // Store the initial row and column.
+ const auto initial_row = row;
+ const auto initial_column = column;
+
+ switch (bottom_osk_index) {
+ case BottomOSKIndex::LowerCase:
+ case BottomOSKIndex::UpperCase: {
+ const auto index = static_cast<std::size_t>(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) {
+ // If we returned back to where we started from, break the loop.
+ if (row == initial_row && column == initial_column) {
+ break;
+ }
+
+ 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) {
+ // If we returned back to where we started from, break the loop.
+ if (row == initial_row && column == initial_column) {
+ break;
+ }
+
+ 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<s32>(current_text.size())) {
+ cursor_position = static_cast<s32>(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<
+ Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
+ Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
+ Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
+ Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
+ Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
+ Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
+ Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
+ Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
+ Core::HID::NpadButton::StickRDown>();
+
+ HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
+ Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
+ Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
+ Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
+ Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
+ Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
+ Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
+ Core::HID::NpadButton::StickRDown>();
+
+ 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,
+ SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback 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::Frontend::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::Frontend::SwkbdResult result,
+ std::u16string submitted_text, bool confirmed) const {
+ submit_normal_callback(result, submitted_text, confirmed);
+}
+
+void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
+ std::u16string submitted_text,
+ s32 cursor_position) const {
+ submit_inline_callback(reply_type, submitted_text, cursor_position);
+}
diff --git a/src/citron/applets/qt_software_keyboard.h b/src/citron/applets/qt_software_keyboard.h
new file mode 100644
index 000000000..7e2fdf09e
--- /dev/null
+++ b/src/citron/applets/qt_software_keyboard.h
@@ -0,0 +1,287 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <thread>
+
+#include <QDialog>
+#include <QValidator>
+
+#include "core/frontend/applets/software_keyboard.h"
+
+class InputInterpreter;
+
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+enum class NpadButton : u64;
+}
+
+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::Frontend::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::Frontend::SwkbdResult result, std::u16string submitted_text,
+ bool confirmed = false) const;
+
+ void SubmitInlineText(Service::AM::Frontend::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 <Core::HID::NpadButton... T>
+ 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 <Core::HID::NpadButton... T>
+ void HandleButtonHold();
+
+ /**
+ * Translates a button press to focus or click a keyboard button.
+ *
+ * @param button The button press to process.
+ */
+ void TranslateButtonPress(Core::HID::NpadButton 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::QtSoftwareKeyboardDialog> 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<std::array<std::array<QPushButton*, NUM_COLUMNS_NORMAL>, NUM_ROWS_NORMAL>, 2>
+ keyboard_buttons;
+ // Stores the numberpad keyboard layout.
+ std::array<std::array<QPushButton*, NUM_COLUMNS_NUMPAD>, NUM_ROWS_NUMPAD> numberpad_buttons;
+
+ // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons.
+ std::array<QPushButton*, 112> all_buttons;
+
+ std::size_t row{0};
+ std::size_t column{0};
+
+ BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase};
+ std::atomic<bool> caps_lock_enabled{false};
+
+ std::unique_ptr<InputInterpreter> input_interpreter;
+
+ std::thread input_thread;
+
+ std::atomic<bool> input_thread_running{};
+};
+
+class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet {
+ Q_OBJECT
+
+public:
+ explicit QtSoftwareKeyboard(GMainWindow& parent);
+ ~QtSoftwareKeyboard() override;
+
+ void Close() const override {
+ ExitKeyboard();
+ }
+
+ void InitializeKeyboard(bool is_inline,
+ Core::Frontend::KeyboardInitializeParameters initialize_parameters,
+ SubmitNormalCallback submit_normal_callback_,
+ SubmitInlineCallback submit_inline_callback_) override;
+
+ void ShowNormalKeyboard() const override;
+
+ void ShowTextCheckDialog(Service::AM::Frontend::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::Frontend::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::Frontend::SwkbdResult result, std::u16string submitted_text,
+ bool confirmed) const;
+
+ void SubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
+ std::u16string submitted_text, s32 cursor_position) const;
+
+ mutable SubmitNormalCallback submit_normal_callback;
+ mutable SubmitInlineCallback submit_inline_callback;
+};
diff --git a/src/citron/applets/qt_software_keyboard.ui b/src/citron/applets/qt_software_keyboard.ui
new file mode 100644
index 000000000..9661cb260
--- /dev/null
+++ b/src/citron/applets/qt_software_keyboard.ui
@@ -0,0 +1,3541 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtSoftwareKeyboardDialog</class>
+ <widget class="QDialog" name="QtSoftwareKeyboardDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1280</width>
+ <height>720</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Software Keyboard</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainOSK" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="320,400">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QStackedWidget" name="topOSK">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="lineOSK">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="lineOSKVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLineOSK" rowstretch="40,50,23,48,65,94" columnstretch="130,1020,130">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="4" column="2">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="0">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="1">
+ <widget class="QWidget" name="charactersOSK" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignRight|Qt::AlignTop">
+ <widget class="QLabel" name="label_characters">
+ <property name="font">
+ <font>
+ <pointsize>17</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">0/32</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="1">
+ <widget class="QWidget" name="inputOSK" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLineEdit" name="line_edit_osk">
+ <property name="font">
+ <font>
+ <pointsize>26</pointsize>
+ <weight>50</weight>
+ <bold>false</bold>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="maxLength">
+ <number>32</number>
+ </property>
+ <property name="placeholderText">
+ <string>Enter Text</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0" colspan="3">
+ <widget class="QWidget" name="headerOSK" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="130,1020,130">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacer_18">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>127</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_header">
+ <property name="font">
+ <font>
+ <pointsize>23</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_19">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>127</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="3">
+ <widget class="QWidget" name="subOSK" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="130,1020,130">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacer_16">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>127</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_sub">
+ <property name="font">
+ <font>
+ <pointsize>17</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_17">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>127</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="boxOSK">
+ <layout class="QVBoxLayout" name="boxOSKVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridBoxOSK" rowstretch="61,178,81" columnstretch="120,1040,120">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_20">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2">
+ <spacer name="horizontalSpacer_21">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1" alignment="Qt::AlignRight|Qt::AlignTop">
+ <widget class="QWidget" name="charactersBoxOSK" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_characters_box">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>17</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">0/500</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QWidget" name="inputBoxOSK" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QTextEdit" name="text_edit_osk">
+ <property name="font">
+ <font>
+ <pointsize>26</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="bottomOSK">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="normalOSK">
+ <layout class="QVBoxLayout" name="normalPageVerticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="kbOSKnormal" rowstretch="15,63,63,63,63,63,70" columnstretch="54,96,96,96,96,96,96,96,96,96,96,96,116,54">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="6" column="1" colspan="12">
+ <widget class="QWidget" name="legendOSK" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="70,525,25,12,22,41,25,12,22,41,25,12,47,37,29,12,69,37,29,12,56,8">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="icon_controller" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_L" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_14">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_left" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_13">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_R" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_12">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_right" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_press_stick" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_shift">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Shift</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_15">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_X" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_cancel">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_A" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_enter">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Enter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="11">
+ <widget class="QPushButton" name="button_minus">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">-</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="11">
+ <widget class="QPushButton" name="button_apostrophe">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">'</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="11">
+ <widget class="QPushButton" name="button_slash">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">/</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="11">
+ <widget class="QPushButton" name="button_exclamation">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">!</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QPushButton" name="button_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">7</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QPushButton" name="button_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">8</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="10">
+ <widget class="QPushButton" name="button_0">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">0</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="9">
+ <widget class="QPushButton" name="button_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">9</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="button_w">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">w</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QPushButton" name="button_r">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">r</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QPushButton" name="button_e">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">e</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="button_q">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">q</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="7">
+ <widget class="QPushButton" name="button_u">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">u</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="6">
+ <widget class="QPushButton" name="button_y">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5">
+ <widget class="QPushButton" name="button_t">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">t</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="9">
+ <widget class="QPushButton" name="button_o">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">o</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="10">
+ <widget class="QPushButton" name="button_p">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">p</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="8">
+ <widget class="QPushButton" name="button_i">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">i</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QPushButton" name="button_a">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">a</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="button_s">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">s</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QPushButton" name="button_d">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">d</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QPushButton" name="button_f">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">f</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="6">
+ <widget class="QPushButton" name="button_h">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">h</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="7">
+ <widget class="QPushButton" name="button_j">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">j</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="5">
+ <widget class="QPushButton" name="button_g">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">g</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="8">
+ <widget class="QPushButton" name="button_k">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">k</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="9">
+ <widget class="QPushButton" name="button_l">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">l</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="10">
+ <widget class="QPushButton" name="button_colon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="12" rowspan="2">
+ <widget class="QPushButton" name="button_return">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Return</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="12" rowspan="2">
+ <widget class="QPushButton" name="button_ok">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">OK</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QPushButton" name="button_z">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QPushButton" name="button_c">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">c</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="button_x">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">x</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="4">
+ <widget class="QPushButton" name="button_v">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">v</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="7">
+ <widget class="QPushButton" name="button_m">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">m</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="8">
+ <widget class="QPushButton" name="button_comma">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">,</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="6">
+ <widget class="QPushButton" name="button_n">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">n</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="5">
+ <widget class="QPushButton" name="button_b">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">b</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2">
+ <widget class="QPushButton" name="button_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="10">
+ <widget class="QPushButton" name="button_question">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="9">
+ <widget class="QPushButton" name="button_dot">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="button_1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QPushButton" name="button_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">3</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QPushButton" name="button_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">4</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="button_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">2</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QPushButton" name="button_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">6</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QPushButton" name="button_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">5</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="3" colspan="9">
+ <widget class="QPushButton" name="button_space">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Space</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="12">
+ <widget class="QPushButton" name="button_backspace">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="13">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="shiftOSK">
+ <layout class="QVBoxLayout" name="shiftPageVerticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="kbOSKshift" rowstretch="15,63,63,63,63,63,70" columnstretch="54,96,96,96,96,96,96,96,96,96,96,96,116,54">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="6" column="1" colspan="12">
+ <widget class="QWidget" name="legendOSKshift" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="70,464,25,12,22,41,25,12,22,41,25,12,95,37,29,12,69,37,29,12,56,8">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="icon_controller_shift" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_22">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_L_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_23">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_left_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_24">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_R_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_25">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_right_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_26">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_press_stick_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_27">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_shift_shift">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Caps Lock</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_28">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_X_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_29">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_cancel_shift">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_30">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_A_shift" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_31">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_enter_shift">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Enter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_32">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="11">
+ <widget class="QPushButton" name="button_underscore">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">_</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="11">
+ <widget class="QPushButton" name="button_quotation">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">&quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="11">
+ <widget class="QPushButton" name="button_at">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">@</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="11">
+ <widget class="QPushButton" name="button_equal">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">=</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QPushButton" name="button_ampersand">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">&amp;&amp;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QPushButton" name="button_asterisk">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">*</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="10">
+ <widget class="QPushButton" name="button_right_parenthesis">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="9">
+ <widget class="QPushButton" name="button_left_parenthesis">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">(</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="button_w_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">W</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QPushButton" name="button_r_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">R</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QPushButton" name="button_e_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">E</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="button_q_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Q</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="7">
+ <widget class="QPushButton" name="button_u_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">U</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="6">
+ <widget class="QPushButton" name="button_y_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Y</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5">
+ <widget class="QPushButton" name="button_t_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">T</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="9">
+ <widget class="QPushButton" name="button_o_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">O</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="10">
+ <widget class="QPushButton" name="button_p_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">P</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="8">
+ <widget class="QPushButton" name="button_i_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">I</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QPushButton" name="button_a_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">A</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="button_s_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">S</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QPushButton" name="button_d_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">D</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QPushButton" name="button_f_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">F</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="6">
+ <widget class="QPushButton" name="button_h_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">H</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="7">
+ <widget class="QPushButton" name="button_j_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">J</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="5">
+ <widget class="QPushButton" name="button_g_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">G</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="8">
+ <widget class="QPushButton" name="button_k_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">K</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="9">
+ <widget class="QPushButton" name="button_l_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">L</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="10">
+ <widget class="QPushButton" name="button_semicolon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="12" rowspan="2">
+ <widget class="QPushButton" name="button_return_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Return</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="12" rowspan="2">
+ <widget class="QPushButton" name="button_ok_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">OK</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QPushButton" name="button_z_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Z</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QPushButton" name="button_c_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">C</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="button_x_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">X</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="4">
+ <widget class="QPushButton" name="button_v_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">V</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="7">
+ <widget class="QPushButton" name="button_m_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">M</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="8">
+ <widget class="QPushButton" name="button_less_than">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">&lt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="6">
+ <widget class="QPushButton" name="button_n_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">N</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="5">
+ <widget class="QPushButton" name="button_b_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">B</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2">
+ <widget class="QPushButton" name="button_shift_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="10">
+ <widget class="QPushButton" name="button_plus">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">+</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="9">
+ <widget class="QPushButton" name="button_greater_than">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="button_hash">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">#</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QPushButton" name="button_right_bracket">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">]</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QPushButton" name="button_dollar">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">$</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="button_left_bracket">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">[</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QPushButton" name="button_circumflex">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">^</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QPushButton" name="button_percent">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">%</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="3" colspan="9">
+ <widget class="QPushButton" name="button_space_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Space</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="12">
+ <widget class="QPushButton" name="button_backspace_shift">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_33">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="13">
+ <spacer name="horizontalSpacer_34">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="numOSK">
+ <layout class="QVBoxLayout" name="graphicsTabVerticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="kbOSKnum" rowstretch="18,63,63,63,63,10,70" columnstretch="54,307,186,186,186,120,187,54">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="1" column="5">
+ <widget class="QPushButton" name="button_backspace_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <spacer name="horizontalSpacer_35">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer_36">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2">
+ <spacer name="verticalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="6" column="1" colspan="6">
+ <widget class="QWidget" name="legendOSKnum" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="25,70,601,25,12,22,41,25,12,22,41,29,12,69,37,29,12,56,30">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacer_48">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="icon_controller_num" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_38">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_L_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_39">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_left_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_40">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_R_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_41">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="arrow_right_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_42">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_X_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_43">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_cancel_num">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_44">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="button_A_num" native="true"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_45">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_enter_num">
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">Enter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_46">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QPushButton" name="button_6_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">6</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="button_4_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">4</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QPushButton" name="button_9_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">9</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QPushButton" name="button_5_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">5</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5" rowspan="3">
+ <widget class="QPushButton" name="button_ok_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">OK</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="button_7_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">7</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QPushButton" name="button_8_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">8</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QPushButton" name="button_2_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">2</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="button_1_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="button_left_optional_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"></string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QPushButton" name="button_0_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">0</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="4">
+ <widget class="QPushButton" name="button_right_optional_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true"></string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QPushButton" name="button_3_num">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>28</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">3</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_37">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="7">
+ <spacer name="horizontalSpacer_47">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="3">
+ <spacer name="verticalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>button_1</tabstop>
+ <tabstop>button_2</tabstop>
+ <tabstop>button_3</tabstop>
+ <tabstop>button_4</tabstop>
+ <tabstop>button_5</tabstop>
+ <tabstop>button_6</tabstop>
+ <tabstop>button_7</tabstop>
+ <tabstop>button_8</tabstop>
+ <tabstop>button_9</tabstop>
+ <tabstop>button_0</tabstop>
+ <tabstop>button_minus</tabstop>
+ <tabstop>button_backspace</tabstop>
+ <tabstop>button_q</tabstop>
+ <tabstop>button_w</tabstop>
+ <tabstop>button_e</tabstop>
+ <tabstop>button_r</tabstop>
+ <tabstop>button_t</tabstop>
+ <tabstop>button_y</tabstop>
+ <tabstop>button_u</tabstop>
+ <tabstop>button_i</tabstop>
+ <tabstop>button_o</tabstop>
+ <tabstop>button_p</tabstop>
+ <tabstop>button_slash</tabstop>
+ <tabstop>button_return</tabstop>
+ <tabstop>button_a</tabstop>
+ <tabstop>button_s</tabstop>
+ <tabstop>button_d</tabstop>
+ <tabstop>button_f</tabstop>
+ <tabstop>button_g</tabstop>
+ <tabstop>button_h</tabstop>
+ <tabstop>button_j</tabstop>
+ <tabstop>button_k</tabstop>
+ <tabstop>button_l</tabstop>
+ <tabstop>button_colon</tabstop>
+ <tabstop>button_apostrophe</tabstop>
+ <tabstop>button_z</tabstop>
+ <tabstop>button_x</tabstop>
+ <tabstop>button_c</tabstop>
+ <tabstop>button_v</tabstop>
+ <tabstop>button_b</tabstop>
+ <tabstop>button_n</tabstop>
+ <tabstop>button_m</tabstop>
+ <tabstop>button_comma</tabstop>
+ <tabstop>button_dot</tabstop>
+ <tabstop>button_question</tabstop>
+ <tabstop>button_exclamation</tabstop>
+ <tabstop>button_ok</tabstop>
+ <tabstop>button_shift</tabstop>
+ <tabstop>button_space</tabstop>
+ <tabstop>button_hash</tabstop>
+ <tabstop>button_left_bracket</tabstop>
+ <tabstop>button_right_bracket</tabstop>
+ <tabstop>button_dollar</tabstop>
+ <tabstop>button_percent</tabstop>
+ <tabstop>button_circumflex</tabstop>
+ <tabstop>button_ampersand</tabstop>
+ <tabstop>button_asterisk</tabstop>
+ <tabstop>button_left_parenthesis</tabstop>
+ <tabstop>button_right_parenthesis</tabstop>
+ <tabstop>button_underscore</tabstop>
+ <tabstop>button_backspace_shift</tabstop>
+ <tabstop>button_q_shift</tabstop>
+ <tabstop>button_w_shift</tabstop>
+ <tabstop>button_e_shift</tabstop>
+ <tabstop>button_r_shift</tabstop>
+ <tabstop>button_t_shift</tabstop>
+ <tabstop>button_y_shift</tabstop>
+ <tabstop>button_u_shift</tabstop>
+ <tabstop>button_i_shift</tabstop>
+ <tabstop>button_o_shift</tabstop>
+ <tabstop>button_p_shift</tabstop>
+ <tabstop>button_at</tabstop>
+ <tabstop>button_return_shift</tabstop>
+ <tabstop>button_a_shift</tabstop>
+ <tabstop>button_s_shift</tabstop>
+ <tabstop>button_d_shift</tabstop>
+ <tabstop>button_f_shift</tabstop>
+ <tabstop>button_g_shift</tabstop>
+ <tabstop>button_h_shift</tabstop>
+ <tabstop>button_j_shift</tabstop>
+ <tabstop>button_k_shift</tabstop>
+ <tabstop>button_l_shift</tabstop>
+ <tabstop>button_semicolon</tabstop>
+ <tabstop>button_quotation</tabstop>
+ <tabstop>button_z_shift</tabstop>
+ <tabstop>button_x_shift</tabstop>
+ <tabstop>button_c_shift</tabstop>
+ <tabstop>button_v_shift</tabstop>
+ <tabstop>button_b_shift</tabstop>
+ <tabstop>button_n_shift</tabstop>
+ <tabstop>button_m_shift</tabstop>
+ <tabstop>button_less_than</tabstop>
+ <tabstop>button_greater_than</tabstop>
+ <tabstop>button_plus</tabstop>
+ <tabstop>button_equal</tabstop>
+ <tabstop>button_ok_shift</tabstop>
+ <tabstop>button_shift_shift</tabstop>
+ <tabstop>button_space_shift</tabstop>
+ <tabstop>button_1_num</tabstop>
+ <tabstop>button_2_num</tabstop>
+ <tabstop>button_3_num</tabstop>
+ <tabstop>button_backspace_num</tabstop>
+ <tabstop>button_4_num</tabstop>
+ <tabstop>button_5_num</tabstop>
+ <tabstop>button_6_num</tabstop>
+ <tabstop>button_ok_num</tabstop>
+ <tabstop>button_7_num</tabstop>
+ <tabstop>button_8_num</tabstop>
+ <tabstop>button_9_num</tabstop>
+ <tabstop>button_left_optional_num</tabstop>
+ <tabstop>button_0_num</tabstop>
+ <tabstop>button_right_optional_num</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../../dist/icons/overlay/overlay.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/citron/applets/qt_web_browser.cpp b/src/citron/applets/qt_web_browser.cpp
new file mode 100644
index 000000000..cce9b2efb
--- /dev/null
+++ b/src/citron/applets/qt_web_browser.cpp
@@ -0,0 +1,449 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <bit>
+
+#include <QApplication>
+#include <QKeyEvent>
+
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineUrlScheme>
+
+#include "hid_core/frontend/input_interpreter.h"
+#include "yuzu/applets/qt_web_browser_scripts.h"
+#endif
+
+#include "common/fs/path_util.h"
+#include "core/core.h"
+#include "input_common/drivers/keyboard.h"
+#include "yuzu/applets/qt_web_browser.h"
+#include "yuzu/main.h"
+#include "yuzu/util/url_request_interceptor.h"
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+namespace {
+
+constexpr int HIDButtonToKey(Core::HID::NpadButton button) {
+ switch (button) {
+ case Core::HID::NpadButton::Left:
+ case Core::HID::NpadButton::StickLLeft:
+ return Qt::Key_Left;
+ case Core::HID::NpadButton::Up:
+ case Core::HID::NpadButton::StickLUp:
+ return Qt::Key_Up;
+ case Core::HID::NpadButton::Right:
+ case Core::HID::NpadButton::StickLRight:
+ return Qt::Key_Right;
+ case Core::HID::NpadButton::Down:
+ case Core::HID::NpadButton::StickLDown:
+ 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<UrlRequestInterceptor>()),
+ input_interpreter(std::make_unique<InputInterpreter>(system)),
+ default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
+ default_profile->settings()} {
+ default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
+ Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine")));
+
+ 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->setUrlRequestInterceptor(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::Frontend::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();
+ FocusFirstLinkElement();
+ SetUserAgent(UserAgent::WebApplet);
+ SetFinished(false);
+ SetExitReason(Service::AM::Frontend::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;
+
+ FocusFirstLinkElement();
+ SetUserAgent(UserAgent::WebApplet);
+ SetFinished(false);
+ SetExitReason(Service::AM::Frontend::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::Frontend::WebExitReason QtNXWebEngineView::GetExitReason() const {
+ return exit_reason;
+}
+
+void QtNXWebEngineView::SetExitReason(Service::AM::Frontend::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 <Core::HID::NpadButton... T>
+void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
+ const auto f = [this](Core::HID::NpadButton button) {
+ if (input_interpreter->IsButtonPressedOnce(button)) {
+ const auto button_index = std::countr_zero(static_cast<u64>(button));
+
+ page()->runJavaScript(
+ QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(button_index),
+ [this, button](const QVariant& variant) {
+ if (variant.toBool()) {
+ switch (button) {
+ case Core::HID::NpadButton::A:
+ SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
+ break;
+ case Core::HID::NpadButton::B:
+ SendKeyPressEvent(Qt::Key_B);
+ break;
+ case Core::HID::NpadButton::X:
+ SendKeyPressEvent(Qt::Key_X);
+ break;
+ case Core::HID::NpadButton::Y:
+ SendKeyPressEvent(Qt::Key_Y);
+ break;
+ default:
+ break;
+ }
+ }
+ });
+
+ page()->runJavaScript(
+ QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
+ .arg(button_index));
+ }
+ };
+
+ (f(T), ...);
+}
+
+template <Core::HID::NpadButton... T>
+void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
+ const auto f = [this](Core::HID::NpadButton button) {
+ if (input_interpreter->IsButtonPressedOnce(button)) {
+ SendKeyPressEvent(HIDButtonToKey(button));
+ }
+ };
+
+ (f(T), ...);
+}
+
+template <Core::HID::NpadButton... T>
+void QtNXWebEngineView::HandleWindowKeyButtonHold() {
+ const auto f = [this](Core::HID::NpadButton 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<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
+ Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
+ Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
+
+ HandleWindowKeyButtonPressedOnce<
+ Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
+ Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
+ Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
+ Core::HID::NpadButton::StickLDown>();
+
+ HandleWindowKeyButtonHold<
+ Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
+ Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
+ Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
+ Core::HID::NpadButton::StickLDown>();
+
+ 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);
+}
+
+void QtNXWebEngineView::FocusFirstLinkElement() {
+ QWebEngineScript focus_link_element;
+
+ focus_link_element.setName(QStringLiteral("focus_link_element.js"));
+ focus_link_element.setSourceCode(QString::fromStdString(FOCUS_LINK_ELEMENT_SCRIPT));
+ focus_link_element.setWorldId(QWebEngineScript::MainWorld);
+ focus_link_element.setInjectionPoint(QWebEngineScript::Deferred);
+ focus_link_element.setRunsOnSubFrames(true);
+ default_profile->scripts()->insert(focus_link_element);
+}
+
+#endif
+
+QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
+ connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
+ &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
+ connect(this, &QtWebBrowser::MainWindowRequestExit, &main_window,
+ &GMainWindow::WebBrowserRequestExit, 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::Close() const {
+ callback = {};
+ emit MainWindowRequestExit();
+}
+
+void QtWebBrowser::OpenLocalWebPage(const std::string& local_url,
+ ExtractROMFSCallback extract_romfs_callback_,
+ OpenWebPageCallback 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,
+ OpenWebPageCallback 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::Frontend::WebExitReason exit_reason,
+ std::string last_url) {
+ if (callback) {
+ callback(exit_reason, last_url);
+ }
+}
diff --git a/src/citron/applets/qt_web_browser.h b/src/citron/applets/qt_web_browser.h
new file mode 100644
index 000000000..e8a0b6931
--- /dev/null
+++ b/src/citron/applets/qt_web_browser.h
@@ -0,0 +1,220 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include <QObject>
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineView>
+#endif
+
+#include "core/frontend/applets/web_browser.h"
+
+class GMainWindow;
+class InputInterpreter;
+class UrlRequestInterceptor;
+
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+enum class NpadButton : u64;
+}
+
+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::Frontend::WebExitReason GetExitReason() const;
+ void SetExitReason(Service::AM::Frontend::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 overridden by games to achieve desired functionality.
+ *
+ * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
+ */
+ template <Core::HID::NpadButton... T>
+ 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 <Core::HID::NpadButton... T>
+ 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 <Core::HID::NpadButton... T>
+ 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 <int... T>
+ 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();
+
+ /// Brings focus to the first available link element.
+ void FocusFirstLinkElement();
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<UrlRequestInterceptor> url_interceptor;
+
+ std::unique_ptr<InputInterpreter> input_interpreter;
+
+ std::thread input_thread;
+
+ std::atomic<bool> input_thread_running{};
+
+ std::atomic<bool> finished{};
+
+ Service::AM::Frontend::WebExitReason exit_reason{
+ Service::AM::Frontend::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 Close() const override;
+ void OpenLocalWebPage(const std::string& local_url,
+ ExtractROMFSCallback extract_romfs_callback_,
+ OpenWebPageCallback callback_) const override;
+
+ void OpenExternalWebPage(const std::string& external_url,
+ OpenWebPageCallback callback_) const override;
+
+signals:
+ void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args,
+ bool is_local) const;
+ void MainWindowRequestExit() const;
+
+private:
+ void MainWindowExtractOfflineRomFS();
+
+ void MainWindowWebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason,
+ std::string last_url);
+
+ mutable ExtractROMFSCallback extract_romfs_callback;
+ mutable OpenWebPageCallback callback;
+};
diff --git a/src/citron/applets/qt_web_browser_scripts.h b/src/citron/applets/qt_web_browser_scripts.h
new file mode 100644
index 000000000..f5530c38f
--- /dev/null
+++ b/src/citron/applets/qt_web_browser_scripts.h
@@ -0,0 +1,198 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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 FOCUS_LINK_ELEMENT_SCRIPT[] = R"(
+if (document.getElementsByTagName("a").length > 0) {
+ document.getElementsByTagName("a")[0].focus();
+}
+)";
+
+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();
+})();
+)";