From 0f40c8c6340aa858cd2e2ffe2e6c54885e0a3649 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 30 Jan 2021 14:38:00 -0500 Subject: [PATCH 01/14] applets: Remove the previous software keyboard applet implementation --- .../frontend/applets/software_keyboard.cpp | 20 +- src/core/frontend/applets/software_keyboard.h | 34 ---- .../service/am/applets/software_keyboard.cpp | 182 +----------------- .../service/am/applets/software_keyboard.h | 51 +---- src/yuzu/applets/software_keyboard.cpp | 143 +------------- src/yuzu/applets/software_keyboard.h | 48 +---- src/yuzu/main.cpp | 23 --- src/yuzu/main.h | 5 - 8 files changed, 14 insertions(+), 492 deletions(-) diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp index 856ed33da5..73e7a89b99 100644 --- a/src/core/frontend/applets/software_keyboard.cpp +++ b/src/core/frontend/applets/software_keyboard.cpp @@ -2,28 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/backend.h" -#include "common/string_util.h" #include "core/frontend/applets/software_keyboard.h" namespace Core::Frontend { + SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; -void DefaultSoftwareKeyboardApplet::RequestText( - std::function)> out, - SoftwareKeyboardParameters parameters) const { - if (parameters.initial_text.empty()) - out(u"yuzu"); - - out(parameters.initial_text); -} - -void DefaultSoftwareKeyboardApplet::SendTextCheckDialog( - std::u16string error_message, std::function finished_check) const { - LOG_WARNING(Service_AM, - "(STUBBED) called - Default fallback software keyboard does not support text " - "check! (error_message={})", - Common::UTF16ToUTF8(error_message)); - finished_check(); -} } // namespace Core::Frontend diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h index f9b2026642..54528837ee 100644 --- a/src/core/frontend/applets/software_keyboard.h +++ b/src/core/frontend/applets/software_keyboard.h @@ -4,51 +4,17 @@ #pragma once -#include -#include -#include -#include "common/bit_field.h" #include "common/common_types.h" namespace Core::Frontend { -struct SoftwareKeyboardParameters { - std::u16string submit_text; - std::u16string header_text; - std::u16string sub_text; - std::u16string guide_text; - std::u16string initial_text; - std::size_t max_length; - bool password; - bool cursor_at_beginning; - - union { - u8 value; - - BitField<1, 1, u8> disable_space; - BitField<2, 1, u8> disable_address; - BitField<3, 1, u8> disable_percent; - BitField<4, 1, u8> disable_slash; - BitField<6, 1, u8> disable_number; - BitField<7, 1, u8> disable_download_code; - }; -}; class SoftwareKeyboardApplet { public: virtual ~SoftwareKeyboardApplet(); - - virtual void RequestText(std::function)> out, - SoftwareKeyboardParameters parameters) const = 0; - virtual void SendTextCheckDialog(std::u16string error_message, - std::function finished_check) const = 0; }; class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { public: - void RequestText(std::function)> out, - SoftwareKeyboardParameters parameters) const override; - void SendTextCheckDialog(std::u16string error_message, - std::function finished_check) const override; }; } // namespace Core::Frontend diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index 79b209c6b9..f966cf67b5 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -2,199 +2,27 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include "common/assert.h" -#include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/software_keyboard.h" -#include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/software_keyboard.h" namespace Service::AM::Applets { -namespace { -enum class Request : u32 { - Finalize = 0x4, - SetUserWordInfo = 0x6, - SetCustomizeDic = 0x7, - Calc = 0xa, - SetCustomizedDictionaries = 0xb, - UnsetCustomizedDictionaries = 0xc, - UnknownD = 0xd, - UnknownE = 0xe, -}; -constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8; -constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; -constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; -constexpr std::size_t DEFAULT_MAX_LENGTH = 500; -constexpr bool INTERACTIVE_STATUS_OK = false; -} // Anonymous namespace -static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( - KeyboardConfig config, std::u16string initial_text) { - Core::Frontend::SoftwareKeyboardParameters params{}; - - params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - config.submit_text.data(), config.submit_text.size()); - params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( - config.header_text.data(), config.header_text.size()); - params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), - config.sub_text.size()); - params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), - config.guide_text.size()); - params.initial_text = std::move(initial_text); - params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; - params.password = static_cast(config.is_password); - params.cursor_at_beginning = static_cast(config.initial_cursor_position); - params.value = static_cast(config.keyset_disable_bitmask); - - return params; -} - SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, const Core::Frontend::SoftwareKeyboardApplet& frontend_) : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} SoftwareKeyboard::~SoftwareKeyboard() = default; -void SoftwareKeyboard::Initialize() { - complete = false; - is_inline = false; - initial_text.clear(); - final_data.clear(); +void SoftwareKeyboard::Initialize() {} - Applet::Initialize(); +bool SoftwareKeyboard::TransactionComplete() const {} - const auto keyboard_config_storage = broker.PopNormalDataToApplet(); - ASSERT(keyboard_config_storage != nullptr); - const auto& keyboard_config = keyboard_config_storage->GetData(); +ResultCode SoftwareKeyboard::GetStatus() const {} - if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) { - is_inline = true; - return; - } +void SoftwareKeyboard::ExecuteInteractive() {} - ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); - std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); +void SoftwareKeyboard::Execute() {} - const auto work_buffer_storage = broker.PopNormalDataToApplet(); - ASSERT_OR_EXECUTE(work_buffer_storage != nullptr, { return; }); - const auto& work_buffer = work_buffer_storage->GetData(); - - if (config.initial_string_size == 0) - return; - - std::vector string(config.initial_string_size); - std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, - string.size() * 2); - initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); -} - -bool SoftwareKeyboard::TransactionComplete() const { - return complete; -} - -ResultCode SoftwareKeyboard::GetStatus() const { - return RESULT_SUCCESS; -} - -void SoftwareKeyboard::ExecuteInteractive() { - if (complete) - return; - - const auto storage = broker.PopInteractiveDataToApplet(); - ASSERT(storage != nullptr); - const auto data = storage->GetData(); - if (!is_inline) { - const auto status = static_cast(data[0]); - if (status == INTERACTIVE_STATUS_OK) { - complete = true; - } else { - std::array string; - std::memcpy(string.data(), data.data() + 4, string.size() * 2); - frontend.SendTextCheckDialog( - Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), - [this] { broker.SignalStateChanged(); }); - } - } else { - Request request{}; - std::memcpy(&request, data.data(), sizeof(Request)); - - switch (request) { - case Request::Finalize: - complete = true; - broker.SignalStateChanged(); - break; - case Request::Calc: { - broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{1})); - broker.SignalStateChanged(); - break; - } - default: - UNIMPLEMENTED_MSG("Request {:X} is not implemented", request); - break; - } - } -} - -void SoftwareKeyboard::Execute() { - if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); - broker.SignalStateChanged(); - return; - } - - const auto parameters = ConvertToFrontendParameters(config, initial_text); - if (!is_inline) { - frontend.RequestText( - [this](std::optional text) { WriteText(std::move(text)); }, parameters); - } -} - -void SoftwareKeyboard::WriteText(std::optional text) { - std::vector output_main(SWKBD_OUTPUT_BUFFER_SIZE); - - if (text.has_value()) { - std::vector output_sub(SWKBD_OUTPUT_BUFFER_SIZE); - - if (config.utf_8) { - const u64 size = text->size() + sizeof(u64); - const auto new_text = Common::UTF16ToUTF8(*text); - - std::memcpy(output_sub.data(), &size, sizeof(u64)); - std::memcpy(output_sub.data() + 8, new_text.data(), - std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); - - output_main[0] = INTERACTIVE_STATUS_OK; - std::memcpy(output_main.data() + 4, new_text.data(), - std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); - } else { - const u64 size = text->size() * 2 + sizeof(u64); - std::memcpy(output_sub.data(), &size, sizeof(u64)); - std::memcpy(output_sub.data() + 8, text->data(), - std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); - - output_main[0] = INTERACTIVE_STATUS_OK; - std::memcpy(output_main.data() + 4, text->data(), - std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); - } - - complete = !config.text_check; - final_data = output_main; - - if (complete) { - broker.PushNormalDataFromApplet( - std::make_shared(system, std::move(output_main))); - broker.SignalStateChanged(); - } else { - broker.PushInteractiveDataFromApplet( - std::make_shared(system, std::move(output_sub))); - } - } else { - output_main[0] = 1; - complete = true; - broker.PushNormalDataFromApplet(std::make_shared(system, std::move(output_main))); - broker.SignalStateChanged(); - } -} } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 1d260fef80..c161ec9ac8 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -4,59 +4,17 @@ #pragma once -#include -#include -#include - #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/am/am.h" +#include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" -union ResultCode; - namespace Core { class System; } namespace Service::AM::Applets { -enum class KeysetDisable : u32 { - Space = 0x02, - Address = 0x04, - Percent = 0x08, - Slashes = 0x10, - Numbers = 0x40, - DownloadCode = 0x80, -}; - -struct KeyboardConfig { - INSERT_PADDING_BYTES(4); - std::array submit_text; - u16_le left_symbol_key; - u16_le right_symbol_key; - INSERT_PADDING_BYTES(1); - KeysetDisable keyset_disable_bitmask; - u32_le initial_cursor_position; - std::array header_text; - std::array sub_text; - std::array guide_text; - u32_le length_limit; - INSERT_PADDING_BYTES(4); - u32_le is_password; - INSERT_PADDING_BYTES(5); - bool utf_8; - bool draw_background; - u32_le initial_string_offset; - u32_le initial_string_size; - u32_le user_dictionary_offset; - u32_le user_dictionary_size; - bool text_check; - u64_le text_check_callback; -}; -static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); - class SoftwareKeyboard final : public Applet { public: explicit SoftwareKeyboard(Core::System& system_, @@ -70,16 +28,9 @@ public: void ExecuteInteractive() override; void Execute() override; - void WriteText(std::optional text); - private: const Core::Frontend::SoftwareKeyboardApplet& frontend; - KeyboardConfig config; - std::u16string initial_text; - bool complete = false; - bool is_inline = false; - std::vector final_data; Core::System& system; }; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index ab8cfd8ee1..da0fed774e 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -2,152 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include -#include -#include -#include -#include -#include -#include "core/hle/lock.h" #include "yuzu/applets/software_keyboard.h" #include "yuzu/main.h" -QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( - Core::Frontend::SoftwareKeyboardParameters parameters) - : parameters(std::move(parameters)) {} +QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator() {} -QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { - if (input.size() > static_cast(parameters.max_length)) { - return Invalid; - } - if (parameters.disable_space && input.contains(QLatin1Char{' '})) { - return Invalid; - } - if (parameters.disable_address && input.contains(QLatin1Char{'@'})) { - return Invalid; - } - if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) { - return Invalid; - } - if (parameters.disable_slash && - (input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) { - return Invalid; - } - if (parameters.disable_number && - std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { - return Invalid; - } +QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {} - if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) { - return c == QLatin1Char{'O'} || c == QLatin1Char{'I'}; - })) { - return Invalid; - } - - return Acceptable; -} - -QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( - QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) - : QDialog(parent), parameters(std::move(parameters_)) { - layout = new QVBoxLayout; - - header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); - header_label->setFont({header_label->font().family(), 11, QFont::Bold}); - if (header_label->text().isEmpty()) - header_label->setText(tr("Enter text:")); - - sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); - sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), - sub_label->font().weight(), true}); - sub_label->setHidden(parameters.sub_text.empty()); - - guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); - guide_label->setHidden(parameters.guide_text.empty()); - - length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); - length_label->setAlignment(Qt::AlignRight); - length_label->setFont({length_label->font().family(), 8}); - - line_edit = new QLineEdit; - line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); - line_edit->setMaxLength(static_cast(parameters.max_length)); - line_edit->setText(QString::fromStdU16String(parameters.initial_text)); - line_edit->setCursorPosition( - parameters.cursor_at_beginning ? 0 : static_cast(parameters.initial_text.size())); - line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); - - connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { - length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); - }); - - buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); - if (parameters.submit_text.empty()) { - buttons->addButton(QDialogButtonBox::Ok); - } else { - buttons->addButton(QString::fromStdU16String(parameters.submit_text), - QDialogButtonBox::AcceptRole); - } - connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject); - layout->addWidget(header_label); - layout->addWidget(sub_label); - layout->addWidget(guide_label); - layout->addWidget(length_label); - layout->addWidget(line_edit); - layout->addWidget(buttons); - setLayout(layout); - setWindowTitle(tr("Software Keyboard")); -} +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(QWidget* parent) : QDialog(parent) {} QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; -void QtSoftwareKeyboardDialog::accept() { - text = line_edit->text().toStdU16String(); - QDialog::accept(); -} - -void QtSoftwareKeyboardDialog::reject() { - text.clear(); - QDialog::reject(); -} - -std::u16string QtSoftwareKeyboardDialog::GetText() const { - return text; -} - -QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { - connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, - &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); - connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, - &GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); - connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, - &QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); -} +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {} QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; - -void QtSoftwareKeyboard::RequestText(std::function)> out, - Core::Frontend::SoftwareKeyboardParameters parameters) const { - text_output = std::move(out); - emit MainWindowGetText(parameters); -} - -void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, - std::function finished_check_) const { - finished_check = std::move(finished_check_); - emit MainWindowTextCheckDialog(error_message); -} - -void QtSoftwareKeyboard::MainWindowFinishedText(std::optional text) { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - text_output(std::move(text)); -} - -void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - finished_check(); -} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 9e1094cce1..8427c0a6ca 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -6,49 +6,23 @@ #include #include + #include "core/frontend/applets/software_keyboard.h" class GMainWindow; -class QDialogButtonBox; -class QLabel; -class QLineEdit; -class QVBoxLayout; -class QtSoftwareKeyboard; class QtSoftwareKeyboardValidator final : public QValidator { public: - explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); + explicit QtSoftwareKeyboardValidator(); State validate(QString& input, int& pos) const override; - -private: - Core::Frontend::SoftwareKeyboardParameters parameters; }; class QtSoftwareKeyboardDialog final : public QDialog { Q_OBJECT public: - QtSoftwareKeyboardDialog(QWidget* parent, - Core::Frontend::SoftwareKeyboardParameters parameters); + QtSoftwareKeyboardDialog(QWidget* parent); ~QtSoftwareKeyboardDialog() override; - - void accept() override; - void reject() override; - - std::u16string GetText() const; - -private: - std::u16string text; - - QDialogButtonBox* buttons; - QLabel* header_label; - QLabel* sub_label; - QLabel* guide_label; - QLabel* length_label; - QLineEdit* line_edit; - QVBoxLayout* layout; - - Core::Frontend::SoftwareKeyboardParameters parameters; }; class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { @@ -57,20 +31,4 @@ class QtSoftwareKeyboard final : public QObject, public Core::Frontend::Software public: explicit QtSoftwareKeyboard(GMainWindow& parent); ~QtSoftwareKeyboard() override; - - void RequestText(std::function)> out, - Core::Frontend::SoftwareKeyboardParameters parameters) const override; - void SendTextCheckDialog(std::u16string error_message, - std::function finished_check_) const override; - -signals: - void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; - void MainWindowTextCheckDialog(std::u16string error_message) const; - -private: - void MainWindowFinishedText(std::optional text); - void MainWindowFinishedCheckDialog(); - - mutable std::function)> text_output; - mutable std::function finished_check; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fbf96be03a..e9d6e74211 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -414,27 +414,6 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } -void GMainWindow::SoftwareKeyboardGetText( - const Core::Frontend::SoftwareKeyboardParameters& parameters) { - QtSoftwareKeyboardDialog dialog(this, parameters); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Rejected) { - emit SoftwareKeyboardFinishedText(std::nullopt); - return; - } - - emit SoftwareKeyboardFinishedText(dialog.GetText()); -} - -void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) { - QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message)); - emit SoftwareKeyboardFinishedCheckDialog(); -} - void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE @@ -2188,8 +2167,6 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); qRegisterMetaType("Core::Frontend::ControllerParameters"); - qRegisterMetaType( - "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType("Core::System::ResultStatus"); qRegisterMetaType("std::string"); qRegisterMetaType>("std::optional"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 04d37d4ae5..6429549ae7 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -128,9 +128,6 @@ signals: void ProfileSelectorFinishedSelection(std::optional uuid); - void SoftwareKeyboardFinishedText(std::optional text); - void SoftwareKeyboardFinishedCheckDialog(); - void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); @@ -141,8 +138,6 @@ public slots: const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); - void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); - void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); From d1e40dd24427582b0329e6470c21a4e774afb400 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 6 Feb 2021 02:31:13 -0500 Subject: [PATCH 02/14] applets: Pass in the LibraryAppletMode each applet's constructor --- src/core/hle/service/am/am.cpp | 4 ++-- src/core/hle/service/am/applets/applets.cpp | 18 +++++++++--------- src/core/hle/service/am/applets/applets.h | 10 +++++++++- src/core/hle/service/am/applets/controller.cpp | 5 +++-- src/core/hle/service/am/applets/controller.h | 4 +++- src/core/hle/service/am/applets/error.cpp | 5 +++-- src/core/hle/service/am/applets/error.h | 4 +++- .../hle/service/am/applets/general_backend.cpp | 14 ++++++++------ .../hle/service/am/applets/general_backend.h | 11 ++++++++--- .../hle/service/am/applets/profile_select.cpp | 4 ++-- .../hle/service/am/applets/profile_select.h | 3 ++- .../hle/service/am/applets/web_browser.cpp | 5 +++-- src/core/hle/service/am/applets/web_browser.h | 4 +++- 13 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 416c5239ac..836fa564e2 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1135,13 +1135,13 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default; void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_id = rp.PopRaw(); - const auto applet_mode = rp.PopRaw(); + const auto applet_mode = rp.PopRaw(); LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id, applet_mode); const auto& applet_manager{system.GetAppletManager()}; - const auto applet = applet_manager.GetApplet(applet_id); + const auto applet = applet_manager.GetApplet(applet_id, applet_mode); if (applet == nullptr) { LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index e2f3b75631..5ddad851ae 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -241,31 +241,31 @@ void AppletManager::ClearAll() { frontend = {}; } -std::shared_ptr AppletManager::GetApplet(AppletId id) const { +std::shared_ptr AppletManager::GetApplet(AppletId id, LibraryAppletMode mode) const { switch (id) { case AppletId::Auth: - return std::make_shared(system, *frontend.parental_controls); + return std::make_shared(system, mode, *frontend.parental_controls); case AppletId::Controller: - return std::make_shared(system, *frontend.controller); + return std::make_shared(system, mode, *frontend.controller); case AppletId::Error: - return std::make_shared(system, *frontend.error); + return std::make_shared(system, mode, *frontend.error); case AppletId::ProfileSelect: - return std::make_shared(system, *frontend.profile_select); + return std::make_shared(system, mode, *frontend.profile_select); case AppletId::SoftwareKeyboard: - return std::make_shared(system, *frontend.software_keyboard); + return std::make_shared(system, mode, *frontend.software_keyboard); case AppletId::Web: case AppletId::Shop: case AppletId::OfflineWeb: case AppletId::LoginShare: case AppletId::WebAuth: - return std::make_shared(system, *frontend.web_browser); + return std::make_shared(system, mode, *frontend.web_browser); case AppletId::PhotoViewer: - return std::make_shared(system, *frontend.photo_viewer); + return std::make_shared(system, mode, *frontend.photo_viewer); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", static_cast(id)); - return std::make_shared(system, id); + return std::make_shared(system, id, mode); } } diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index b9a006317a..26b4820158 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -62,6 +62,14 @@ enum class AppletId : u32 { MyPage = 0x1A, }; +enum class LibraryAppletMode : u32 { + AllForeground = 0, + Background = 1, + NoUI = 2, + BackgroundIndirectDisplay = 3, + AllForegroundInitiallyHidden = 4, +}; + class AppletDataBroker final { public: explicit AppletDataBroker(Kernel::KernelCore& kernel_); @@ -200,7 +208,7 @@ public: void SetDefaultAppletsIfMissing(); void ClearAll(); - std::shared_ptr GetApplet(AppletId id) const; + std::shared_ptr GetApplet(AppletId id, LibraryAppletMode mode) const; private: AppletFrontendSet frontend; diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index c2bfe698f5..a33f05f97c 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -45,8 +45,9 @@ static Core::Frontend::ControllerParameters ConvertToFrontendParameters( }; } -Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Controller::Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Controller::~Controller() = default; diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index d4c9da7b13..07cb92bf94 100644 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -106,7 +106,8 @@ static_assert(sizeof(ControllerSupportResultInfo) == 0xC, class Controller final : public Applet { public: - explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_); + explicit Controller(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ControllerApplet& frontend_); ~Controller() override; void Initialize() override; @@ -119,6 +120,7 @@ public: void ConfigurationComplete(); private: + LibraryAppletMode applet_mode; const Core::Frontend::ControllerApplet& frontend; Core::System& system; diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index 0c8b632e8b..a9f0a9c950 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -86,8 +86,9 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace -Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Error::Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Error::~Error() = default; diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h index a105cdb0c8..a3e520cd49 100644 --- a/src/core/hle/service/am/applets/error.h +++ b/src/core/hle/service/am/applets/error.h @@ -25,7 +25,8 @@ enum class ErrorAppletMode : u8 { class Error final : public Applet { public: - explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_); + explicit Error(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::ErrorApplet& frontend_); ~Error() override; void Initialize() override; @@ -40,6 +41,7 @@ public: private: union ErrorArguments; + LibraryAppletMode applet_mode; const Core::Frontend::ErrorApplet& frontend; ResultCode error_code = RESULT_SUCCESS; ErrorAppletMode mode = ErrorAppletMode::ShowError; diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index 4d1df5cbeb..71016cce74 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -37,8 +37,9 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) } } -Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +Auth::Auth(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::ParentalControlsApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} Auth::~Auth() = default; @@ -152,8 +153,9 @@ void Auth::AuthFinished(bool is_successful) { broker.SignalStateChanged(); } -PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +PhotoViewer::PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -202,8 +204,8 @@ void PhotoViewer::ViewFinished() { broker.SignalStateChanged(); } -StubApplet::StubApplet(Core::System& system_, AppletId id_) - : Applet{system_.Kernel()}, id{id_}, system{system_} {} +StubApplet::StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_) + : Applet{system_.Kernel()}, id{id_}, applet_mode{applet_mode_}, system{system_} {} StubApplet::~StubApplet() = default; diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index ba76ae3d3d..d9e6d43843 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -20,7 +20,8 @@ enum class AuthAppletType : u32 { class Auth final : public Applet { public: - explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_); + explicit Auth(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::ParentalControlsApplet& frontend_); ~Auth() override; void Initialize() override; @@ -32,6 +33,7 @@ public: void AuthFinished(bool is_successful = true); private: + LibraryAppletMode applet_mode; Core::Frontend::ParentalControlsApplet& frontend; Core::System& system; bool complete = false; @@ -50,7 +52,8 @@ enum class PhotoViewerAppletMode : u8 { class PhotoViewer final : public Applet { public: - explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_); + explicit PhotoViewer(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::PhotoViewerApplet& frontend_); ~PhotoViewer() override; void Initialize() override; @@ -62,6 +65,7 @@ public: void ViewFinished(); private: + LibraryAppletMode applet_mode; const Core::Frontend::PhotoViewerApplet& frontend; bool complete = false; PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; @@ -70,7 +74,7 @@ private: class StubApplet final : public Applet { public: - explicit StubApplet(Core::System& system_, AppletId id_); + explicit StubApplet(Core::System& system_, AppletId id_, LibraryAppletMode applet_mode_); ~StubApplet() override; void Initialize() override; @@ -82,6 +86,7 @@ public: private: AppletId id; + LibraryAppletMode applet_mode; Core::System& system; }; diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 77fba16c7d..ab8b6fcc5d 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -15,9 +15,9 @@ namespace Service::AM::Applets { constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; -ProfileSelect::ProfileSelect(Core::System& system_, +ProfileSelect::ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, const Core::Frontend::ProfileSelectApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} ProfileSelect::~ProfileSelect() = default; diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 648d33a249..90f054030f 100644 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -33,7 +33,7 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco class ProfileSelect final : public Applet { public: - explicit ProfileSelect(Core::System& system_, + explicit ProfileSelect(Core::System& system_, LibraryAppletMode applet_mode_, const Core::Frontend::ProfileSelectApplet& frontend_); ~ProfileSelect() override; @@ -47,6 +47,7 @@ public: void SelectionComplete(std::optional uuid); private: + LibraryAppletMode applet_mode; const Core::Frontend::ProfileSelectApplet& frontend; UserSelectionConfig config; diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 2ab4207894..b28b849bc7 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -208,8 +208,9 @@ void ExtractSharedFonts(Core::System& system) { } // namespace -WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} +WebBrowser::WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend(frontend_), system{system_} {} WebBrowser::~WebBrowser() = default; diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 04c2747546..5eafbae7b0 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -25,7 +25,8 @@ namespace Service::AM::Applets { class WebBrowser final : public Applet { public: - WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_); + WebBrowser(Core::System& system_, LibraryAppletMode applet_mode_, + const Core::Frontend::WebBrowserApplet& frontend_); ~WebBrowser() override; @@ -63,6 +64,7 @@ private: void ExecuteWifi(); void ExecuteLobby(); + LibraryAppletMode applet_mode; const Core::Frontend::WebBrowserApplet& frontend; bool complete{false}; From 0a40106cf143c5ae5e029ce3f828bead6d3fdb92 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 11 Mar 2021 22:31:05 -0500 Subject: [PATCH 03/14] ILibraryAppletAccessor: Demote from ERROR to DEBUG for null storage logs Avoids unnecessary console spam when the inline software keyboard is used. --- src/core/hle/service/am/am.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 836fa564e2..6373d816dd 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -971,7 +971,7 @@ private: auto storage = applet->GetBroker().PopNormalDataToGame(); if (storage == nullptr) { - LOG_ERROR(Service_AM, + LOG_DEBUG(Service_AM, "storage is a nullptr. There is no data in the current normal channel"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_NO_DATA_IN_CHANNEL); @@ -1002,7 +1002,7 @@ private: auto storage = applet->GetBroker().PopInteractiveDataToGame(); if (storage == nullptr) { - LOG_ERROR(Service_AM, + LOG_DEBUG(Service_AM, "storage is a nullptr. There is no data in the current interactive channel"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERR_NO_DATA_IN_CHANNEL); From e3e6a11ab8fc14a99bce35eb6fd860d583f255d8 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 12 Mar 2021 10:13:31 -0500 Subject: [PATCH 04/14] hle_ipc: Add helper functions to get copy/move handles --- src/core/hle/kernel/hle_ipc.cpp | 8 ++++++-- src/core/hle/kernel/hle_ipc.h | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 161d9f7826..2b363b1d98 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -75,10 +75,14 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_ if (incoming) { // Populate the object lists with the data in the IPC request. for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { - copy_objects.push_back(handle_table.GetGeneric(rp.Pop())); + const u32 copy_handle{rp.Pop()}; + copy_handles.push_back(copy_handle); + copy_objects.push_back(handle_table.GetGeneric(copy_handle)); } for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { - move_objects.push_back(handle_table.GetGeneric(rp.Pop())); + const u32 move_handle{rp.Pop()}; + move_handles.push_back(move_handle); + move_objects.push_back(handle_table.GetGeneric(move_handle)); } } else { // For responses we just ignore the handles, they're empty and will be populated when diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 9a769781b6..6fba426153 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -210,6 +210,14 @@ public: /// Helper function to test whether the output buffer at buffer_index can be written bool CanWriteBuffer(std::size_t buffer_index = 0) const; + Handle GetCopyHandle(std::size_t index) const { + return copy_handles.at(index); + } + + Handle GetMoveHandle(std::size_t index) const { + return move_handles.at(index); + } + template std::shared_ptr GetCopyObject(std::size_t index) { return DynamicObjectCast(copy_objects.at(index)); @@ -285,6 +293,8 @@ private: std::shared_ptr server_session; std::shared_ptr thread; // TODO(yuriks): Check common usage of this and optimize size accordingly + boost::container::small_vector move_handles; + boost::container::small_vector copy_handles; boost::container::small_vector, 8> move_objects; boost::container::small_vector, 8> copy_objects; boost::container::small_vector, 8> domain_objects; From a8c09cd5e46904441165c4aeaacb5397efdf2173 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 12 Mar 2021 10:14:01 -0500 Subject: [PATCH 05/14] ILibraryAppletCreator: Implement CreateHandleStorage Used by Monster Hunter Generations Ultimate --- src/core/hle/service/am/am.cpp | 71 ++++++++++++++++++++++++++++++---- src/core/hle/service/am/am.h | 1 + 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 6373d816dd..c59054468f 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1125,7 +1125,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) {2, nullptr, "AreAnyLibraryAppletsLeft"}, {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, {11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"}, - {12, nullptr, "CreateHandleStorage"}, + {12, &ILibraryAppletCreator::CreateHandleStorage, "CreateHandleStorage"}, }; RegisterHandlers(functions); } @@ -1134,6 +1134,7 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default; void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + const auto applet_id = rp.PopRaw(); const auto applet_mode = rp.PopRaw(); @@ -1159,9 +1160,18 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const u64 size{rp.Pop()}; + + const s64 size{rp.Pop()}; + LOG_DEBUG(Service_AM, "called, size={}", size); + if (size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + std::vector buffer(size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -1170,18 +1180,65 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { } void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_AM, "called"); - IPC::RequestParser rp{ctx}; - rp.SetCurrentOffset(3); - const auto handle{rp.Pop()}; + struct Parameters { + u8 permissions; + s64 size; + }; + + const auto parameters{rp.PopRaw()}; + const auto handle{ctx.GetCopyHandle(0)}; + + LOG_DEBUG(Service_AM, "called, permissions={}, size={}, handle={:08X}", parameters.permissions, + parameters.size, handle); + + if (parameters.size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } auto transfer_mem = system.CurrentProcess()->GetHandleTable().Get(handle); if (transfer_mem == nullptr) { - LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); + LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + const u8* const mem_begin = transfer_mem->GetPointer(); + const u8* const mem_end = mem_begin + transfer_mem->GetSize(); + std::vector memory{mem_begin, mem_end}; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(system, std::move(memory)); +} + +void ILibraryAppletCreator::CreateHandleStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s64 size{rp.Pop()}; + const auto handle{ctx.GetCopyHandle(0)}; + + LOG_DEBUG(Service_AM, "called, size={}, handle={:08X}", size, handle); + + if (size <= 0) { + LOG_ERROR(Service_AM, "size is less than or equal to 0"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + auto transfer_mem = + system.CurrentProcess()->GetHandleTable().Get(handle); + + if (transfer_mem == nullptr) { + LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); return; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index f6a453ab7c..aefbdf0d55 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -254,6 +254,7 @@ private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); + void CreateHandleStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework { From 5bc9f15c6d3270862fb495778ecc706ff35750a4 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 16 Mar 2021 12:59:52 -0400 Subject: [PATCH 06/14] applets/swkbd: Implement the Normal and Inline Software Keyboard Applet --- src/core/CMakeLists.txt | 1 + .../service/am/applets/software_keyboard.cpp | 1067 ++++++++++++++++- .../service/am/applets/software_keyboard.h | 140 ++- .../am/applets/software_keyboard_types.h | 295 +++++ 4 files changed, 1489 insertions(+), 14 deletions(-) create mode 100644 src/core/hle/service/am/applets/software_keyboard_types.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 286e912e3e..532e418b0e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -273,6 +273,7 @@ add_library(core STATIC hle/service/am/applets/profile_select.h hle/service/am/applets/software_keyboard.cpp hle/service/am/applets/software_keyboard.h + hle/service/am/applets/software_keyboard_types.h hle/service/am/applets/web_browser.cpp hle/service/am/applets/web_browser.h hle/service/am/applets/web_types.h diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index f966cf67b5..c3a05de9cc 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -1,7 +1,8 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/software_keyboard.h" #include "core/hle/service/am/am.h" @@ -9,20 +10,1068 @@ namespace Service::AM::Applets { -SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, - const Core::Frontend::SoftwareKeyboardApplet& frontend_) - : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} +namespace { + +// The maximum number of UTF-16 characters that can be input into the swkbd text field. +constexpr u32 DEFAULT_MAX_TEXT_LENGTH = 500; + +constexpr std::size_t REPLY_BASE_SIZE = sizeof(SwkbdState) + sizeof(SwkbdReplyType); +constexpr std::size_t REPLY_UTF8_SIZE = 0x7D4; +constexpr std::size_t REPLY_UTF16_SIZE = 0x3EC; + +constexpr const char* GetTextCheckResultName(SwkbdTextCheckResult text_check_result) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + return "Success"; + case SwkbdTextCheckResult::Failure: + return "Failure"; + case SwkbdTextCheckResult::Confirm: + return "Confirm"; + case SwkbdTextCheckResult::Silent: + return "Silent"; + default: + UNIMPLEMENTED_MSG("Unknown TextCheckResult={}", text_check_result); + return "Unknown"; + } +} + +void SetReplyBase(std::vector& reply, SwkbdState state, SwkbdReplyType reply_type) { + std::memcpy(reply.data(), &state, sizeof(SwkbdState)); + std::memcpy(reply.data() + sizeof(SwkbdState), &reply_type, sizeof(SwkbdReplyType)); +} + +} // Anonymous namespace + +SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_) + : Applet{system_.Kernel()}, applet_mode{applet_mode_}, frontend{frontend_}, system{system_} {} SoftwareKeyboard::~SoftwareKeyboard() = default; -void SoftwareKeyboard::Initialize() {} +void SoftwareKeyboard::Initialize() { + Applet::Initialize(); -bool SoftwareKeyboard::TransactionComplete() const {} + LOG_INFO(Service_AM, "Initializing Software Keyboard Applet with LibraryAppletMode={}", + applet_mode); -ResultCode SoftwareKeyboard::GetStatus() const {} + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); -void SoftwareKeyboard::ExecuteInteractive() {} + swkbd_applet_version = SwkbdAppletVersion{common_args.library_version}; -void SoftwareKeyboard::Execute() {} + switch (applet_mode) { + case LibraryAppletMode::AllForeground: + InitializeForeground(); + break; + case LibraryAppletMode::Background: + case LibraryAppletMode::BackgroundIndirectDisplay: + InitializeBackground(applet_mode); + break; + default: + UNREACHABLE_MSG("Invalid LibraryAppletMode={}", applet_mode); + break; + } +} + +bool SoftwareKeyboard::TransactionComplete() const { + return complete; +} + +ResultCode SoftwareKeyboard::GetStatus() const { + return status; +} + +void SoftwareKeyboard::ExecuteInteractive() { + if (complete) { + return; + } + + if (is_background) { + ProcessInlineKeyboardRequest(); + } else { + ProcessTextCheck(); + } +} + +void SoftwareKeyboard::Execute() { + if (complete) { + return; + } + + if (is_background) { + return; + } + + ShowNormalKeyboard(); +} + +void SoftwareKeyboard::SubmitTextNormal(SwkbdResult result, std::u16string submitted_text) { + if (complete) { + return; + } + + if (swkbd_config_common.use_text_check && result == SwkbdResult::Ok) { + SubmitForTextCheck(submitted_text); + } else { + SubmitNormalOutputAndExit(result, submitted_text); + } +} + +void SoftwareKeyboard::SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + if (complete) { + return; + } + + current_text = std::move(submitted_text); + current_cursor_position = cursor_position; + + if (inline_use_utf8) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringUtf8; + break; + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorUtf8; + break; + case SwkbdReplyType::DecidedEnter: + reply_type = SwkbdReplyType::DecidedEnterUtf8; + break; + default: + break; + } + } + + if (use_changed_string_v2) { + switch (reply_type) { + case SwkbdReplyType::ChangedString: + reply_type = SwkbdReplyType::ChangedStringV2; + break; + case SwkbdReplyType::ChangedStringUtf8: + reply_type = SwkbdReplyType::ChangedStringUtf8V2; + break; + default: + break; + } + } + + if (use_moved_cursor_v2) { + switch (reply_type) { + case SwkbdReplyType::MovedCursor: + reply_type = SwkbdReplyType::MovedCursorV2; + break; + case SwkbdReplyType::MovedCursorUtf8: + reply_type = SwkbdReplyType::MovedCursorUtf8V2; + break; + default: + break; + } + } + + SendReply(reply_type); +} + +void SoftwareKeyboard::InitializeForeground() { + LOG_INFO(Service_AM, "Initializing Normal Software Keyboard Applet."); + + is_background = false; + + const auto swkbd_config_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_config_storage != nullptr); + + const auto& swkbd_config_data = swkbd_config_storage->GetData(); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon)); + + std::memcpy(&swkbd_config_common, swkbd_config_data.data(), sizeof(SwkbdConfigCommon)); + + switch (swkbd_applet_version) { + case SwkbdAppletVersion::Version5: + case SwkbdAppletVersion::Version65542: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld)); + std::memcpy(&swkbd_config_old, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld)); + break; + case SwkbdAppletVersion::Version196615: + case SwkbdAppletVersion::Version262152: + case SwkbdAppletVersion::Version327689: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigOld2)); + std::memcpy(&swkbd_config_old2, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigOld2)); + break; + case SwkbdAppletVersion::Version393227: + case SwkbdAppletVersion::Version524301: + ASSERT(swkbd_config_data.size() == sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdConfig revision={} with size={}", swkbd_applet_version, + swkbd_config_data.size()); + ASSERT(swkbd_config_data.size() >= sizeof(SwkbdConfigCommon) + sizeof(SwkbdConfigNew)); + std::memcpy(&swkbd_config_new, swkbd_config_data.data() + sizeof(SwkbdConfigCommon), + sizeof(SwkbdConfigNew)); + break; + } + + const auto work_buffer_storage = broker.PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + + if (swkbd_config_common.initial_string_length == 0) { + InitializeFrontendKeyboard(); + return; + } + + const auto& work_buffer = work_buffer_storage->GetData(); + + std::vector initial_string(swkbd_config_common.initial_string_length); + + std::memcpy(initial_string.data(), + work_buffer.data() + swkbd_config_common.initial_string_offset, + swkbd_config_common.initial_string_length * sizeof(char16_t)); + + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(initial_string.data(), + initial_string.size()); + + LOG_DEBUG(Service_AM, "\nInitial Text: {}", Common::UTF16ToUTF8(initial_text)); + + InitializeFrontendKeyboard(); +} + +void SoftwareKeyboard::InitializeBackground(LibraryAppletMode applet_mode) { + LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet."); + + is_background = true; + + const auto swkbd_inline_initialize_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(swkbd_inline_initialize_arg_storage != nullptr); + + const auto& swkbd_inline_initialize_arg = swkbd_inline_initialize_arg_storage->GetData(); + ASSERT(swkbd_inline_initialize_arg.size() == sizeof(SwkbdInitializeArg)); + + std::memcpy(&swkbd_initialize_arg, swkbd_inline_initialize_arg.data(), + swkbd_inline_initialize_arg.size()); + + if (swkbd_initialize_arg.library_applet_mode_flag) { + ASSERT(applet_mode == LibraryAppletMode::Background); + } else { + ASSERT(applet_mode == LibraryAppletMode::BackgroundIndirectDisplay); + } +} + +void SoftwareKeyboard::ProcessTextCheck() { + const auto text_check_storage = broker.PopInteractiveDataToApplet(); + ASSERT(text_check_storage != nullptr); + + const auto& text_check_data = text_check_storage->GetData(); + ASSERT(text_check_data.size() == sizeof(SwkbdTextCheck)); + + SwkbdTextCheck swkbd_text_check; + + std::memcpy(&swkbd_text_check, text_check_data.data(), sizeof(SwkbdTextCheck)); + + std::u16string text_check_message = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_text_check.text_check_message.data(), swkbd_text_check.text_check_message.size()); + + LOG_INFO(Service_AM, "\nTextCheckResult: {}\nTextCheckMessage: {}", + GetTextCheckResultName(swkbd_text_check.text_check_result), + Common::UTF16ToUTF8(text_check_message)); + + switch (swkbd_text_check.text_check_result) { + case SwkbdTextCheckResult::Success: + SubmitNormalOutputAndExit(SwkbdResult::Ok, current_text); + break; + case SwkbdTextCheckResult::Failure: + ShowTextCheckDialog(SwkbdTextCheckResult::Failure, text_check_message); + break; + case SwkbdTextCheckResult::Confirm: + ShowTextCheckDialog(SwkbdTextCheckResult::Confirm, text_check_message); + break; + case SwkbdTextCheckResult::Silent: + default: + break; + } +} + +void SoftwareKeyboard::ProcessInlineKeyboardRequest() { + const auto request_data_storage = broker.PopInteractiveDataToApplet(); + ASSERT(request_data_storage != nullptr); + + const auto& request_data = request_data_storage->GetData(); + ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand)); + + SwkbdRequestCommand request_command; + + std::memcpy(&request_command, request_data.data(), sizeof(SwkbdRequestCommand)); + + switch (request_command) { + case SwkbdRequestCommand::Finalize: + RequestFinalize(request_data); + break; + case SwkbdRequestCommand::SetUserWordInfo: + RequestSetUserWordInfo(request_data); + break; + case SwkbdRequestCommand::SetCustomizeDic: + RequestSetCustomizeDic(request_data); + break; + case SwkbdRequestCommand::Calc: + RequestCalc(request_data); + break; + case SwkbdRequestCommand::SetCustomizedDictionaries: + RequestSetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::UnsetCustomizedDictionaries: + RequestUnsetCustomizedDictionaries(request_data); + break; + case SwkbdRequestCommand::SetChangedStringV2Flag: + RequestSetChangedStringV2Flag(request_data); + break; + case SwkbdRequestCommand::SetMovedCursorV2Flag: + RequestSetMovedCursorV2Flag(request_data); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdRequestCommand={}", request_command); + break; + } +} + +void SoftwareKeyboard::SubmitNormalOutputAndExit(SwkbdResult result, + std::u16string submitted_text) { + std::vector out_data(sizeof(SwkbdResult) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(submitted_text); + + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-8 Submitted Text: {}", result, + utf8_submitted_text); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + LOG_DEBUG(Service_AM, "\nSwkbdResult: {}\nUTF-16 Submitted Text: {}", result, + Common::UTF16ToUTF8(submitted_text)); + + std::memcpy(out_data.data(), &result, sizeof(SwkbdResult)); + std::memcpy(out_data.data() + sizeof(SwkbdResult), submitted_text.data(), + submitted_text.size() * sizeof(char16_t)); + } + + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + + ExitKeyboard(); +} + +void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { + current_text = std::move(submitted_text); + + std::vector out_data(sizeof(u64) + STRING_BUFFER_SIZE); + + if (swkbd_config_common.use_utf8) { + std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); + const u64 buffer_size = sizeof(u64) + utf8_submitted_text.size(); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, + utf8_submitted_text); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), + utf8_submitted_text.size()); + } else { + const u64 buffer_size = sizeof(u64) + current_text.size() * sizeof(char16_t); + + LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, + Common::UTF16ToUTF8(current_text)); + + std::memcpy(out_data.data(), &buffer_size, sizeof(u64)); + std::memcpy(out_data.data() + sizeof(u64), current_text.data(), + current_text.size() * sizeof(char16_t)); + } + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(out_data))); +} + +void SoftwareKeyboard::SendReply(SwkbdReplyType reply_type) { + switch (reply_type) { + case SwkbdReplyType::FinishedInitialize: + ReplyFinishedInitialize(); + break; + case SwkbdReplyType::Default: + ReplyDefault(); + break; + case SwkbdReplyType::ChangedString: + ReplyChangedString(); + break; + case SwkbdReplyType::MovedCursor: + ReplyMovedCursor(); + break; + case SwkbdReplyType::MovedTab: + ReplyMovedTab(); + break; + case SwkbdReplyType::DecidedEnter: + ReplyDecidedEnter(); + break; + case SwkbdReplyType::DecidedCancel: + ReplyDecidedCancel(); + break; + case SwkbdReplyType::ChangedStringUtf8: + ReplyChangedStringUtf8(); + break; + case SwkbdReplyType::MovedCursorUtf8: + ReplyMovedCursorUtf8(); + break; + case SwkbdReplyType::DecidedEnterUtf8: + ReplyDecidedEnterUtf8(); + break; + case SwkbdReplyType::UnsetCustomizeDic: + ReplyUnsetCustomizeDic(); + break; + case SwkbdReplyType::ReleasedUserWordInfo: + ReplyReleasedUserWordInfo(); + break; + case SwkbdReplyType::UnsetCustomizedDictionaries: + ReplyUnsetCustomizedDictionaries(); + break; + case SwkbdReplyType::ChangedStringV2: + ReplyChangedStringV2(); + break; + case SwkbdReplyType::MovedCursorV2: + ReplyMovedCursorV2(); + break; + case SwkbdReplyType::ChangedStringUtf8V2: + ReplyChangedStringUtf8V2(); + break; + case SwkbdReplyType::MovedCursorUtf8V2: + ReplyMovedCursorUtf8V2(); + break; + default: + UNIMPLEMENTED_MSG("Unknown SwkbdReplyType={}", reply_type); + ReplyDefault(); + break; + } +} + +void SoftwareKeyboard::ChangeState(SwkbdState state) { + swkbd_state = state; + + ReplyDefault(); +} + +void SoftwareKeyboard::InitializeFrontendKeyboard() { + if (is_background) { + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + appear_arg.ok_text.data(), appear_arg.ok_text.size()); + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + const s32 initial_cursor_position = + current_cursor_position > 0 ? current_cursor_position : 0; + + const auto text_draw_type = + max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{ok_text}, + .header_text{}, + .sub_text{}, + .guide_text{}, + .initial_text{current_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{appear_arg.type}, + .password_mode{SwkbdPasswordMode::Disabled}, + .text_draw_type{text_draw_type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .use_blur_background{false}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.InitializeKeyboard( + true, std::move(initialize_parameters), {}, + [this](SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position) { + SubmitTextInline(reply_type, submitted_text, cursor_position); + }); + } else { + std::u16string ok_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.ok_text.data(), swkbd_config_common.ok_text.size()); + + std::u16string header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.header_text.data(), swkbd_config_common.header_text.size()); + + std::u16string sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.sub_text.data(), swkbd_config_common.sub_text.size()); + + std::u16string guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_config_common.guide_text.data(), swkbd_config_common.guide_text.size()); + + const u32 max_text_length = + swkbd_config_common.max_text_length > 0 && + swkbd_config_common.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? swkbd_config_common.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = swkbd_config_common.min_text_length <= max_text_length + ? swkbd_config_common.min_text_length + : 0; + + const s32 initial_cursor_position = [this] { + switch (swkbd_config_common.initial_cursor_position) { + case SwkbdInitialCursorPosition::Start: + default: + return 0; + case SwkbdInitialCursorPosition::End: + return static_cast(initial_text.size()); + } + }(); + + const auto text_draw_type = [this, max_text_length] { + switch (swkbd_config_common.text_draw_type) { + case SwkbdTextDrawType::Line: + default: + return max_text_length <= 32 ? SwkbdTextDrawType::Line : SwkbdTextDrawType::Box; + case SwkbdTextDrawType::Box: + case SwkbdTextDrawType::DownloadCode: + return swkbd_config_common.text_draw_type; + } + }(); + + const auto enable_return_button = text_draw_type == SwkbdTextDrawType::Box + ? swkbd_config_common.enable_return_button + : false; + + const auto disable_cancel_button = swkbd_applet_version >= SwkbdAppletVersion::Version393227 + ? swkbd_config_new.disable_cancel_button + : false; + + Core::Frontend::KeyboardInitializeParameters initialize_parameters{ + .ok_text{ok_text}, + .header_text{header_text}, + .sub_text{sub_text}, + .guide_text{guide_text}, + .initial_text{initial_text}, + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .initial_cursor_position{initial_cursor_position}, + .type{swkbd_config_common.type}, + .password_mode{swkbd_config_common.password_mode}, + .text_draw_type{text_draw_type}, + .key_disable_flags{swkbd_config_common.key_disable_flags}, + .use_blur_background{swkbd_config_common.use_blur_background}, + .enable_backspace_button{true}, + .enable_return_button{enable_return_button}, + .disable_cancel_button{disable_cancel_button}, + }; + + frontend.InitializeKeyboard(false, std::move(initialize_parameters), + [this](SwkbdResult result, std::u16string submitted_text) { + SubmitTextNormal(result, submitted_text); + }, + {}); + } +} + +void SoftwareKeyboard::ShowNormalKeyboard() { + frontend.ShowNormalKeyboard(); +} + +void SoftwareKeyboard::ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + frontend.ShowTextCheckDialog(text_check_result, text_check_message); +} + +void SoftwareKeyboard::ShowInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsHidden) { + return; + } + + ChangeState(SwkbdState::InitializedIsAppearing); + + const auto& appear_arg = swkbd_calc_arg.appear_arg; + + const u32 max_text_length = + appear_arg.max_text_length > 0 && appear_arg.max_text_length <= DEFAULT_MAX_TEXT_LENGTH + ? appear_arg.max_text_length + : DEFAULT_MAX_TEXT_LENGTH; + + const u32 min_text_length = + appear_arg.min_text_length <= max_text_length ? appear_arg.min_text_length : 0; + + Core::Frontend::InlineAppearParameters appear_parameters{ + .max_text_length{max_text_length}, + .min_text_length{min_text_length}, + .key_top_scale_x{swkbd_calc_arg.key_top_scale_x}, + .key_top_scale_y{swkbd_calc_arg.key_top_scale_y}, + .key_top_translate_x{swkbd_calc_arg.key_top_translate_x}, + .key_top_translate_y{swkbd_calc_arg.key_top_translate_y}, + .type{appear_arg.type}, + .key_disable_flags{appear_arg.key_disable_flags}, + .key_top_as_floating{swkbd_calc_arg.key_top_as_floating}, + .enable_backspace_button{swkbd_calc_arg.enable_backspace_button}, + .enable_return_button{appear_arg.enable_return_button}, + .disable_cancel_button{appear_arg.disable_cancel_button}, + }; + + frontend.ShowInlineKeyboard(std::move(appear_parameters)); + + ChangeState(SwkbdState::InitializedIsShown); +} + +void SoftwareKeyboard::HideInlineKeyboard() { + if (swkbd_state != SwkbdState::InitializedIsShown) { + return; + } + + ChangeState(SwkbdState::InitializedIsDisappearing); + + frontend.HideInlineKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); +} + +void SoftwareKeyboard::InlineTextChanged() { + Core::Frontend::InlineTextParameters text_parameters{ + .input_text{current_text}, + .cursor_position{current_cursor_position}, + }; + + frontend.InlineTextChanged(std::move(text_parameters)); +} + +void SoftwareKeyboard::ExitKeyboard() { + complete = true; + status = RESULT_SUCCESS; + + frontend.ExitKeyboard(); + + broker.SignalStateChanged(); +} + +// Inline Software Keyboard Requests + +void SoftwareKeyboard::RequestFinalize(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Finalize"); + + ChangeState(SwkbdState::NotInitialized); + + ExitKeyboard(); +} + +void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented."); +} + +void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented."); +} + +void SoftwareKeyboard::RequestCalc(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: Calc"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArg)); + + std::memcpy(&swkbd_calc_arg, request_data.data() + sizeof(SwkbdRequestCommand), + sizeof(SwkbdCalcArg)); + + if (swkbd_calc_arg.flags.set_input_text) { + current_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + swkbd_calc_arg.input_text.data(), swkbd_calc_arg.input_text.size()); + } + + if (swkbd_calc_arg.flags.set_cursor_position) { + current_cursor_position = swkbd_calc_arg.cursor_position; + } + + if (swkbd_calc_arg.flags.set_utf8_mode) { + inline_use_utf8 = swkbd_calc_arg.utf8_mode; + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_customize_dic) { + ReplyUnsetCustomizeDic(); + } + + if (swkbd_state <= SwkbdState::InitializedIsHidden && + swkbd_calc_arg.flags.unset_user_word_info) { + ReplyReleasedUserWordInfo(); + } + + if (swkbd_state == SwkbdState::NotInitialized && swkbd_calc_arg.flags.set_initialize_arg) { + InitializeFrontendKeyboard(); + + ChangeState(SwkbdState::InitializedIsHidden); + + ReplyFinishedInitialize(); + } + + if (!swkbd_calc_arg.flags.set_initialize_arg && + (swkbd_calc_arg.flags.set_input_text || swkbd_calc_arg.flags.set_cursor_position)) { + InlineTextChanged(); + } + + if (swkbd_state == SwkbdState::InitializedIsHidden && swkbd_calc_arg.flags.appear) { + ShowInlineKeyboard(); + return; + } + + if (swkbd_state == SwkbdState::InitializedIsShown && swkbd_calc_arg.flags.disappear) { + HideInlineKeyboard(); + return; + } +} + +void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented."); +} + +void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector& request_data) { + LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries"); + + ReplyUnsetCustomizedDictionaries(); +} + +void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector& request_data) { + LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag"); + + ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1); + + std::memcpy(&use_moved_cursor_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1); +} + +// Inline Software Keyboard Replies + +void SoftwareKeyboard::ReplyFinishedInitialize() { + LOG_DEBUG(Service_AM, "Sending Reply: FinishedInitialize"); + + std::vector reply(REPLY_BASE_SIZE + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::FinishedInitialize); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDefault() { + LOG_DEBUG(Service_AM, "Sending Reply: Default"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::Default); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedString() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedString"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedString); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursor() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursor"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursor); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedTab() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedTab"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedTabArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedTab); + + const SwkbdMovedTabArg moved_tab_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_tab_arg, + sizeof(SwkbdMovedTabArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnter() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnter"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnter); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyDecidedCancel() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedCancel"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedCancel); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyDecidedEnterUtf8() { + LOG_DEBUG(Service_AM, "Sending Reply: DecidedEnterUtf8"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdDecidedEnterArg)); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::DecidedEnterUtf8); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdDecidedEnterArg decided_enter_arg{ + .text_length{static_cast(current_text.size())}, + }; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &decided_enter_arg, + sizeof(SwkbdDecidedEnterArg)); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); + + HideInlineKeyboard(); +} + +void SoftwareKeyboard::ReplyUnsetCustomizeDic() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizeDic"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizeDic); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyReleasedUserWordInfo() { + LOG_DEBUG(Service_AM, "Sending Reply: ReleasedUserWordInfo"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ReleasedUserWordInfo); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyUnsetCustomizedDictionaries() { + LOG_DEBUG(Service_AM, "Sending Reply: UnsetCustomizedDictionaries"); + + std::vector reply(REPLY_BASE_SIZE); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::UnsetCustomizedDictionaries); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringV2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringV2); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorV2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorV2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorV2); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, current_text.data(), + current_text.size() * sizeof(char16_t)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF16_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyChangedStringUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: ChangedStringUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::ChangedStringUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdChangedStringArg changed_string_arg{ + .text_length{static_cast(current_text.size())}, + .dictionary_start_cursor_position{-1}, + .dictionary_end_cursor_position{-1}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &changed_string_arg, + sizeof(SwkbdChangedStringArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdChangedStringArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} + +void SoftwareKeyboard::ReplyMovedCursorUtf8V2() { + LOG_DEBUG(Service_AM, "Sending Reply: MovedCursorUtf8V2"); + + std::vector reply(REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg) + 1); + + SetReplyBase(reply, swkbd_state, SwkbdReplyType::MovedCursorUtf8V2); + + std::string utf8_current_text = Common::UTF16ToUTF8(current_text); + + const SwkbdMovedCursorArg moved_cursor_arg{ + .text_length{static_cast(current_text.size())}, + .cursor_position{current_cursor_position}, + }; + + constexpr u8 flag = 0; + + std::memcpy(reply.data() + REPLY_BASE_SIZE, utf8_current_text.data(), utf8_current_text.size()); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE, &moved_cursor_arg, + sizeof(SwkbdMovedCursorArg)); + std::memcpy(reply.data() + REPLY_BASE_SIZE + REPLY_UTF8_SIZE + sizeof(SwkbdMovedCursorArg), + &flag, 1); + + broker.PushInteractiveDataFromApplet(std::make_shared(system, std::move(reply))); +} } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index c161ec9ac8..85aeb4eb1b 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -1,4 +1,4 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -8,6 +8,7 @@ #include "common/common_types.h" #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/software_keyboard_types.h" namespace Core { class System; @@ -17,8 +18,8 @@ namespace Service::AM::Applets { class SoftwareKeyboard final : public Applet { public: - explicit SoftwareKeyboard(Core::System& system_, - const Core::Frontend::SoftwareKeyboardApplet& frontend_); + explicit SoftwareKeyboard(Core::System& system_, LibraryAppletMode applet_mode_, + Core::Frontend::SoftwareKeyboardApplet& frontend_); ~SoftwareKeyboard() override; void Initialize() override; @@ -28,10 +29,139 @@ public: void ExecuteInteractive() override; void Execute() override; -private: - const Core::Frontend::SoftwareKeyboardApplet& frontend; + /** + * Submits the input text to the application. + * If text checking is enabled, the application will verify the input text. + * If use_utf8 is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the normal software keyboard. + * + * @param result SwkbdResult enum + * @param submitted_text UTF-16 encoded string + */ + void SubmitTextNormal(SwkbdResult result, std::u16string submitted_text); + /** + * Submits the input text to the application. + * If utf8_mode is enabled, the input text will be converted to UTF-8 prior to being submitted. + * This should only be used by the inline software keyboard. + * + * @param reply_type SwkbdReplyType enum + * @param submitted_text UTF-16 encoded string + * @param cursor_position The current position of the text cursor + */ + void SubmitTextInline(SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position); + +private: + /// Initializes the normal software keyboard. + void InitializeForeground(); + + /// Initializes the inline software keyboard. + void InitializeBackground(LibraryAppletMode applet_mode); + + /// Processes the text check sent by the application. + void ProcessTextCheck(); + + /// Processes the inline software keyboard request command sent by the application. + void ProcessInlineKeyboardRequest(); + + /// Submits the input text and exits the applet. + void SubmitNormalOutputAndExit(SwkbdResult result, std::u16string submitted_text); + + /// Submits the input text for text checking. + void SubmitForTextCheck(std::u16string submitted_text); + + /// Sends a reply to the application after processing a request command. + void SendReply(SwkbdReplyType reply_type); + + /// Changes the inline keyboard state. + void ChangeState(SwkbdState state); + + /** + * Signals the frontend to initialize the software keyboard with common parameters. + * This initializes either the normal software keyboard or the inline software keyboard + * depending on the state of is_background. + * Note that this does not cause the keyboard to appear. + * Use the respective Show*Keyboard() functions to cause the respective keyboards to appear. + */ + void InitializeFrontendKeyboard(); + + /// Signals the frontend to show the normal software keyboard. + void ShowNormalKeyboard(); + + /// Signals the frontend to show the text check dialog. + void ShowTextCheckDialog(SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + /// Signals the frontend to show the inline software keyboard. + void ShowInlineKeyboard(); + + /// Signals the frontend to hide the inline software keyboard. + void HideInlineKeyboard(); + + /// Signals the frontend that the current inline keyboard text has changed. + void InlineTextChanged(); + + /// Signals both the frontend and application that the software keyboard is exiting. + void ExitKeyboard(); + + // Inline Software Keyboard Requests + + void RequestFinalize(const std::vector& request_data); + void RequestSetUserWordInfo(const std::vector& request_data); + void RequestSetCustomizeDic(const std::vector& request_data); + void RequestCalc(const std::vector& request_data); + void RequestSetCustomizedDictionaries(const std::vector& request_data); + void RequestUnsetCustomizedDictionaries(const std::vector& request_data); + void RequestSetChangedStringV2Flag(const std::vector& request_data); + void RequestSetMovedCursorV2Flag(const std::vector& request_data); + + // Inline Software Keyboard Replies + + void ReplyFinishedInitialize(); + void ReplyDefault(); + void ReplyChangedString(); + void ReplyMovedCursor(); + void ReplyMovedTab(); + void ReplyDecidedEnter(); + void ReplyDecidedCancel(); + void ReplyChangedStringUtf8(); + void ReplyMovedCursorUtf8(); + void ReplyDecidedEnterUtf8(); + void ReplyUnsetCustomizeDic(); + void ReplyReleasedUserWordInfo(); + void ReplyUnsetCustomizedDictionaries(); + void ReplyChangedStringV2(); + void ReplyMovedCursorV2(); + void ReplyChangedStringUtf8V2(); + void ReplyMovedCursorUtf8V2(); + + LibraryAppletMode applet_mode; + Core::Frontend::SoftwareKeyboardApplet& frontend; Core::System& system; + + SwkbdAppletVersion swkbd_applet_version; + + SwkbdConfigCommon swkbd_config_common; + SwkbdConfigOld swkbd_config_old; + SwkbdConfigOld2 swkbd_config_old2; + SwkbdConfigNew swkbd_config_new; + std::u16string initial_text; + + SwkbdState swkbd_state{SwkbdState::NotInitialized}; + SwkbdInitializeArg swkbd_initialize_arg; + SwkbdCalcArg swkbd_calc_arg; + bool use_changed_string_v2{false}; + bool use_moved_cursor_v2{false}; + bool inline_use_utf8{false}; + s32 current_cursor_position{}; + + std::u16string current_text; + + bool is_background{false}; + + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard_types.h b/src/core/hle/service/am/applets/software_keyboard_types.h new file mode 100644 index 0000000000..21aa8e8005 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard_types.h @@ -0,0 +1,295 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +constexpr std::size_t MAX_OK_TEXT_LENGTH = 8; +constexpr std::size_t MAX_HEADER_TEXT_LENGTH = 64; +constexpr std::size_t MAX_SUB_TEXT_LENGTH = 128; +constexpr std::size_t MAX_GUIDE_TEXT_LENGTH = 256; +constexpr std::size_t STRING_BUFFER_SIZE = 0x7D4; + +enum class SwkbdAppletVersion : u32_le { + Version5 = 0x5, // 1.0.0 + Version65542 = 0x10006, // 2.0.0 - 2.3.0 + Version196615 = 0x30007, // 3.0.0 - 3.0.2 + Version262152 = 0x40008, // 4.0.0 - 4.1.0 + Version327689 = 0x50009, // 5.0.0 - 5.1.0 + Version393227 = 0x6000B, // 6.0.0 - 7.0.1 + Version524301 = 0x8000D, // 8.0.0+ +}; + +enum class SwkbdType : u32 { + Normal, + NumberPad, + Qwerty, + Unknown3, + Latin, + SimplifiedChinese, + TraditionalChinese, + Korean, +}; + +enum class SwkbdInitialCursorPosition : u32 { + Start, + End, +}; + +enum class SwkbdPasswordMode : u32 { + Disabled, + Enabled, +}; + +enum class SwkbdTextDrawType : u32 { + Line, + Box, + DownloadCode, +}; + +enum class SwkbdResult : u32 { + Ok, + Cancel, +}; + +enum class SwkbdTextCheckResult : u32 { + Success, + Failure, + Confirm, + Silent, +}; + +enum class SwkbdState : u32 { + NotInitialized = 0x0, + InitializedIsHidden = 0x1, + InitializedIsAppearing = 0x2, + InitializedIsShown = 0x3, + InitializedIsDisappearing = 0x4, +}; + +enum class SwkbdRequestCommand : u32 { + Finalize = 0x4, + SetUserWordInfo = 0x6, + SetCustomizeDic = 0x7, + Calc = 0xA, + SetCustomizedDictionaries = 0xB, + UnsetCustomizedDictionaries = 0xC, + SetChangedStringV2Flag = 0xD, + SetMovedCursorV2Flag = 0xE, +}; + +enum class SwkbdReplyType : u32 { + FinishedInitialize = 0x0, + Default = 0x1, + ChangedString = 0x2, + MovedCursor = 0x3, + MovedTab = 0x4, + DecidedEnter = 0x5, + DecidedCancel = 0x6, + ChangedStringUtf8 = 0x7, + MovedCursorUtf8 = 0x8, + DecidedEnterUtf8 = 0x9, + UnsetCustomizeDic = 0xA, + ReleasedUserWordInfo = 0xB, + UnsetCustomizedDictionaries = 0xC, + ChangedStringV2 = 0xD, + MovedCursorV2 = 0xE, + ChangedStringUtf8V2 = 0xF, + MovedCursorUtf8V2 = 0x10, +}; + +struct SwkbdKeyDisableFlags { + union { + u32 raw{}; + + BitField<1, 1, u32> space; + BitField<2, 1, u32> at; + BitField<3, 1, u32> percent; + BitField<4, 1, u32> slash; + BitField<5, 1, u32> backslash; + BitField<6, 1, u32> numbers; + BitField<7, 1, u32> download_code; + BitField<8, 1, u32> username; + }; +}; +static_assert(sizeof(SwkbdKeyDisableFlags) == 0x4, "SwkbdKeyDisableFlags has incorrect size."); + +struct SwkbdConfigCommon { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + INSERT_PADDING_BYTES(1); + SwkbdKeyDisableFlags key_disable_flags{}; + SwkbdInitialCursorPosition initial_cursor_position{}; + std::array header_text{}; + std::array sub_text{}; + std::array guide_text{}; + u32 max_text_length{}; + u32 min_text_length{}; + SwkbdPasswordMode password_mode{}; + SwkbdTextDrawType text_draw_type{}; + bool enable_return_button{}; + bool use_utf8{}; + bool use_blur_background{}; + INSERT_PADDING_BYTES(1); + u32 initial_string_offset{}; + u32 initial_string_length{}; + u32 user_dictionary_offset{}; + u32 user_dictionary_entries{}; + bool use_text_check{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdConfigCommon) == 0x3D4, "SwkbdConfigCommon has incorrect size."); + +#pragma pack(push, 4) +// SwkbdAppletVersion 0x5, 0x10006 +struct SwkbdConfigOld { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; +}; +static_assert(sizeof(SwkbdConfigOld) == 0x3E0 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld has incorrect size."); + +// SwkbdAppletVersion 0x30007, 0x40008, 0x50009 +struct SwkbdConfigOld2 { + INSERT_PADDING_WORDS(1); + VAddr text_check_callback{}; + std::array text_grouping{}; +}; +static_assert(sizeof(SwkbdConfigOld2) == 0x400 - sizeof(SwkbdConfigCommon), + "SwkbdConfigOld2 has incorrect size."); + +// SwkbdAppletVersion 0x6000B, 0x8000D +struct SwkbdConfigNew { + std::array text_grouping{}; + std::array customized_dictionary_set_entries{}; + u8 total_customized_dictionary_set_entries{}; + bool disable_cancel_button{}; + INSERT_PADDING_BYTES(18); +}; +static_assert(sizeof(SwkbdConfigNew) == 0x4C8 - sizeof(SwkbdConfigCommon), + "SwkbdConfigNew has incorrect size."); +#pragma pack(pop) + +struct SwkbdTextCheck { + SwkbdTextCheckResult text_check_result{}; + std::array text_check_message{}; +}; +static_assert(sizeof(SwkbdTextCheck) == 0x7D8, "SwkbdTextCheck has incorrect size."); + +struct SwkbdCalcArgFlags { + union { + u64 raw{}; + + BitField<0, 1, u64> set_initialize_arg; + BitField<1, 1, u64> set_volume; + BitField<2, 1, u64> appear; + BitField<3, 1, u64> set_input_text; + BitField<4, 1, u64> set_cursor_position; + BitField<5, 1, u64> set_utf8_mode; + BitField<6, 1, u64> unset_customize_dic; + BitField<7, 1, u64> disappear; + BitField<8, 1, u64> unknown; + BitField<9, 1, u64> set_key_top_translate_scale; + BitField<10, 1, u64> unset_user_word_info; + BitField<11, 1, u64> set_disable_hardware_keyboard; + }; +}; +static_assert(sizeof(SwkbdCalcArgFlags) == 0x8, "SwkbdCalcArgFlags has incorrect size."); + +struct SwkbdInitializeArg { + u32 unknown{}; + bool library_applet_mode_flag{}; + bool is_above_hos_500{}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(SwkbdInitializeArg) == 0x8, "SwkbdInitializeArg has incorrect size."); + +struct SwkbdAppearArg { + SwkbdType type{}; + std::array ok_text{}; + char16_t left_optional_symbol_key{}; + char16_t right_optional_symbol_key{}; + bool use_prediction{}; + bool disable_cancel_button{}; + SwkbdKeyDisableFlags key_disable_flags{}; + u32 max_text_length{}; + u32 min_text_length{}; + bool enable_return_button{}; + INSERT_PADDING_BYTES(3); + u32 flags{}; + INSERT_PADDING_WORDS(6); +}; +static_assert(sizeof(SwkbdAppearArg) == 0x48, "SwkbdAppearArg has incorrect size."); + +struct SwkbdCalcArg { + u32 unknown{}; + u16 calc_arg_size{}; + INSERT_PADDING_BYTES(2); + SwkbdCalcArgFlags flags{}; + SwkbdInitializeArg initialize_arg{}; + f32 volume{}; + s32 cursor_position{}; + SwkbdAppearArg appear_arg{}; + std::array input_text{}; + bool utf8_mode{}; + INSERT_PADDING_BYTES(1); + bool enable_backspace_button{}; + INSERT_PADDING_BYTES(3); + bool key_top_as_floating{}; + bool footer_scalable{}; + bool alpha_enabled_in_input_mode{}; + u8 input_mode_fade_type{}; + bool disable_touch{}; + bool disable_hardware_keyboard{}; + INSERT_PADDING_BYTES(8); + f32 key_top_scale_x{}; + f32 key_top_scale_y{}; + f32 key_top_translate_x{}; + f32 key_top_translate_y{}; + f32 key_top_bg_alpha{}; + f32 footer_bg_alpha{}; + f32 balloon_scale{}; + INSERT_PADDING_WORDS(4); + u8 se_group{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(SwkbdCalcArg) == 0x4A0, "SwkbdCalcArg has incorrect size."); + +struct SwkbdChangedStringArg { + u32 text_length{}; + s32 dictionary_start_cursor_position{}; + s32 dictionary_end_cursor_position{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdChangedStringArg) == 0x10, "SwkbdChangedStringArg has incorrect size."); + +struct SwkbdMovedCursorArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedCursorArg) == 0x8, "SwkbdMovedCursorArg has incorrect size."); + +struct SwkbdMovedTabArg { + u32 text_length{}; + s32 cursor_position{}; +}; +static_assert(sizeof(SwkbdMovedTabArg) == 0x8, "SwkbdMovedTabArg has incorrect size."); + +struct SwkbdDecidedEnterArg { + u32 text_length{}; +}; +static_assert(sizeof(SwkbdDecidedEnterArg) == 0x4, "SwkbdDecidedEnterArg has incorrect size."); + +} // namespace Service::AM::Applets From 578e6c5a57bc29aed27e604ac0ede34f87bae86d Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 16 Mar 2021 13:01:03 -0400 Subject: [PATCH 07/14] applets/swkbd: Implement the Default Software Keyboard frontend --- .../frontend/applets/software_keyboard.cpp | 140 +++++++++++++++++- src/core/frontend/applets/software_keyboard.h | 98 +++++++++++- 2 files changed, 236 insertions(+), 2 deletions(-) diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp index 73e7a89b99..12c76c9ee0 100644 --- a/src/core/frontend/applets/software_keyboard.cpp +++ b/src/core/frontend/applets/software_keyboard.cpp @@ -1,11 +1,149 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include + +#include "common/logging/log.h" +#include "common/string_util.h" #include "core/frontend/applets/software_keyboard.h" namespace Core::Frontend { SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; +DefaultSoftwareKeyboardApplet::~DefaultSoftwareKeyboardApplet() = default; + +void DefaultSoftwareKeyboardApplet::InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + LOG_WARNING( + Service_AM, + "(STUBBED) called, backend requested to initialize the inline software keyboard."); + + submit_inline_callback = std::move(submit_inline_callback_); + } else { + LOG_WARNING( + Service_AM, + "(STUBBED) called, backend requested to initialize the normal software keyboard."); + + submit_normal_callback = std::move(submit_normal_callback_); + } + + parameters = std::move(initialize_parameters); + + 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(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), + Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), + Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, + parameters.min_text_length, parameters.initial_cursor_position, parameters.type, + parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, + parameters.use_blur_background, parameters.enable_backspace_button, + parameters.enable_return_button, parameters.disable_cancel_button); +} + +void DefaultSoftwareKeyboardApplet::ShowNormalKeyboard() const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to show the normal software keyboard."); + + SubmitNormalText(u"yuzu"); +} + +void DefaultSoftwareKeyboardApplet::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to show the text check dialog."); +} + +void DefaultSoftwareKeyboardApplet::ShowInlineKeyboard( + InlineAppearParameters appear_parameters) const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to show the inline software keyboard."); + + 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); + + std::thread([this] { SubmitInlineText(u"yuzu"); }).detach(); +} + +void DefaultSoftwareKeyboardApplet::HideInlineKeyboard() const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to hide the inline software keyboard."); +} + +void DefaultSoftwareKeyboardApplet::InlineTextChanged(InlineTextParameters text_parameters) const { + LOG_WARNING(Service_AM, + "(STUBBED) called, backend requested to change the inline keyboard text."); + + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, + text_parameters.input_text, text_parameters.cursor_position); +} + +void DefaultSoftwareKeyboardApplet::ExitKeyboard() const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to exit the software keyboard."); +} + +void DefaultSoftwareKeyboardApplet::SubmitNormalText(std::u16string text) const { + submit_normal_callback(Service::AM::Applets::SwkbdResult::Ok, text); +} + +void DefaultSoftwareKeyboardApplet::SubmitInlineText(std::u16string_view text) const { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + for (std::size_t index = 0; index < text.size(); ++index) { + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::ChangedString, + std::u16string(text.data(), text.data() + index + 1), + static_cast(index) + 1); + + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + + submit_inline_callback(Service::AM::Applets::SwkbdReplyType::DecidedEnter, std::u16string(text), + static_cast(text.size())); +} + } // namespace Core::Frontend diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h index 54528837ee..506eb35bb1 100644 --- a/src/core/frontend/applets/software_keyboard.h +++ b/src/core/frontend/applets/software_keyboard.h @@ -1,20 +1,116 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include +#include + #include "common/common_types.h" +#include "core/hle/service/am/applets/software_keyboard_types.h" + namespace Core::Frontend { +struct KeyboardInitializeParameters { + std::u16string ok_text; + std::u16string header_text; + std::u16string sub_text; + std::u16string guide_text; + std::u16string initial_text; + u32 max_text_length; + u32 min_text_length; + s32 initial_cursor_position; + Service::AM::Applets::SwkbdType type; + Service::AM::Applets::SwkbdPasswordMode password_mode; + Service::AM::Applets::SwkbdTextDrawType text_draw_type; + Service::AM::Applets::SwkbdKeyDisableFlags key_disable_flags; + bool use_blur_background; + bool enable_backspace_button; + bool enable_return_button; + bool disable_cancel_button; +}; + +struct InlineAppearParameters { + u32 max_text_length; + u32 min_text_length; + f32 key_top_scale_x; + f32 key_top_scale_y; + f32 key_top_translate_x; + f32 key_top_translate_y; + Service::AM::Applets::SwkbdType type; + Service::AM::Applets::SwkbdKeyDisableFlags key_disable_flags; + bool key_top_as_floating; + bool enable_backspace_button; + bool enable_return_button; + bool disable_cancel_button; +}; + +struct InlineTextParameters { + std::u16string input_text; + s32 cursor_position; +}; + class SoftwareKeyboardApplet { public: virtual ~SoftwareKeyboardApplet(); + + virtual void InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) = 0; + + virtual void ShowNormalKeyboard() const = 0; + + virtual void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const = 0; + + virtual void ShowInlineKeyboard(InlineAppearParameters appear_parameters) const = 0; + + virtual void HideInlineKeyboard() const = 0; + + virtual void InlineTextChanged(InlineTextParameters text_parameters) const = 0; + + virtual void ExitKeyboard() const = 0; }; class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { public: + ~DefaultSoftwareKeyboardApplet() override; + + void InitializeKeyboard( + bool is_inline, KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard(InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +private: + void SubmitNormalText(std::u16string text) const; + void SubmitInlineText(std::u16string_view text) const; + + KeyboardInitializeParameters parameters; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; }; } // namespace Core::Frontend From e681723a4a5c62e2cecb9f1a62fa544a79fddd6d Mon Sep 17 00:00:00 2001 From: Its-Rei Date: Tue, 16 Mar 2021 14:24:19 -0400 Subject: [PATCH 08/14] icons: Add icons for the On-Screen Keyboard overlay --- dist/icons/overlay/arrow_left.png | Bin 0 -> 1490 bytes dist/icons/overlay/arrow_left_dark.png | Bin 0 -> 712 bytes dist/icons/overlay/arrow_right.png | Bin 0 -> 1394 bytes dist/icons/overlay/arrow_right_dark.png | Bin 0 -> 683 bytes dist/icons/overlay/button_A.png | Bin 0 -> 3494 bytes dist/icons/overlay/button_A_dark.png | Bin 0 -> 3167 bytes dist/icons/overlay/button_B.png | Bin 0 -> 3375 bytes dist/icons/overlay/button_B_dark.png | Bin 0 -> 2975 bytes dist/icons/overlay/button_L.png | Bin 0 -> 796 bytes dist/icons/overlay/button_L_dark.png | Bin 0 -> 745 bytes dist/icons/overlay/button_R.png | Bin 0 -> 1841 bytes dist/icons/overlay/button_R_dark.png | Bin 0 -> 1835 bytes dist/icons/overlay/button_X.png | Bin 0 -> 3968 bytes dist/icons/overlay/button_X_dark.png | Bin 0 -> 3530 bytes dist/icons/overlay/button_Y.png | Bin 0 -> 3337 bytes dist/icons/overlay/button_Y_dark.png | Bin 0 -> 2883 bytes dist/icons/overlay/button_minus.png | Bin 0 -> 2401 bytes dist/icons/overlay/button_minus_dark.png | Bin 0 -> 1969 bytes dist/icons/overlay/button_plus.png | Bin 0 -> 2497 bytes dist/icons/overlay/button_plus_dark.png | Bin 0 -> 2066 bytes dist/icons/overlay/button_press_stick.png | Bin 0 -> 5225 bytes .../icons/overlay/button_press_stick_dark.png | Bin 0 -> 3636 bytes dist/icons/overlay/controller_dual_joycon.png | Bin 0 -> 7312 bytes .../overlay/controller_dual_joycon_dark.png | Bin 0 -> 5889 bytes dist/icons/overlay/controller_handheld.png | Bin 0 -> 4645 bytes .../overlay/controller_handheld_dark.png | Bin 0 -> 3745 bytes dist/icons/overlay/controller_pro.png | Bin 0 -> 9493 bytes dist/icons/overlay/controller_pro_dark.png | Bin 0 -> 7488 bytes .../overlay/controller_single_joycon_left.png | Bin 0 -> 7489 bytes .../controller_single_joycon_left_a.png | Bin 0 -> 2609 bytes .../controller_single_joycon_left_a_dark.png | Bin 0 -> 2564 bytes .../controller_single_joycon_left_b.png | Bin 0 -> 2559 bytes .../controller_single_joycon_left_b_dark.png | Bin 0 -> 2383 bytes .../controller_single_joycon_left_dark.png | Bin 0 -> 6768 bytes .../controller_single_joycon_left_x.png | Bin 0 -> 2541 bytes .../controller_single_joycon_left_x_dark.png | Bin 0 -> 2392 bytes .../controller_single_joycon_left_y.png | Bin 0 -> 2641 bytes .../controller_single_joycon_left_y_dark.png | Bin 0 -> 2639 bytes .../controller_single_joycon_right.png | Bin 0 -> 7497 bytes .../controller_single_joycon_right_dark.png | Bin 0 -> 6729 bytes dist/icons/overlay/osk_button_B.png | Bin 0 -> 741 bytes dist/icons/overlay/osk_button_B_dark.png | Bin 0 -> 767 bytes .../overlay/osk_button_B_dark_disabled.png | Bin 0 -> 781 bytes dist/icons/overlay/osk_button_B_disabled.png | Bin 0 -> 791 bytes dist/icons/overlay/osk_button_Y.png | Bin 0 -> 726 bytes dist/icons/overlay/osk_button_Y_dark.png | Bin 0 -> 502 bytes .../overlay/osk_button_Y_dark_disabled.png | Bin 0 -> 694 bytes dist/icons/overlay/osk_button_Y_disabled.png | Bin 0 -> 699 bytes dist/icons/overlay/osk_button_backspace.png | Bin 0 -> 2919 bytes .../overlay/osk_button_backspace_dark.png | Bin 0 -> 2958 bytes dist/icons/overlay/osk_button_plus.png | Bin 0 -> 626 bytes dist/icons/overlay/osk_button_plus_dark.png | Bin 0 -> 676 bytes .../overlay/osk_button_plus_dark_disabled.png | Bin 0 -> 645 bytes .../overlay/osk_button_plus_disabled.png | Bin 0 -> 664 bytes dist/icons/overlay/osk_button_shift.png | Bin 0 -> 1876 bytes dist/icons/overlay/osk_button_shift_dark.png | Bin 0 -> 2003 bytes .../overlay/osk_button_shift_lock_off.png | Bin 0 -> 281 bytes .../overlay/osk_button_shift_lock_on.png | Bin 0 -> 274 bytes dist/icons/overlay/osk_button_shift_on.png | Bin 0 -> 1573 bytes .../overlay/osk_button_shift_on_dark.png | Bin 0 -> 1937 bytes dist/icons/overlay/overlay.qrc | 64 ++++++++++++++++++ 61 files changed, 64 insertions(+) create mode 100644 dist/icons/overlay/arrow_left.png create mode 100644 dist/icons/overlay/arrow_left_dark.png create mode 100644 dist/icons/overlay/arrow_right.png create mode 100644 dist/icons/overlay/arrow_right_dark.png create mode 100644 dist/icons/overlay/button_A.png create mode 100644 dist/icons/overlay/button_A_dark.png create mode 100644 dist/icons/overlay/button_B.png create mode 100644 dist/icons/overlay/button_B_dark.png create mode 100644 dist/icons/overlay/button_L.png create mode 100644 dist/icons/overlay/button_L_dark.png create mode 100644 dist/icons/overlay/button_R.png create mode 100644 dist/icons/overlay/button_R_dark.png create mode 100644 dist/icons/overlay/button_X.png create mode 100644 dist/icons/overlay/button_X_dark.png create mode 100644 dist/icons/overlay/button_Y.png create mode 100644 dist/icons/overlay/button_Y_dark.png create mode 100644 dist/icons/overlay/button_minus.png create mode 100644 dist/icons/overlay/button_minus_dark.png create mode 100644 dist/icons/overlay/button_plus.png create mode 100644 dist/icons/overlay/button_plus_dark.png create mode 100644 dist/icons/overlay/button_press_stick.png create mode 100644 dist/icons/overlay/button_press_stick_dark.png create mode 100644 dist/icons/overlay/controller_dual_joycon.png create mode 100644 dist/icons/overlay/controller_dual_joycon_dark.png create mode 100644 dist/icons/overlay/controller_handheld.png create mode 100644 dist/icons/overlay/controller_handheld_dark.png create mode 100644 dist/icons/overlay/controller_pro.png create mode 100644 dist/icons/overlay/controller_pro_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_left.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_a.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_a_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_b.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_b_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_x.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_x_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_y.png create mode 100644 dist/icons/overlay/controller_single_joycon_left_y_dark.png create mode 100644 dist/icons/overlay/controller_single_joycon_right.png create mode 100644 dist/icons/overlay/controller_single_joycon_right_dark.png create mode 100644 dist/icons/overlay/osk_button_B.png create mode 100644 dist/icons/overlay/osk_button_B_dark.png create mode 100644 dist/icons/overlay/osk_button_B_dark_disabled.png create mode 100644 dist/icons/overlay/osk_button_B_disabled.png create mode 100644 dist/icons/overlay/osk_button_Y.png create mode 100644 dist/icons/overlay/osk_button_Y_dark.png create mode 100644 dist/icons/overlay/osk_button_Y_dark_disabled.png create mode 100644 dist/icons/overlay/osk_button_Y_disabled.png create mode 100644 dist/icons/overlay/osk_button_backspace.png create mode 100644 dist/icons/overlay/osk_button_backspace_dark.png create mode 100644 dist/icons/overlay/osk_button_plus.png create mode 100644 dist/icons/overlay/osk_button_plus_dark.png create mode 100644 dist/icons/overlay/osk_button_plus_dark_disabled.png create mode 100644 dist/icons/overlay/osk_button_plus_disabled.png create mode 100644 dist/icons/overlay/osk_button_shift.png create mode 100644 dist/icons/overlay/osk_button_shift_dark.png create mode 100644 dist/icons/overlay/osk_button_shift_lock_off.png create mode 100644 dist/icons/overlay/osk_button_shift_lock_on.png create mode 100644 dist/icons/overlay/osk_button_shift_on.png create mode 100644 dist/icons/overlay/osk_button_shift_on_dark.png create mode 100644 dist/icons/overlay/overlay.qrc diff --git a/dist/icons/overlay/arrow_left.png b/dist/icons/overlay/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d4fecfe603214504b78e3c27344d5414a50463 GIT binary patch literal 1490 zcmV;@1ugoCP)IM%rVPWl-7mjh@T>jPPwz^6bCpeW#rKYj}${gs91;PbZ9v2Aj zQv@-_K851AKsXb;gZqMHeJP*MKhcl4K=2bxjImD;6I>wJ3EshdQL?`8ae?4~UEvbZ z%*e>d$$rNLf&q>#F5v$fOqOL`Moe)5e)k6-_; zB-1o60eD%mUP)ZQp9v<@G%pd6EbceiZ1!;#aRK0f$Im0i*hR=suF|-Gw-daB`-)_} z^00`fuy?`eVz>%bOnk{>a7)V7Iv6)$8>Wali$7{~Sz=v5%49?wg+8 zY8~9;al-|=Tp~= zB)FcW3D~l%vqFfsC0~DYXlN)|7TRRb65$dzN#|_sPH=+jPtpKjn&x`|-U+!E6(drn zX}(KD39e5`0r)fQCR9$^w(SK?)BYl&hr@10!H6^f*tWf(>pB2D7-~D}MeOeE;hg_$ z7)BkyLm@V#T13;I%9drF7DAktdbJS00C=QQsnk4gO_4P1P2-#|=(2o~KhF7lg6vK9-CgMxnTRGeO`A!O_onUkRX4~0666-?xIdV4KCfw7 ziijSNc-}!Gnj9S+Ev>GuD!r*sHc}g~ZQFK*EQCmqok;I+PmsHdt0M#nvirC`qtONO z;Xv$C?H%*i1)>FN-SgPfK`E;JgABXLFU`jnAv+j9wW=k#=pD?#p@p3li8$nE3F zK}6fO=M2L*0N|KJd7;s094-_JvvEN7?4&2>JV9;|Pmj_j$j$Nmta5_9H@+MO?*=)S z%gwB>uPcY_+vT8~^EqADGmu0c7#KLBBC@ZyuX4_RiaWCJ*V}f1tkiN5$o;`Ck;ha* z4(x(v2YCR%QHgS8kORE|JC4YKUoq|$Ih)Pim&@g5`xQBqJKW=h9NH!M4ssTXBZvAe z1f269RYwl}YaUcb4jmX!8#xldfV#+$0S2~_HBGyVh$bb<$1<7Bp~1nyNFzrI7yxYB zmPRI`do!8Lq2b}-nYFdGNPb~S+- z8#0qjL}!+kmOc-vFSm&-FtEE^+`SL70PuRbT>dENzT70zzyM&|_75$PcW^I;)t77{ s4-7OR_YH2pg&1QO7-O>cWJV(Y0pEG)`=#zJdjJ3c07*qoM6N<$f`{eApa1{> literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_left_dark.png b/dist/icons/overlay/arrow_left_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f73672a591fcf7a160735609f510818cceaf9ab7 GIT binary patch literal 712 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8khhnmSQK*5Dp-y;YjHK@;M7UB8wRq z_>O=u<5X=vX`rBFiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}CuAVNA zAr*7p-m&ZxaTI8M_(DoR!9sBQ1BL=NgWHUmV$%fqem^p7IQVX3b%FYxnLiCZ9GNdK zQd+Luth4w2>uvMwre0ioN^7$p)3pZK2RYxSoeV9Q|7mfo;`$9uzgzC#W<8gh|9YS5 z4yQO**6$yMVie_<)yM2)SDb(D-W}fq29Hj~z7aG?dH-%v)9%LyYT4GLzJI6mkd5t3 zLp$G{=LaUfxf`o;=K$9`X7+d75Bq#}G>YE&eTa7j)AywX?qLP*In5OwvV}GN_S&H= zoR`4*eO&?bsspvWGH37I1zOh!<|*HqD0=7iAzlxV=4ie<+Yf96ni|sh+i!=m_73w_ zX^{*`ao4L5s9R!PUm!j283u?wk$cxbASYEVSpkE?5u=W^rB@+@N}A zfmVTkO8&q3vvunjww-yCbZMIAw(p-`>@7%^I&-GMS2)juc|&5_^iAzPTMwSum)bw0 z=x~nN?!2_mZxn-b4UDxk|NMioQSpND0%fQN7?kFglbKHH`?+=!2 zxwC*PWp4iGHouHx>QC4#Czejwtp-$J`(AMV1nDO(*OC)WId?j~czN*$SSQ5D5Bonj zpSY(r;cJ*^{(;_!2Yw%z`DSmd*(a}S!a?nwPuKI`X4gx3Kih>}fxG;wt^&8YQr%wl g&o$q~{O>a|@GvwjJ-llQFx4@5y85}Sb4q9e0Mvs#3IG5A literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_right.png b/dist/icons/overlay/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..e47ee94bd8984106329e427b0d42b585eaa53b87 GIT binary patch literal 1394 zcmV-&1&#WNP)@&7SV-vGmA#=N^77#$ zM~+;%apT5H+I{U5CxGkq`g_1dpg+06^78T_7m%$7?%`fw4R>(J1>{oN4Q!WfaQk_` z)9L&&J3DLbpjg{j5CkVhsTd7nI08<9#Q@-yX znx3A%(r&k{Lbetdh?G+EzV9CZjv3U~wbuK!)>pA;lC9o@ftAV0$q#{_O^QY2gCGcg z!lL(O^8^FXZnvZ9>FKMTPRD4HMP$nN{ryTQD<;``U>G3Jn<4|pZ9#Sd7&aik030<~ za@_a*s#5A2DIz-q3~nMj1q|*XI|mFKkYB_c`METZodkvf^6xQ_UrqzrSzs6%`B|mZ zb;gA3G%&0oYprV{(lA&uR%GXaVHJ61X2we7OaQ|g^3u}MOMnq_rBZpOw+7TLC6mCgid?N$=eyl*9hlsy zUPKZ>&MYv%&6_t@s@3Y{ZnxV24)0ViB2C}-pXz4DpeHSA5@pQYrPvD9)dG zU>FMdM5R)BVid@^0ESg$&+~o*j*5sW@yVEF2Mw0IYJywh*f+fAP#%z750h)BXc%{-~UxoDPEE zZ2v(4oIph6Ory~_2gMeVbj}tNoM1$xTL32%5jnH4u<&&OoIv`xP<+UZJR}^*!9LHawS1Ywl+hxKLawZ<@Hcz?WedK7b3w1V4}=anEjb zu$bU=X*Qet?%usS54>);WLsP)CU}rOu852b7Ybk?Ib0}!W8+3qbZ&8R@pIGH&-H~y ztJT_@BrfE5f?KWD-aB{h%mZ(j)DMFTIRzdUF63~Fh>I&C-wcZjIh){Na2FO9K6{vR z<_0(oT*%3lT&-5yy|S`$33$t-epI-Sg9)zHYPcZMbB^ZTE*wrh(JOg$x7R zfD0J~wgne52y7EBWDK~6dkHvYxa7Mq3{NN7Q)YmJAlM}$b6A8683DEx7cu~BGcGs| zJOu9fFbqFQvZwpwEU*i>;3%*wxZvRL!9CnKU&T_x1;-|M9qv1ZOH#!Jhk)baMp5+r z;^N}R3AeRloB(ze7aZ7wgCN*dDwTf4L0qtWg8R5p6uoOwZyPRH44egAuy~6QkyEC) zzS!Qz{qN~wQ5&$}%mZ*Er?MbQto;)2D%09xz6Jz>%07*qoM6N<$f{avv AVE_OC literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/arrow_right_dark.png b/dist/icons/overlay/arrow_right_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..91cf83d2c57ecff0626bf14e3ed0d7f0ecf40c65 GIT binary patch literal 683 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8khhnmSQK*5Dp-y;YjHK@;M7UB8wRq z_>O=u<5X=vX`rBFiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}C8lEnW zAr*7p-Z||j;waPlQ8R#N-=zG+Mdus26U~ooa12P)tk|!x>*GG=`KMcFoNv9_si=7Q z|0e&?t~vSFcTbMDnmhALs@ZFe2Vw;*F`0XxUYc51|8YCN+dP8@-|Tldzwfni6n`L9 z*(LUXPnP-g+U;fH1TXmOm#02i?5?-Ri^T=^VNHdDmpm3%X^r zVOgbDX13Yq^&GCVnb$=N6v+M%{IS?DfA7xOJ=$v+$~S*~mS?nXLr-;9jN>uUw3tNJ z9kkNKfH`vkdF%b*Hf0k!*bZ z{>2~v1G5T`#x~^K(X?)V!+%us)b%Ip&fT#u=KJ#X`|)eGz=Xx%>FVdQ&MBb@08D%_ AHvj+t literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_A.png b/dist/icons/overlay/button_A.png new file mode 100644 index 0000000000000000000000000000000000000000..fd90f8b42a3c5d2bbcd4a446bb71690e672a6707 GIT binary patch literal 3494 zcmY*cc{tSF7yruG4aSyz-=fC8^a|0~Vk|LUL)MU;vXhXZp=^nrndG0;Wz4zR6?mhQ&?maih$-x@REx`={0Myn7aS7zH{}oPl zuy(BwRRtL&#=_Q>6QmoQ0a&oj6>Wo#0RX;N{}q-J4L(V*Q#{tvJ=P^EC^o_GdLWRH zkN^*f42!wq7aa(Xx_-5C>zo8=LD3dr?wVLde-IWfJS#GCe04YoYT(;O-rF7iFebJ_3-#1A9^3KE$-;ZwMd-P{%7VI zD+<`&`puT%rl&v5+vgDexpRAKDQS-6XwiCgyDBCAIAy!4J7%jtJ@fx4 zsc@Q44C(Fd?RN3v+e<(asmcyv$MS(i&SXEz>Sr48FV~94&*MsfTGm~H^$60&sfWnfTkcxM$DN4gnYP-9;=ZjU4`|RmYfEWIqX3h>Qi6+9_{qt1n z{v#2-yT<>S!C}`qfEQ5T-07oNwYc&GAuQm4n*31le~kIXaFrzK+*y>$EF)@@&NSYUHdd z0|{vQ9VKcR6OE+&I^nCR-uAGTytOL=(4PEy%~{685@-dI*T$|GoA-N1iRcdG9!VbM z*sH=!V4RR6B}z*K#H%YD9SW+BSJ;Pzui}VW>B~9{etjg*s8;YDO}Lrau>%UVoRzwz zip;_~=)|D3#KIk^rM$QA$tU->W{)bKGg$Rk5}co!nu>*sE0%I^>?ZdEe7X!Njl(t= zy@!A^FfgdLok~}rR3`2u_tVO{-@{@!&*s)k?J;kJ3F`aW@_@cJ1yq3m59ec}T07_a zyEg}pWBXJq;4LGc(U4lc!*L~Pe@Y#GV`o( zoTZ}{U7{!PyFZRV<5fwGWTFQKZi-1^gugTs^78Ue95JH$^3DeXY5 z3$wep5%qvz9&n=tS`3=i3``S&kv5L^U9*3>JV$P@{yr=jCANvW9L~j_88wnj(K0`& z>3uHZ$Lc6DUXn{)O>L0i)r%SP8MrB3X&7atru4-R%M0(lOz0~x-EWC$G=$QwgA4X< zt1JVY&}i|$nIw{Zt5@%gT$JFa{OaoJn&D)MfH{M~zyZGru&!cx6BB9L>_`pd`${Ii z#0v700NI5Md+w=)a6+MK*lH#GjKvKis1V$k$X=4ArDd66R5go?vOPDZ%kg1Dhw5=rFD@nM@yW?2k+Sz3b)q{O}mSjNV4pG$~2&<{^@jy7fGDeAh%wYofjlE>A)N*Q^8Q?~PWSjd5~!S9|A;Mx!mB zn_3CxAY{Z7&mbq(13VX;(P*yvr`ATs#@14{y==zp)`*`wgJGv`i3$sovY@&!SCnN) z$Rnru2gV8Yy}h0-tOJ6`D^9mR1 z>lIGB%cXqFUY&^R7b~3oLcLyEwtoIbM8p}3IsrbJb86uW*+g>k`FphpSB0T``}X$s zHviY$b?wvB(?EJ@sc4n4s)0~lN5@no(V0umeB#5058;5%MXL1ZeH>0vIdGF}8K>!E zB`5IvPZZfLbZ8|wV}V{>-ckGL(IdMNcpw~sX>Zr5Yvd0khTZ_wOBU><~FDzKV*9Qq_%e5ejyyjkWcP z7c$NRIQU}E1}TG;qj5Hn9dW;}mQ?jy_Y~`!H*W@e;%Jz+e2@!6XX7@6375MS0zPMO z$h5gcgHmntjf&(}jFTFs!MY&-cQt`#bmMoMQ!1s|@#GOp`T2Zab6usm!tC`!ztmKd zo*sWq@!0i=I(gz`@SK`ah5Chw)fzin1x-yyBMrY+(Zp|)%6bL{-1A_}wAn@LJ%xxs zoqBqDqBo`F2c$gXJVD(as?WF^G<5~Pz& zSy|b}(NVTn&fog^(6_BEtJKtfCv-2wS_jfEPqdoE^1T{~iGX~f#YYH8#f=HLZpznZ zkSY!N4>eZrBIDvZqknP(s*csKuGZw7(T&Pn3HImoaCVlzUouE~xkv|!Y#^PTo!6ND z@}RYjF%(Lq(jY=mePLoEy{oJ1Us~dJ{~l(n6o>N^2i3bfxhH1vDY|!tmN>bc08XRZ zAt}E$&A|})I+C1{avRZyz}&rCmvM0al*=H=qW`>=6?^WTJD<0>l*rW`wWJRUKg{zK zs^M@j>UmDW^(HEB;_>)a!H%=v;D0*{yUbjY&Dq=xZ@t_-iQl;LiNQS0m0#9vIZP29 zgFIX6iYCIpEB)INLPecA6w#F}_EVJ!*AX*SS5Xa+Wm*05r9yqZBZSfxgnViDwj#Tn z2!lzpt_Hrp!~f7mj@&8OI@sv;_G9#KF}Z6G6beQtyo2{KPZxU=u8`|tCt1WTjHHIU zA{KVOHUIckLWz?e!)cEEqAz5<1yhm+JpS!^T)spmu~xFMcX!GMIHyWp!hT5ITeR>$ zamY$LshQuS=WQ`HJ^gWim-dDvm14g2$fdcyu@PZpeF|2Vv^*FVVt5UYf1xrq6Z}eu zS6ttNGCP}KXSy9Mwn)e0_ity+PD1kF4-fg#zpNC_oPkOjV3D!CNeq>ZjSXH#;PKJn z%Rne!{*h_o*9-GQr&E*{BwuDjB{9zg^JUE<*W+j}vv`6dMP*&I+e-MD?;nD>S=JI{ z$E@6dL&z~9Y*jxjJ+VnvLeG;v06L){lix-}uLt>kJCG{nd-R9-63s=Y(}RP9wO;1Q zztVkrN>PzjS^4@^FG2FyyLay{OrB~i2-nDMXHEb40$x3Xc5z|#nhorY<}r-?Fqk8x ziW#$$Y1>8#e|i0p;~~$+-oY`{x4^yapHvUC*-+eVW~rvnV=KdCenOa7G^ARN0dBbEBaa64d(T__97dlwYxfs^8;xE zJNlj$(Ni-Nicri4@-&F{Kh_R4&>0KbMuCAU(jd(N4 z%dhQjpgx`wRjfMJ2NRpu$;)?BJwtQel+wiVE`;79uC$QrK60H!bU@SHH@b&{FWcGL zGT*yr@t)KZ!DiB9k=1_iTE0cF(Zv{=62;Z?QqVuhtwq`PR1> zu|Dr@iy#7Pbn|>%fi6|p60JWIi5Yqta4C0|wyVSN`52CuxdZo_y(1|pIsPi8UYWsp zBIA5I77t+0D5j>6M@e6Duw3qap9$k%8S6ba#nMwMhd#<+&9?NlB-~ z;5Z;w6Y47~Hs}!Kowi0Q=M!g-cplcjKqvGef6=!B)<*P^2hZ#qx)we^YiMZD*U+Fv zSUR5r?$($^LG^));HXoV-$LgE5#dRL16|ZSeP`^tSr5UiIizB$>M?PSkj2*o&WxjW zg!}_OE>;MiEF^)+HNmkmOZwB+6q5ZO-WJCrZSUvz@lUkTAH8%{mPRzaKu#WrvQ{Xf zBauk|gWF1@|H5K{f|24OlA{pW1!!`oJMt!%Cq2jG7aBsIG-+=^q~$aAfB{0RI8EKgai8#p5f+NJ%{eP38K_Keq#l z@{hhu>wbTY|6mAQwm#-=Fa$bSgZ82um3-8*K*zd*A*_(ZJ( P|5Sjjr2~Rs;d|?U@cf2? literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_A_dark.png b/dist/icons/overlay/button_A_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b5514fabac904da9231749555b1c2c2700d070 GIT binary patch literal 3167 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 FV1lPxA(;RG literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_B.png b/dist/icons/overlay/button_B.png new file mode 100644 index 0000000000000000000000000000000000000000..e8927addc4dd0ba42485d890925e4d0e157c437e GIT binary patch literal 3375 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 BTUP)8 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_B_dark.png b/dist/icons/overlay/button_B_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3acbeddcd62aeba7c749697312ce1f142fd245ee GIT binary patch literal 2975 zcmV;Q3t;q#P)x^6zN1QQ=otdXaHk11dHSc zn_rvc^pCe-e&=oWz8{;s&&*DC_uY5ynf>Ozckg}oo&x~^0RaI40RaI40RaI4?jc?~ z;54e53FH7{fibc7LxI%T>%7=sfCz9Z_BR5Q0*7O-gTNsXIq9bV@zUc2Rn?)u6~HXu zYG9<{DoTNW0^5MCBJ!DG`w7t_1Xa~bfyKal;1ZXsKMWKCn?&TG%XPOQX$4i)!N5(x zBH;TTyK{zr0h@srMdW{8JChWs>IhX`r>YV4FSV-rtg2q%_3!0dRCT0ULe_XHReaQ| z>ICgub*lQgw^|tGOI2O0s*d$teAy{d z)sevSz)Y`wafr8p+npHl_)bt&oeLCT4fuO96+pg-ykgSmIVO!If~xjW)m6Zs>G@&; zqysOjb#Lu!%BU||sH$cIf5kuJNw@hBm@6V>i5l`;(AfQQEAG$m_>H5$H6rp!B1SyD zrOJh0;Y?|;doARRZvwOLU7NFhamaTiBILuB!eRYvRE3ac`2Mz&sIot*e5L z|G=s`0@w{?I9*o{=M*qWM2>b=$k9t~>@m?s0x?Z+9`IaOg`B>J-bBxR1m*>^RP~0= z3OIJjRqN^e$2c?-RPtE~Cr`$|M7Cc_Kl=3P)Bdr({pi!T<*}5M6jD-B=-Y~Hk)yj$}qDk^fb>M*dpy#vMtRckHkEvD`InX0NP9$xt{1se)z zYHD(;+9j%bVe2oQXrapiV@oQM1y$wcmtJPW^Bdf%vJdUtM~w=qs^NYjjPV> z?`|hQKcCFOnOzr7jLE8+BO-^u?8L%igSL#a;D!YW+c6tf11Xb1H^n#TL zp-_ki9(=&|tkAJz$DDQO8!nA$V627y(2yP9lbV{!tgC0)DqB&}?)8SNQY*S+s)io_UHjNF9)YQ}f)@3yki4ciIsHmu5-@bhu{P>{l zVMzWR`3xUE+>G<_k!3{oz2|q=eky3D+Zbnbf5tnJ%V}j=aGL$ zzLzcpf|?*RGn0FMc@I;jO!3+kBZB(=lItXynVGEk^$N!1_@Zw(&^K|4l9iJuPqOTu zW!!(?{p{Si(`$E(Jm5Y}uo;Q{`}ec|SNoYbaU#p^UB=L%o^<}JOtjFhN7=oIUAuO1 z_tLw)YOR5wP7sMiSh-@Q7ot{~y5#zr=8MFl(I|Cwb#2Pj)zuNLkDmP;i9~2@YNVpP zg0d53oG3dHSDg(F4LtVfW2}F6Jy}^<-FH4dj0kEwLWftFSR__nUd{*af559-UZtd@ z#MU|0)zz$BvzAqluS&Rno0OTjF;P&gSW7o{noKXwU9F?B2E8LC;!n zFc36O!r?GWf3cL@T>FbO3JVJz^r!`g0ztdUT}$txZ$Eo`vi9uWLwR|*!yW;K1ob@m zj2EP(r`cYk+t}F1{(bu$_6UGa5*AcMz6N}iF3K%(a_m3bKYrZtzT;65`P$Uf-#dov zBo*oD>9)((*VjAf31B5AFt&`dR|?wRc(x;4*xCIKm=koHAv;M$JbOSLj+UaB6jVgM zq!;#fcm3ObeByy=jy^X2SVT&}?C!Hy4chWOMMXsv7q^Kj(;=fqjdIW%&JJ`<3cAst zEnibyT+Eu)YiyTKPfw4lN8Anmc;=0<4Zk9CSXFldKQLv}Z$zU}-gx~DHWqB8wzk&x zc~dW+iil%T_})LWdmGG$hTDV>1>N!94%=mGYHDa`Y&iS5sj-O@Cr(gOQo`Q^Fep(;~^h&9~glh!KwMw5$VGxBk+&pd#{L;04om zlZ|Q9r?K$Hg^ z0rxs#->-zjVHPc3#DW_ZI4LH;x{i_wI2qCUA>e0#C)=>xL`1l9#+59|TS9hrw$s&> z0uOg|&@n+pq)Juq#~RA?I5ILa7(IG4Q>IQ~>g7|($Z&0~`7#ly?C7AAEff)XNmYN2 z196PTPZ&=q)N#=4P&m|S;Qvsl{o|ofi2eht?*|MRKDL;Uq{gRzg_Y zYhAoZ**pp>%dg`Dwj`$%OUzEQ5w^zl9o!;v1h@upWhr@*QVIMhUNOM~K>6Tq7d( zLVb3C=TT5%_lYZk554xqLyCYYiHZq65ELNREuRjo1YFAo=K~b@9WYZwzA|CZ^Gj}P zRP{P~N8auV;C2z&V$!HDT4-}bR@dY!74XwTsbRrPHKDpyd| zG*w-rs!?yXNQin>U9GCUIxb=hRP`cNU8kxwo=OuRb*lP;S~dRCSfAMm&|O zb81!fSyla(+rN+RdbxH{E3@rFto)gNc_jA%)?SmBVzDP&Cus$3j;fBq5_Udtsms+L zz}la=Swuc@x$ZXf2tk{pszZPoz^vHI^iH8ttjB-bfHz}HYJQnN0 z`WUQC7O7a_)d%rSRPNJQkM*iz@5``44V-<6$SF7d4+sbd2nYxW2nYxW2ng^q{|5=T V0~d_hmf`>a002ovPDHLkV1fmYsowwq literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_L.png b/dist/icons/overlay/button_L.png new file mode 100644 index 0000000000000000000000000000000000000000..77838369eedad723c3058a39376666ce2af6f31a GIT binary patch literal 796 zcmeAS@N?(olHy`uVBq!ia0vp^DL@>Q{=-HE3585Bx znmx@Y(z-iZT>QJv(X|r0w{B%^jasX8 zYzK3K^nqnFbD4JVK2U35cyKLr?)Uv%^`9TvoZrs)X6DUchJFT^x&{0?Zl*n&~&g^qA z%=&m-L33$210Q3|`t-gtypw{e z5#={Ff2!NRDJgQ=`~?zub(}zRvl({#MYGt)M!qfEZ7A2jpK-_g9qCWc-cwwB@r9_! z;k!B>Js*3Y3(Vv?@FrY2e(!OQuY8s7b~&XfF-%GQd8E3-r?qVNUOS*h=8VdI!Gv5B zskbcxF-av>vN<_9IyHGlGZ)-{e|^J=`%yiHGiRQDcsEbnHBhJK-Ip&VsZB1%)h}$F zvFyG_uq3Sf>euW0dOpXV$fMPm-cI&L1-ZUXl zz=+Gq&GCc=ha{`wVuciumL7@)BR|U(2==-$v$L~rontD)XDGpwW%=6XzsrQQ&7FV$ z{*5|kWoK8$yGXkm-LA*KESFq={qV($6UB!l)z#J4J#W`JUGtB5*&Oe1F_IkknYl$V W=e(p{Pzf+4GkCiCxvX=px|QJWUCM!r5!7~ch8=@x_SD8TenU+ zR`JesDd5^s!MWCa!oy;ZGns1*1t)*rJL^09+^SQRFAx7*o_RaLPbrCY2j3l*YYpCu z`#&F$T);SI?)AU_!h84seAE%ayz2n-1F;LUj;~1lcHB|vk;-8X{RZpe_$^P;_=E&m zKZsQD#>CuMq-q{K;o`+DXFo)2V%1s}DcahlE9Ttn&XPe%8=I#D%e*P&x|EIVfdDJj7~ zruz+3-x#v_w$&H;c3yn_)#8TzjH=kxSCd}<3*+tnWZIY=X@39r2KW8<=U2r>PV*LC z!muu_e}fle%t^V#sZ2X2*dEeiy>Jf1wwMcI*Zo&K>Uc!tK<695u$jN=Bg6FB1s^LU z$++}1cN7X3@+lp2JYvxyL9yWFGjayq2NMpQ`{t+nJ!|{zoO`#|JdNA`={(EL+U?P& z_gc;M%My@}M7MaxmG6po+hebpUo%#h*<6)p`t@ILN5>V`vX*`wk{tKUIyWrlrIt+m RcVMz)@O1TaS?83{1ON!fH(~$) literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_R.png b/dist/icons/overlay/button_R.png new file mode 100644 index 0000000000000000000000000000000000000000..4e555395445f9db54c4beab20e09d2c51dc4f04c GIT binary patch literal 1841 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 U3YsB9nw9}wgaN!(%P#VN0Ffwzb^rhX literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_X_dark.png b/dist/icons/overlay/button_X_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c83d0c1123d7f076dcabec447a78beb5be4e5c GIT binary patch literal 3530 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 Eg0H{T{{R30 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_Y.png b/dist/icons/overlay/button_Y.png new file mode 100644 index 0000000000000000000000000000000000000000..ca0de569df161c6492d7e42ed06f291b13a75322 GIT binary patch literal 3337 zcmV+k4fgVhP)k@0^qU?z?;6edpc_*x1f?4-{8zI7xJU>= zT`pH15k0{en+;%`k+Gv^@H>pLa-YvvV`S`}7_@@s{pYbIz9m zSPUS=?0f|20PvQgD1Y#Jy+I=aT3%3($CDO`L|%-Zl}TpjGeE)swzRdiy?FBE$t!vW zvb3O1r*jr#Yzu&Io1Kd!>WJtCP1DK&kRAanDQL8L-3DNi*}3Qe5gp*1Kc&YdkL3h) zyWNiv(QD|u;7?>OGsYJAe7@Z>omxiF2@@tb+S=MyF~)uk3+1IV1Yjc({YulckPL^G zc;oW&^6pSobq}m_J!vS6u_HwEh~Mw8kK31wgyJFkefc2_9^tVdrz4Tbv_K$mA|5BE z3hH*d-2gryqHmd*hlQx;oXsA<}H(E|+o4$aQaPUf8d6}FhMkfkWfdrqfwnCJn937V3U zvKB-1Ix`-K=so~{6ty>`$#uD0GXd-|DqaFYjIp2ie7^VkGt~VJj^2UO&yM}po0Gl?lKmuqfc2D&x5+1c46 zQ&Up|GDd>Z($etgqmKdrR8>VwY3%F#*M?2DN}@x88Ko6ii?Y7gue-g!-176 zS7Pkgu|oEdNCXQPE|eAi_O`aR-vL9e%76fG?+!uB&~&cr?U z+#@rEZ1zl=x1k6Vs+m6Sm*CZy!GU>@z8`d%&Tp>P_FHQk%tsf`Z#3 zk;oeW95VfO=gO5UNJ>gVK|z6#eQIhdl9Q8hB5Bz*SemNdWLp%bo>-Rs!X3fQC?n-)2I6!Fu9J|vu7haJ3FqJ z)2C05sgqS&TB_@1Qv5j`Qxmnd7dY(aB#v#|X$&pdRX0*REYL`IiA>?3+)V6hZUz^Y4U7l?W9T75L_tSlkh!oos4 z@W2B?ww!Ym7Z)QO4h!cZk-Vq|as&;B!#|SYXP~TGw+`2?UF+WvorC);R;=i^gXC~H z@a(hC3g@tU_ikvKCTuSo&iTWDye8MwTV&_YpU11OzA9{=pP!G(lPC9S6U%sQeSJM% zdF2)1_i6@_YQrOo^FTEt(Lg>h3 zK!Tt-IXRgC?ut8|IEh3eC@wAr=Um9XuMX0ZB}Q z1$8(a#@U#4TuaWiHE5R#t|( zx;i1-8#_pfqG0LLrNSdhn>KC2g$t&vUehCyNMwA%3EJM?j&ULP zvt}VLFHh*W>gwv4MyiZ}F&4j|M3f^V8JSd8R^sE2KNhxMwrm-)va+yf(IO%HP$(4B zSh9h{FDNWJ>1e}-4Z)s(dSdvNAL@H0YF(bfgGs`6joE*49>(mX-?J2Lb`S`|i6sW+WXcf(ozDVFh9-SE;C| z5WXE}5$a`(trnxLwxZkOfDCN@-6{d3o2X@W+V(*CzBe>TD#?!NP( z?23hQ$^LBm=eqY?s-U4zXeR*Er(q2tu7<!yrL6*Rg{vAGi^hY*ahvYMKjrtZ&4 zFA`G}r5J!^3s)0}c12N2ZhTU@pkA-{9Dp}u`?a}=*Q%?lZ(43euQJXx=-j5*Mjtw& zE#Xb*BBe|}cR`vFxZh{7b!4@L= zrC$DrDnzvOiI4n#f1eBX z>b8kM7mTq50FDC4&^wk0oO1+&gYw4E#iXR9U-W0B`(!1*-+vasd~|LgZZj~5F}C2? zv14cYGnBfbb#F8@G}Mk6GiDTk`;CZ~fRy3DI5!P3P7RX@2?jhtaVI8Fh-H6!y0p z;fsJ(IA`yu%bep(*PDB&q5fd<3 zPyq0Hy{8#tKOEwhG?fmA<9anTVd!H0>`kofK%{f_9Oco13Ak>SKzc%p;=v%+4pu zr$kf^;H~J|6NWQr1?^6Letr(;d>#?a1|YVrgFKEC(L0>;H;rmm`ywF(?T*Le8BIhF zan7eO#->32rB5AzLyWQgM6@qjVq+N+PS6{1I-TjNs^$<;F6VqaV=M=qJ6WWmb9wbq z`2VWhUjw*=&J{+#22c+mNJK$ZRf9wn^!a?2Dg(5!v9YnSv9YnSv9YnSF_ZrRVS7ty TfH!AO00000NkvXXu0mjfpua&z literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_Y_dark.png b/dist/icons/overlay/button_Y_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0f3e4df25f8d8861e85fec40d0692a9b31ed9c08 GIT binary patch literal 2883 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_minus.png b/dist/icons/overlay/button_minus.png new file mode 100644 index 0000000000000000000000000000000000000000..7b315fe79df234a8e720fe37c3e1cdcef7c49991 GIT binary patch literal 2401 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 TlOYCn00000NkvXXu0mjfA(@M7 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_minus_dark.png b/dist/icons/overlay/button_minus_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6dfcdc1b5f74b04a508c6c765854198df58173ba GIT binary patch literal 1969 zcmV;i2Tu5jP)3O2 zj5bZ=LBzC5h_5~f3BDN<6PiFwYJ35!m_njPR8$&;l%Qe|FaZ-2`T=d_qf8r=+Lk&4 zx8cvjzN6FWeB6(<&p!9w^-FH%=ALub+ByGO`+V%Z*McAjf*=TjAP9mWs17ULfZRx0 z0*nCbfZ^u%Wx%55=c49cfYZS7=HJu6C~%M%>GrnF+G8#m6CQyI<5LLF6mxLD@vcQ+9GL{l8|FXrD~6NB<+$kQu_SW z21$z~ZI{$2DouAZl>QknD1APQB5AXvSIcVQ%IlK0Ny>KbQp=knNvnV#0qaY*B@>SW zx8-=`shyybwgC5&9{Bs26TsKa>=&L!-{)zp8zc=$+7A4h{udMA1HjLfuC4REjMgGT zC0!2umijzTR?RcO7Bf56t)b$AHm{eDQvDmAx^WoTWM(gRW29)8E9q*|d(lA`6Vf~9 zCnbHf8zaR8m2^Ex%qvQ@p@u94{wV3XP7M_4awUBP_!Dqp3I9F7NnpL1J=gAml?y6q z4Jk60RqeaScmufB%nr13P^E%Ox{SDHw!?Xu)ht4W9t+YIWyIx?D+LAiX#!{@mN62>7CzJv`Tr*-x;fmB91B#kqa;ImdyI znc3mlcFNl2HiwCRLWt?XMZkS??Uef(dJFwK1Xh9#lD;(C4%xa~rP2Al#G4>*jsok< z?8I~%b4KWQ2{Ci94EXj;8?!{Hq_w2zoR`CXz!_4M9-O)%SA>3x5Hly|0e4Qf!97Bi z=Aym?xQ)UG)6xLePMw#OBSP;a#H@lL;G0u_bd69+mjW*X=X=@>W@HRlF?mItcZ7bK z5VK0o2X3DHgSVhto$Up4bJjU`>vENr^u6F^FKCN(X0{)EBXpait)M-=GV#STLJtsw z*QMBNW*-74K_x8*Uh}XObj+1zcEqd8{fv{HpmS~jziVg=k(FmX_z8NWlbxV*ZUl#G zs8aHZH@a?4;K34)2#x#!^|(i4L2{NYY^ zKe9Wwo2+d7>_%?COe z_^{7ofcsY9#}18|kw&AznKNf{y2#GFdGnY*Kl_rOuX+rhWslIIp&^EbhH|^<2ljhJ z=*2*-%SR0m-A_R=vo}dA&VsTWHnTUqUj04cWGCpH$H8wJX9!xJC%|th#1+614_iS; z2v}}rqb?C@X0H>^qb1pEW~1P{obXqUwu1I}XyS|Ka>8LEN=xCt;J2Ad`)^+Mf|mK~ zgqYx;W8nd3d%@hCbLS#azivI#Dv!%6ZmMPxpX)~CW`zDJCc~Zk&2^=J{ ziRI>PV2zob_5ANX>(9(y19o}a4bI{IsbWID^r~xseWa|Bfj9WxaZ$eE&iF)0c1@?1U!lu^=)gp<>4%r{Z}vq%pgoGX&f^mzYpdF*`GR z1lUo9uRi4-Gy6>|J62X)u30ki&!lR5LF+sP++b#nR(7tsAnc{UlSHzrRyjy22z#v6 z14@)^UQMd@xXtVkunG82)xLX(lfdWO6%z^w3Segc0JZ?r z7GxW6PLRZWzLSHCR@{~JVc-wM>%j?*0-rOpzjtb&cnM`mt4N{_dWyM2z$P<$sT(83 zv!FDu6Q2g2DczQWJOzBRTQQ*qf&w&e%dZ8#1H4ZD#gMPouSn(8-wD?|^H7J*C@{k;j0my~V6Ok~T|vMg7AONrPR1O40?Az9*?s zR*SB9N76l#A}`_ul0GPDhorHh(zM6hl6EQ8BoC%T(o#v=C7muR)$EK*x>sqAa;4Pn zF1J-AEg=;{-Aa9ipC{@VlmzB$FFHuRmKQ&GyJGz_dG4Xh87Dp@QdrB}a@|7YcX zi!`ivs`>pGX%X}PK4x~j%Jv6A5ClOG1VIo4L7jOI)Yq&OF`Z+|( literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_plus.png b/dist/icons/overlay/button_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8090d7dbecbb5ec5be0e945c29ce302efac58c GIT binary patch literal 2497 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 wA0;g{aQr^_y7O^ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_press_stick.png b/dist/icons/overlay/button_press_stick.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0254d50ee535c54ae1f5576d1fbb53d3f4b488 GIT binary patch literal 5225 zcmbW5_di?z7sqeR*t?A_C}I<}T53c?T57aZ?G+8RcWc&)U9DZCXremoT_Z(}3K~(` zYSAI6y^G?T&%f}!kH@+1yMB0|_c`y^d7hhMZiZxI7GMSdfX&1hVM!g`sdgXCKs}r2 z?)Oj!x*!9Sn_%h@33kU)@0t9KZG!-Sz5hQ0<;t-OQXldM8{H1J@!oHy3{oMZZALr|Xvm0Dy;Lg3!MiUc6Qk5h?UOcQB^*YEgDZBD6(~uEBBeZ)(Sb zd+$RzE~T$z+W-|tv_guWWDHaCup3Eq2Uc!GcX(y{Bl(F@gJhegzs zH10WSDxX7Tg6&1Ni7E8f)&Iwar)gOG+HwkO6_H_(|oVBv34l>ueQD%-=hI4>FKV z>|`MP4V<#l*OjQi;W4Mx_Ix=fPq>*AyD_#?zW}Mcw>7Gb@r7B|s5eTU7x)ebTAP}P zJO=;x^@1+^k)u@^X~;uVlBP!-ER2%6NOyNbp#4x__6;KH0>y!PnQP8q{=?!C9%F@F z;s+XKZ8wycoGE^CIN5!}mrMQTu)P}2pPb?^hiZK^IY}!9SVP%rR%bf&pKN1E%E2)Q zz4_EwfTr>eOLWiKM`KCuSHa&UT(vRY-ZNZax^wWLFG*Q_^VLa7Nf&&4d@fzB*yeiL z6!rHN+^%`uP-Z{fv z;wtg0AbYY^W7suVh&nuf(%6QT82Kh=Tj=u#WmdQGY_DtYuh)J-kv__hRggbK_JE?w<)Db`RL$jNPGonZ*c3f|n#Ifz@4#|SehGWEl5c0I z_OtZ0>Agz8o=L1YuNd%unG+Zy`%XE%u^>Y0!`VJwzC~{0Ya1(}3;2f#Tq8!}EWIwq zHpt;unMrDEfOrQ3p0BBpn!=mldO0En#`}ZwY@@TN;Z?OxCBf_{LTgx^DT8P z@aiC$N(0L1)9D;zNnbjD6ll*~VLyjtS?p1-fVRje6x}(DTr?8T?#XX+06y&6epFj) zLWpzG`^z$09Vw!x&|}3~TDi_e6e|Pl1puWb`uQC(jhw6EH`^(No&rT$8-ozRuRUy0 z1S8oag53lWM90uhz! zh|R>vmzG{D$}{T5!@RDLfk?C?y)#OLBpI7rcz6{z`O2532tNv{vxR+P1j^0t=6TqT z^P0Du-(ZGz3#TBoSGP|9iY(4Cex$SS)Jc)SUue6B-Y$`|3L&1onDn`Y6;;9cz&%)Q zJFGX$E-xfPP2kp;V7d8{9@-IYI~9h#D+@oXE-!b%{2Ap>!fE!oQo}!Zu)BL1fk1MS z7>WNNTyIoOca+A`J8F`Fe>4P(h(C3Jy%esqd#J@j#Xb@2FkMUQA0X0hN{lQGYWA6W zZJQAi5>oZ0are=+?!7p@`K25O7%%fh_)b}2gd;RVqGCGbuE_^u2z#>h{{Fr$Xx{n! zRw&xh!pSLSdLTpaib;9SVe?;WQGUl+ep%F&Z*qc;$8AfYnD z*Mfu_v~dJBYCfmj{JFbK4inYz{ia2XWQtW_Nf?} zQ_O%c)tyhvQHGjAH5+SdU33IKKp^mU%**=vfaPmSU6UnMA~s3SIqmp4K$=p?pRYpu zQ>r&YWkq9IQkIZFs8R)Ld^|JxpH-aRvyg(awONO<@7JaPJwm5 z;8agRLBTBHRLfLrEFKdL?H53wRtdQtP8EIC8SAbU70YsS=v!@3MoXJ(Z_s(25 zXzPg3R#{GPao&B2YszBbX@yERQ97ll0__m5z62(o8hU^I%*Yrn10nttL@1w?Z9Oy8 zVLB#jHTjXaKEksI9}y-C6=Ki&&oKeQq2Y`1;ZU@$xKA*piP!V?y=$xX^-0(MAgmpH*bnOvlqj4 zHb3fRQSDjj5D~9P{&DIRP2*f|`KPv;u1`6dc^=JiXlc$k|CcJ~3R~g!+Fv_Z3GqWZ z+ycJe2h}w+7^zht2;j^bY*oK_QO6J=4L}wjFK$vOltt>B2Zzk#-f4xpQAg{5$o7jD zY1nfnFsH^^6LOQ2>JH!G2L;d`qRDc4?_K66svZHa5r@&>W&4_yBcHmma!W4>xY9eq z6468$e#LyK*Jf73eWUB3n|2+)BN=NATR3~p9qiDPfgKySK{G*tYY*Q!-%y5JE-3Uf z@UYQ8;9!Vox}j4+Ceb*5WytP(*ZM_}*txV2H4Tk0TFq}pEx77xlYw7bqmGFY zr?f45`b7y+*6C~WK_}+zzyxku`+NHG` z@H5u+58+vbvS*Ya#q#UOCMq~lfQBzjj zv9!G0&PND6-;OK7d)zLE^G?jo#pv1}v7@`YD=RBmfOCrWw~>e|Fr!yUEt{5>mLuR< zW8;q1gNZ-SGqGdWZa{7tdwaji!kUE*Pbwrp)MTka*?878HADK)H2PG7cj7z|;W@wQghLBEjNS33@#M!N+7jimc{j_BM27)BAU9 zA*1Q_Y-4vFK;!(RTkj8l9cMX7kt|X00bech5oK$XcxFE_l?h7VTpmvcwP|^8%#z7F zAp~nIr)xbjBA(@Vzhfx+g4bd}T;W04MQ>}jKSkKnC8_+0xH`BnMVzdZ5UvtJg^=zz z(s^S%_uqffXVX^azkmNOIRCTz196T9a5)gQeR5e`mF#dkfs-)@*X{3E!{rfTt#*xPUMJ3tPCW3aqU)!)y6^G+T4SPzZOsV{y!F!K z{g5ZGvPIFMx|bFk=>h6;p3recoNUhp+Hb&D^D-<74&^eR_p}c)LD68~Y(9Woh%sRL zsxAes7})hjhn{}6TDGE#jR}7HWqP`GH2bF2&__(L{af2;kaH?f*E&-Y@M}P;wIJhe zLcEeD|F{{Lv;b|*iXBv%Tins=YWZ&6<;8n-VBpqBgT2nrl)D-rjBixAy30pNdSdf8jq^rO z8NFf^nY%00tZ#lmRaF{oizpd7#ncJ>*h`9(ohRAySPUS{!O-u&y?!Sb z!b?l2OCn(hIF$NN^XQ-T~`BkS)i(`4-LShhWl1G5;-ldO5r9$0j1pMDQ;F}Q!2ytO!e2no z90+~t900MndCGBy#Zr?BLWSrJ!{Efi8KbbU13?HlV$s^!nnD$E$;x4enTT)We1=#H>IhjmN?980^Ij>KXZE})W=dukuc-FzC3fn= zKIL!Q+ppY`7uUTX1Pj}t0?U2qtbsCMyU7tO|AWRxzOPNi0^8rtbZPk=qLjmt^c1_PCkiO? zrSXr+L`id#l)m}O;MPH#I}eL=wc9_K@&oe+$F)Y8{z-^n(Fq2C{{bsCpbkT84uSYC zeq9#WLJUn?-`cFq;{Gm~Z-S)e!xS4cn%!E4UB7-~$Q#go(r>D`ih7n)X zM$@it`{ywSHVe;@_P}YSrKJm->scT%Si;aBnuh%c{lB)tgMxxs0?Sw~N<&yf-@B?z z)5{(E=P3Z0dL9o`Zao$OJWXJJo7~E*zbt|C3ouU{rpUG|y!N)V4RkzE0q*1(BjKQ8 z8gv0JO2;wyQ%Pn+We2*vb*^NoBU%mqmOXmHm@|7tZ0HsgRV@=MPp+pGE$NISujYHGmj%|Pixcx$JprfZ08celO)m2o0oyIPyvLepC4DBd5{E85F;eC|>gw8MWIc{;5e13W%T9D|B) zU)KecXxnb14qY9GWvP*jPvq2i&IrgnVtk%cRgM*PBdPGL#orVG`3KiWfG%fWmx`GW z7I|e&Ar`{PnIav$M!It1N0oCMPDRm^R{DpjTxKdEe0Hm#HJ zG_PLW=${6Fvd7zcYVl^vRRp-uvo!OjfFHGLw8(oSqoPhc zhus5{=Qie;fT*Y_o=DxfeuTJ+BUpvh@VeRnyA14mP?iD8vt4}ALE)hqZZamnqVDlk zeCegIb&->klb*x_m~UmqmED7bG8d$f0+R<59~Hf|fEva}3-%hQ&rJXI{4u#5^9>2B zY42TY*V-3L!;T}n(X7Q-tvjy$@Yv+X!@mPachyE5ns{?g^f5)D0M~ccc|zdyEwp3f zjep&L`$9kc2R3 zoV1(DTyNA(cj(jopS5t=V0kXhpNXS&A+&IeCoue@fsf#gXc8gZp`sd+&uHY8cA_Rz zqJfi%)Dw6DC%hnn0Y1)Yu=<|(*2THBkx{oSYJ+Z*i7RS=_!jtAg4I?S`6F4Opj+lQ jf;WBoEwka3z^Q0m^IzrolG7^cZ$ZGs$P7_q;Qa7^A(6KO literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/button_press_stick_dark.png b/dist/icons/overlay/button_press_stick_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..757d0ab292972636084bb75a461820f0d632f4d9 GIT binary patch literal 3636 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_dual_joycon_dark.png b/dist/icons/overlay/controller_dual_joycon_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..63e03eb4e0f1c692864c6af98aa0d349c83dc3a5 GIT binary patch literal 5889 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 IS?ulq0u7?|TmS$7 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_handheld.png b/dist/icons/overlay/controller_handheld.png new file mode 100644 index 0000000000000000000000000000000000000000..deb375011c85a5f4d615c08e33fcbb97f8532944 GIT binary patch literal 4645 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 F|6eLh>Gl8s literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_pro.png b/dist/icons/overlay/controller_pro.png new file mode 100644 index 0000000000000000000000000000000000000000..67cf86d5c46f43eedd96beacd789af20d94a07c1 GIT binary patch literal 9493 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 aGluAqkh6)lJK5kCCcsGF99gUDn(%+W&hcab literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_pro_dark.png b/dist/icons/overlay/controller_pro_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7be655b961eba94566216874cf4b14c633c1e084 GIT binary patch literal 7488 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_b.png b/dist/icons/overlay/controller_single_joycon_left_b.png new file mode 100644 index 0000000000000000000000000000000000000000..7429450a33de84a48866309cb0ed7e65e33f7913 GIT binary patch literal 2559 zcmYLL3pCVQ7yl2L3WIRbm~f?p2AK(YGb4|XG|3|jWtwRSA#~A{46e*@J*Se!E6SrX zo}rQQsKLnK5~8`}dOu4O`u_U9wZ5~~Ip=rI-us-r*WPFEljQ7V0~3Xd0ssKBv&FiA zbJ$Mb0|iIiJvnu7+7)VLhuZ_T=slMb!S&u?TlY`^ka)AxA(>hdis0t{Fl)E4vq6Ng z2;Xb|Ktx1@Ht8xk^rCODzjn~I%l8*d;Gkf%9o7;T`CvZRCjfE&x3*U(a^*o|l@g8Q zOdRdZhqCG#%w+0oMVhAC5!*+%Wj>>6@hz;7Q+I|Ri$1sh%>-?Ad*IyBQ`cR?``YF) zY4$!ArIEL@3VB~E=2#lUzB~HbdlTQ>Y-2E5;|4!mH#L4ayA|4vT}|KAH~zNzJ#1lN ztp1zl>%a~k4@ZoOic+R10izs*2v-PCp8D%Pux*W%2OdNqz62QM#%d|ijjjIDg4&6d zsPXyyh<&}sRE@;(o=G~d9I)w3XW~Cg6Rp`#nU!)Qa#R|f{)wc}b{3!s?a7XMc@E%D z1aEvvkT@WS82WtW?jkHVC&v(r?E&fvUe`9aaax6ig>}wtG@pB0OQq3h0|scc$!`3! zdwkv1>6w|CgBT2^_Q?sSqNO5cK|w(Z3bH_KhWlpe^XJRkV`F3eaJr{7l|^ysbf~JE?7Bk}PbEylXK>0CJ3AOQ z?AfbVKkpVob)qj4Rnv%ssVE>%ChM9hLSk`1klAK%RvYzGbcGsMB zIp14KOG{Hh#8Vs|7`U!Ong3vP;qRKp#t|e^Reqi7H@`2(9??FeQ~o=+(p&W=!M>~H zKf)NajD8R@`5TE;kvCTuRzY*gH!%F=It*6Fb~boIB*jLZrzi@5j^0H!9_t~*o4?~3O%f}rCR0#|feh?$-&0EI%m z{ie=X?mf8UeKDGkl+S)qz|qmM9ca|lT~qe(^rVa3^^|T#v1)4v^&P9|iQbhH&ng+k zLm&J4F7^j=94C@v>aSjsKyFE}Pd_o?*^=M~)E5|YN-;k>R|$iZ18ZYo`J^nXlwFzOW!%*`fF9|m8s#D39Ncyi1De*iX&CUzw7 z4KEug{L;0_O!1dMueb8x;xC2n&GN7OvVKbNkC%ohcw?_eUL*CF(dDOGa#v+!WYm+0 zB@HiD^scQ<_fEL@$R@vS#w)bN5}M^LWZCi%G(SBdvjVcQn-WFX)}k1=l=5tfXhr2_ z;ql(RJYHA;iKHPkZhS6%(#ehi?Ip76D=I1^*>S*Ey{f^J{wk-C%aee0*VX(Hw(4Lx`0iN9~&Q6N(&o zQARvQX!Co%8_|9uVVJQO@OcsVl2vRI^SN7q`o7%VOC%EV#E4qHPEU7t_bIW?lj{|)+>Xi}&OiUC#{nPM zU;5D0Efen{27x^M%JtP_pDdDA7rL!_R3DIBZk{iLa9C)h5BGkf$3cevESih1 zIL3sB$z?L)FwJ)uKUK>ubD7vMIkMPjY#e0M-Q$iC26bACcd|Wlsdz12&VtF@|1ew5 zS(8&{Rb`JszBhv5&v@AEJvv-d{<-}&4sK*_elsPptuQ=;Z3p|21Qda3)K@`$oUYx} z!ci}@E-RQU>ws2xH{CcWo%A=bkUro-Q3oG_auGr8WeUW#D+-ItexCjNLt)|(k!NxsP@4E-6-Y{f1JlV3JRK!5=zP=N=wrYA} zS`Js z8EKSchu|VUwo6mg8Q-7A>dGvU5(s@@(e_hl<73w@;ucK)RYlQ-B5$B>(f-;PB)ORA9lx!e9_WM(;#$pSxXfSt7yw!#X3`#--%tP=nL literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_b_dark.png b/dist/icons/overlay/controller_single_joycon_left_b_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3bd97a8eb695e6f9c2d7c0982970b76a900f7594 GIT binary patch literal 2383 zcmX|D30x9b6MsM+$%n4idZ3n}Yp$A>+F>50V2XK=VP4p38lp&EkZD-18B$~>Mrvrv zW?p&bQK<#n;gNnGc_rnQDWrxj>X+S%wfoKcy*Kmb&41p^Z+`QB?*h)l8KHz!0stVe zE*LKvr>;#!dD)7;uD(^q*2Otu@rp8{D~2*<`}NT-esKUOKUtd)j)}6iOsPe6@}(Y* zB2wdnV~>OQ_;|DM6Xdv%;OOIKQL$lMi7gTU#V#zy0e>p*U4CGsPT0?k@wz&4kyPI$ zLc^rY7)RK;+sqrj&Av`e@VJKpH}=iQXm`oajjkY(`Gi~b4#;y%TMp# zKhS@X8fu|c;%j=Qn>GqV-O(0!fG@z_>#O-~lvIPwG7#yuS?{*8T0XFgOIyLm%lE1G zVi{>^3+@JLA>g4$YyfLB-&OicTa00GadFj2^lzGxN@&YmwI>~XkjS@-x2jp~tINyF<~H=vDm6c}v$ONz&d$!Q*fY1e zQ_Obr#M7q>_RwbWJnj_rq6;!n+{EoW8z3 zd^-^;czk)PE7T&l@V%&NgEcxkJKGaQupIvIS|}9uY;2{hcMjNZQq=!emE9AOZjL%F zub`lzuAwph{CO|4{S~_Z?eY;e97v8_i@k|AM)-em-H`ih_L~e|lp%~s2wW4O;zFPTpE<=YC`Mw-Ze=%MXY?|@{L z1v0$!Ned4p_4(Hh927!lx1dE>S>V^%k%T|J1MFS%v2ue?eAZ*1M0hof#w`n;`@zGt-S=Zcr@ll z@6Z-hM!@J_3YZBVNgob}^T!*=KKlS?+y(2AXB{_VonFiR#X`5q!y;D)7Hv2mgZ&zD z{P=WPO&f*dLNfw{hxA++4E6zo`b#hQ_bZHiTHt%}NL^S|n$^*lm~I^K_4hZPALsA8 z+teg9K?UZY!Fp6wRM7R6h)`W2sWLZwyR9u9kGbJ2iXSv*wZB+irkaV*KdSK5Lsy0( zul79n{F7iegl^Q{0A9&Q@8!6D(aY`}RgB$|{;~de>7d)Wa|$Xa;&j3Ym;>O|%hzq= zutl$0-#o=8QlVeLHAK2&vBNH|JE7%NjAz35yhJK#q1=nR*sK)PK^wg<-MD+47mys0 zbbXgqx7A{sYtnU#?}Aqn(#-TpuKZWu+n#9jlpE}9#b8@}@Rl{BV@b1PKU6~{lB?Qb z$kmVr`<*u5aj{7eCrNqX6B*MV@ibBbXF5>RM&2AX%scNp-rqwj(C+(>vJEw8r+4QLJC5FWVUzp zF36K#%wt;7HS6#N#b@k0MN6BPS5|Zltw$0l@s6>tXa385dw|Gx%d~||r~r?`IN7_D z2CV@WP02O2O1#SQ-LHR8Otwx9GX!*bfldeY=C{(og8e+L;4tJBTjhnZp~c{;gIB>N zmdoM8w-3yqosUr-wObjT-6{TPSB|}5Dl)Pyz+~X$Tn*C;&`&|8wYCi+Is{APFI)$S!nWV7m z+{mxlN3RM|E#@Dt`N>7|IitzfY?hmAq6{b8A9#{kzHOmt&jU#Olx{N-Eh_*>$R{26 zT;F%Ss+cyJSM0;`ZO|IBcn1YSSuAY?f{CjK7w(Pi)EpQBP_afPpM0_A1yA88J4=Y? zl19CguNAerF&WJFs|WS<<_n?F!cAH-NmnNQG(>iuJ=WKoL53?PH=SfL{JXc4QWND< zozNlBpgvrumV7#{C%J|s=GV)un<#DsEXKpiZF=DBfXo^0aKK)FjNQQFKX@8z$2%#I znISjq7|cAQJ+D#z32UjrNaIN)^^!_8nY&)os=kXQgbD?z#H@b*1o*RNX6p@<-dM_O z&->d;8ff)`A~{RQOzZFy-WAJv`X%~^XBeUTItti?cZHd^4bD26ud_HHYYgn=td{)?*m`dQyE;=x z|Gv|wg>a1}IQ-rIJ1S$mlBC7UN9aoX%_Wz70_7K#W$cUdYJ^SaSy1LDy}A5$R8jW% N0azywOqF9$%HQ@KW+VUr literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_dark.png b/dist/icons/overlay/controller_single_joycon_left_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..24ed2c44c919f36ca9c4211ac1e4d03e70dc739c GIT binary patch literal 6768 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 cc&PhF!+An+!;Sb9@M{L>l8rQfH`;=tO#lD@ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_x_dark.png b/dist/icons/overlay/controller_single_joycon_left_x_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..119e3091af700fd7015099a98687973bdc5a2fd7 GIT binary patch literal 2392 zcmYjTdpy(YAOEfy%WP5_XPV*+UEGo>$$c(!-0G(tA(zN~xf5kp!jP0YX_DDW5+m-}srO*6w3s7^paCHe# z^CpIN$Nt;VCxN{2(R9{b;7#P6S|FU;@J4*zwiZ<9J`TXCsdx-d{SVtJ`} zVUilRum?ua!PqK_)c}9s1XR2W5P{n3GJ1BBV}MOWFoJyu*xawXRS-Cb^3dtHA$gA9 zq>d!zN=PNMt+u@XAWO9Ho7W{=?dvNoxTR{$(a5YV3+G~>1_Gf1Y3)1j6qOvwmLpU!nzbJ=`_TS#Pn~EktFuy;E zd_?;(B&A?i?hZa{D@?hAC!Opx=I$VZ>~FI6vMqtVe~LbbZf95H!aV(5t+x|FbNeiz zVyBNsb}gnpAUT>nF?yuU&)xRjnCul@82QE#s1P7e77yx`jv9NYv6Whx3QY=PkXpKn zKQy@0k#;rn2qL%RS|pUXFr9!@ z_{c+}N|^QsB%m3Ph;LMihbS^3cEGHYWUNfrFm)>REzh0J-}s5BhJ=qWj4GAdqG3&u zQ`tk?GWJy&u^IH$7}(U65F!W(|IyS&*7p8zVW7YBs_NlhB__nzJh@0)#x9Wo?Q}VN zONi_TZ}E}A1N}#tkQH{2oFovqy5yPBMBn^=fT;H8{c$?lN1WJ0P`699IhOSe55bQO?`_wd^WH}Y zEk7zQN+`QnJ{Fd@jZ6suErt@&Iv0lusN}y85LR_7h@~9oFrxbu^zops9#IGDuBCi0 z%QvcpQnP>%hNf~Fbab=8O&WGiXWXoJ1LLATg>?a)>i+&fX|HN$W9M0%#q0Ypzv34G z`_$G9IJ%sq8{_3})%=LWj&iNP_h4FpiiNyUZ5?twWy9<%j8WrNRH1@KqhB7q#t*YdJy7B@9 z?VE|5H~^ekiTib0p9O20?21MC)_PUa2V*VLr8B}aID@wLy_~Qm*xKb+#0Wvng{dqE zI~34%p3L%u#_by4PaZ4ik(!!3Bhc7&?{ac-vMGUvz~$8R^tDN#0-6+`lC%Mk!I*_B ztNnb!#plqyN~YI_$4_ej8{p^Nb%Ap}kC^MNKa8}sw6qM;lby~(?3M-w2M0CGv8`R_ zGW}4;EiBq@NcND6ag2KGheN-)9qiM)bjP>BtGB_ngy#@s2z;75_6L7RcSGjmBSx4p zADlm=jNulJzWZ^mq4DZSv*%vgz|k3hb>$eD>LVAOPq|h7sZT_UX8FN2voD6R z3OV*T?Ez7+jFqduGoOn(=fD`H%iyn!o>*TSywqD+^Ql)hUC6yqR}<3`NDD_tTaMdV zzX9IQhLT)ki~fiVulaHvEL8#j!y}YX{VN-}8JT4%Q-Q zsK=+CT*68CsM?|U_~_`F^Kyv4Vk0}nc!2(RPWLmRBS3ydy-Cb0Lmjua&ZJ6iMPiv> z7V~!*G)5NIgml;#r4=YDDgCa>?g|z$-Kx%Ac-#{$+m|xlsH$r0%zx`hF~)3Nn|;ygRdFKy5Mw!|h`FvHdl{FPm#c`wZC^_7 z-p$2@IhAUZ+%7CE3@Xf~Vhx4W_mxO#g*~8^tmw=3IQ{VO@THfpPTrv^l3Hi^!k^cK zlgCL4;#pvRYQlP^9U_i5qi%%Q&{U<+phMY5?MqllnQ z3-ZG+iRsTic!0U?*{P9&YLjLQVbl^=*fHoKoMyq>GxIxDf=5$ET7-J@0kpfvq~6)e7`Lw~*qc$oW!_pa zLQOd8#KRPl+F-d`)kML&&su({s}$~-cO6wxMB$diVEJrR^OSclRdL8+<1NF{C<5jC zhDGlPFt$$&@Xw>+bej!sbMAH4n8(#f-N8gOsZT4~ns4Wyy>~g+k7wSG6nC@erWH|@ z4X=yS9UCH0k+8+kQE$zEAOl2h;{YhZRFM4mN*iE10cXmHMr;`|7 z-w*$988uR0roXs5LpBX+J7J~X^gvdvuV%ERJY7@uru*{ZHKXO(PnZl1W@d>BqhXFHoz0j8;5p!~cbj9f z*DAH|lPGXU^Fn_{eV9$;vs#q~I(4m*y9J_r|GtkgB$$=`f&jdeC$7;U`09TF-8pLU literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_left_y.png b/dist/icons/overlay/controller_single_joycon_left_y.png new file mode 100644 index 0000000000000000000000000000000000000000..24421d8b9126458f73acd55552992b3466255c8e GIT binary patch literal 2641 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 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/controller_single_joycon_right_dark.png b/dist/icons/overlay/controller_single_joycon_right_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..afa80e6ef0f54acd62711c65abab9b9e7ad204a9 GIT binary patch literal 6729 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 ldcWMtNc(@e`la^d6613Y`I`V4 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B.png b/dist/icons/overlay/osk_button_B.png new file mode 100644 index 0000000000000000000000000000000000000000..f4a04117857cd2c50ba830151e6ec7006622164a GIT binary patch literal 741 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdP@ z-t+Fccka9Q0sGj;{{r)j>+9=7p65-GoM^Y($4Hh*t^rtYOp;_8 zz!d-odz@$ zeI%(@)tyS>IKBhmTE<=j@B)D8CIX-jKm_1KPMq0NsdU3Bk~aAhGCh#A7)8-&-hoD= zG1_jopQM6aw5+wqU6-NYdEOKpO-_?qO(B|tnQRd?x@#lwWH}S8Yej{X&{}a&j6mLFXOkJf_L#7 X+Klz_&aj%Q00000NkvXXu0mjfR$)gs literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B_dark.png b/dist/icons/overlay/osk_button_B_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2d2bffccaf7d650b43846a2b9b24254cebdc15ef GIT binary patch literal 767 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdqUg?F z0suy%D7sxJ6na%n8X*2eqb>0T;IX8vnXQjwIbaj8t0PA-m&;uQ&L%J89uu`eq9v&V( zXl744?BA0hj+uQ(8UzM_5^x+i1>7nYi@8dr@(lRc;eDS7k#xTYq)le_s$4EN#>U3d zK)z!vF^^+r(=^Zd9~vASd@X4}(m*zw-B7Jo_W{wu!omrfRW2)(bTbJe0MGNT0B2g_ zGm=iXyk~&Cne_otya(Vgv8zV3GJo?+e@N~|So<9Hp002ovPDHLkV1itxR5<_u literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_B_dark_disabled.png b/dist/icons/overlay/osk_button_B_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..93c102b1b977463f3ea2157c9c838b41ca2e83a9 GIT binary patch literal 781 zcmV+o1M>WdP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd|hr! zONaIlUSgU>mO8101S-4Z+O_Z`fe=I?K}A0fX>{lqROF$;gW6#?ily3DK= zbn|N(&rZ~)iN0^e2j zaXOv8KRY{n*7yAv4fKsD5d*A5ISG0)1f&34MDFEsxpv#OUjm;SxSt|LL}amv$To<` zn_{uJH9kHb13DUfk#S<4=gnJ|H3qabI*Y~P+e9MK31C^)*6{G~Pk@=3nM(v!)==_1 z?`~vr)k9N2rq2FORj<^2w}?oGh_nHxRP`dkudZRzahz+Bl>o%!@zFw|aFRc(<(P;( zX(Wft1_ZlnEJ}b{HmOvqR4$hXf#qi85G)yE2AeI%-y#5+OlHj()32&i1n)&Nlz>TN z%xT-U*LTV!^kvLuvxlm?XnP000>X1^@s6#OZ}&00004b3#c}2nYxW zd|z)H3lbIdeZSQhlTy|5B61iw0IaC$f{4sGj+5){?fv{uBID!Z zjjrnstLkMD*;8j<5x5hE;b4D%e{shmzV9D2#ykLy@AQPgYin&+CX-pJM>0@$V2qjm zO96m(W6adp*jPQ+Bmi939R}K~;$MMhz`n{sfF@u+aI~gIM>3hb30#j85#RS)MdW?W z9n5*2*BP}T2!c-F;f7!ZMaOZD#5F_07?T1b?PjytHW9g{s^3Kf^q?zUhgHiL=()=ha*hS&0)ds=5%HG$u*;4*G#B!2Ntazc-aiy#PMd&_Bl35s{g= zWGzJG&HViQ`t0m%g7UkmMr@sg<2boesWb#M)+Sq8THY=$E+zri*4EYs1_pisWV6{z zB9g4479Gc#j6EDc5Co&ZwW|2Mh+M5KRMi!cCZJJOPXZU&ngpooNIIRq5$gmX48uX! zbx*K0T8^pelWJl_%U1Aeb#-+pF1hIhHmC3u@JD;r!CXT_!-bxnp5=O^P0N`~W~opp zoB~EE{}=U8RMn9}p>TRT0f_k-CnqNlS!+`pqoNIHru;L0FvdJm)tu*fU$!e=!Ed`x V|F``x6RiLM002ovPDHLkV1k4+VpjkF literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y.png b/dist/icons/overlay/osk_button_Y.png new file mode 100644 index 0000000000000000000000000000000000000000..b08b4e26b9cde9477f9541ccfeba2f14cfc7ce7b GIT binary patch literal 726 zcmV;{0xA88P)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd zMZ_Y-RtkcRUw=R=QJXZT5o-fh@iT}ZB$Y`$&TL~0i`ry}AYp|WaOYUeB!p#VH&N_- z%X8mzo^$8kIT!xxzy6gZE9f}Rm@%fIsuzG$KuJWlRdrKDmQ{7p^SrNr1Q{P6FANP0 z-2<)zM|_Ui z=g^XfDJU)1d2+MdbeEgwvRD2g7a>WS_IRh`L&kbK)L zU8>b;7k%IVVvITO`~HgKIMcwjEVGE%y&$E$MO9s}ZM*2Y?z-bRgCg=I&B^lS)-fWo zowo>73WdTA0AtK$;B*gfZXKhln>`XCxyAs;`uINQLPTV_Px3+aCKsZrizKt?ZxoXJ zJDC~?f?%gyE)N6KSq2gLJ~A@WiR1XJh}5z?sygrc{!$)9U~l@a08@wRKhatkhUYdm zHljR(0cf>ap)qC_SUV)(gJoH>y#SC7NPw-at)InW@u_85gFu~R_~j{7)%hrjt~8s? zoqnBUzKqpsbyP$esyZVgwyGXaU&c=&@>WC^UDy5AulTNh1M>sj0H55+cmMzZ07*qo IM6N<$g0&S$+5i9m literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y_dark.png b/dist/icons/overlay/osk_button_Y_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1fba9ca93dd607194f860d9f908f0493ff7701a8 GIT binary patch literal 502 zcmVP000>X1^@s6#OZ}&00004b3#c}2nYxW zdE5&Q=M32J4q7SUJ>R-#Rel^|k~Sje%M3B+}0c303Ux0&5H_r0?-!@iMv>glf( zLtsO8Nwjfhy1l6@0bvDrU<%D#Zbc{5*pVJGT&uZ`$}uDBc1JKFChQ7Q6vt zcK+1HZh<}^2Q(vcwtbMxhy;L>u9dz6^Y;DF;lJ=fo*hX5%hoe%$2rWIMXBk7e0U%Y zV9@&dfjf`4?P6qP{$!WNC2iT~bxG46ud_}@rd9Zqv*S>V@4*MD#Uv-ynGaHtOMX>V!Z07*qoM6N<$f{M+_Q2+n{ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_Y_dark_disabled.png b/dist/icons/overlay/osk_button_Y_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6ce53f9e436caa8b4510b3fa44b242787198b271 GIT binary patch literal 694 zcmV;n0!jUeP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd2emGB5}Z{!NChEOu;S36 zlL5h{-uGz9XbmD-Ew7>JA}*Z@#iD{(#4g=TcSo^CgLuEG;ed?(Xj1 z2PT2TDS0(u(eu2Sd_G_68ZtLGcdVzU=M`|S+Zh6z^?H4*P$+D7gd{820M7pdps9+g zju>Npx(o`CY*7>N3wRE!0$*DE)g-=3W6J<8#BqEtZ5Emw^5ow!Ywbo51mnQX7XO_f z2*$0oD?o2EC?X~d!@;zbOwe-3BJyBKT75D*JL`$aVoROgOeQno zge1RdtNK;7oD>g$AAnyh7NfqtzK6i^HaVkdD;aW(T`!eN*L>gqq^g&F-`^;eO2gLL zTMqx}G{}H!P(&8X<#N9nM#*cIjjs$F~8p+3XGAOp4c8M}e)B zL`1|(K98jNfR9dyh^%!;?pCjzkSL0l`O{eYRV|L=71v;9eSQ6VE|>FwVTb2iE|-ge zi@;fjC!Cs^df|d7faiHLz^21<6?jIYY1^rys&`$0JxD&Euhr}IF`$xWZliaqI+k`h z?m++wg~GO~j#z6S)A@{Qz#~P000>X1^@s6#OZ}&00004b3#c}2nYxW zd<)Tx%#!6D_zI}Ul+kmkPBf?L1i zx!-d?=l(huj&!8|CFvCmf?(JfQ&iP)5jg`K2ewspQ$*Gr$Ei(CO?^8QWN~pZmf?DX_>>%fp82u>Pf-T)U4S|P9zMbS0i_qUQv2HGny#=QPV0Klj* zX0=={C#|{-ab0%~7>#3*h`a<=fbU)YpK*MJ_Lc#RWwY5wiI5-&hE;W@Gp4HRp63-r z!u}`^N?s=op7*y4TE`KJ9 zqM{X|s^dN41CHaA0ir0n5Bw65d-;6+_wMfQ6W~;Lf~sD%LgIJOOV=xv%B1i6Ux6#7 zQfa+ftzHz7+ZO+b6+-)=^^}M#RIAki&+|4KjYb-H5$Eias#@nUz_x7=$N_f%!Z5rE zoJ%OkI*(D+%>)R*U|eaokMD~WA|h*jk`E+ReQSj{j#FdrVI5YjR4TP>g~Z*o``&lz zY^_$i0i3rPRP|X>S55(hVL0o$?j`nGOQ$AObt0jNk2^a%4->}sb>UTDEGeN&AJXac zWT8;_k!1RN=Y8MbYBrmBV4nVG)Kb-_&1Um*KLA*M#(KRz6h%=nZWSZI3EDs7XJgDe hRjqlR*WID5`UAl3&#)*bw|D>m002ovPDHLkV1l`nIKuz{ literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_backspace.png b/dist/icons/overlay/osk_button_backspace.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad284720ded558efda48766f900bc4426e3cd63 GIT binary patch literal 2919 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 RA&CG0002ovPDHLkV1hb2VV(d0 literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_backspace_dark.png b/dist/icons/overlay/osk_button_backspace_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..19ac8847e42b79bc89d6219359f942926b248980 GIT binary patch literal 2958 zcmV;93vu*`P);VU>uUpy=TW+3)YCd zWks|lc6Mg(eRh3; zW+?6eLFnn}Ib?8f@O}VS#hv9~r{x3^LbManS^yWtoh1dZ;tt@4LZNUN5p6&!Phx19 z8Au3mI1#M}aCY2TQvOd%2xQy#kpMO^^BHkxN%sm$C5;Dordq)blYW*{NNd;re@ zI4`0&0hp;#W-l{esFd1jD%Zh=l#>1Y z|3ktG;s+tb*`DXE2XMGXnSXkox3s^%|7BCTQVvY)U*n~e7ZA}#=*jaofSy#I=!sPz zA;jeX9tY5&QRYq0^Uif$w`?d@iVtEFNJ{w`B3c8WEocp9-VI<;e}BI+7A(aVu?Qrk zydJ;<(EAMbr%WcZ$aURU4aG_cK;#11wtX{zyEVt|A08e)r(7<-ZYWktKq3{$w(Xml z`3IV1N{vQiaivmu(@3ZkEu#7wPeO=0nE7iuMVNVetya5W*REavHWe&Ik0@p!5<>ip zh_vJZuob|vxaBDn3bW!GTL{s#J26l%)Al6QAG4uloAyRg|=F)_9%dr02UL`(@Bvi6oIR4Ua?bt66Hb>$*1p z@J!?i$~f|jWr&$CQA%wHW9$j!8Ot_iUT$(}_q&;DbO!3~?moivygprdnE5u>b-#>c z%L9Zs6HP%LAOSOxF@f^={G3Lku@%5VojS~Xn^NjqhGGR~3i32DF3}X_X%eeMMg$T< z9P4@B769EEWtf@2-eU4JF(#2ud78u~k+wj&T<#Y#5{pDy0?nU4|CCH7vmHQ}Mwww|zSeP^yL7Ucf|;*}e|eg;6H%{i+e`gED1>O= zw{KrBg2~h53?hp32vlH!q?9?&^R@ywQDgieX1+`*^?*hVqhzz$s{uTrQ-YbF@G}!N ze}#%-4``#_$H7*`F#F#jC_SWUSsSUGZ&RoztN~+oKmS&&t|iiLQkS8 zFK3#|R+CVFPM8ee0tq2L;d$P30A_29`@Usa%au~=HEM)LR1&qiJb+JuY}-DIh&BPx z*-(6sh?bPgFG(tHNqx|Se_<{ zm1x|})ZX6SVP?J(Bd=Ks1QD$m92~q43A)T&mzd;fl1ParY}HUo4b|)QO91>)qyE)W z$~!flPYebd%3jiVK5-IF+UrxTR`(1I4J`%GuQA4rQp)dYJRdtITUxW(>|#W8WGD&_ z*eo_MFff?OWX=Qd7mabgCxrN_#`CdZx&c1wb7x^QrEbHWJ9qvaz!Cs&1s#Zper(&e z?uKoogmrpiI!QF;c2(DPUnQdR0K6M;EMn%LODR_cd_GdbK6*1^P2NS=Mp@ zL2ty$0C>>0?Xv%B?@%n z!f~A60=NRf+dyVA^JXb!F5vT_QLEK{1wDCcM6}XRKToMtsx$Lt%>1Orxa~yLE2aFn zMh)KtKCr_Q{PO6%&TBPloauSqx~{IStf5TWOhKa1PESlxqFCht%w9C^ zjovS~QKQC^j*gCpip64L-e*6FM4=zOIe6N5*C0pL0%t_5>%Pd$S778ah9Df~dESQ8PCIRZ$y_OVL?#dbD5W+K(KQHuk;5#@vYyH3^YaYG zO3@%vfdIg9oQIhCMvW3jd!E-Pg!rhjP$_|lTp$2YO5IIFw`i0&5kQ}m@)%Q@Qi2hS zKmg!4&aD7`6!du_It9Sy?(Xg*%%w{4OWgZSB&EC;z!zf9krI&DW}s0xm&;uT;PIGq zqy!}H83@409*<>NPcrkT;?9!dkGN+b03!|M?d|Os19&0sEGhm-#0=D=P$V zM3)0-#GNH&e-d;4YBJt==bh@DIdgUcSb>Buy9XQp1zs>)iA2UVssI2007*qoM6N<$ Ef|*-odH?_b literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_plus.png b/dist/icons/overlay/osk_button_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..5baa5201e64079e4e89874a182ab789b75997fdc GIT binary patch literal 626 zcmV-&0*(ENP)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd||N?<)0v`x*S5d58w`fi#6*C z0Nc*FyCjRbA$gu(7LjKFuFYE^fH&6KZjvO&bwLIg?Lb7HE(idCbrE@@s&%^_LCR0G z9*Ox0;0J&Z@m~)i+^d7IJmh`E&tNT0)Au2Sj;bC+VhRzt8v7ZHG3Is+U|ua-Yqw%f zL-OVs0b^~(982Xrn6@c^&UoUS`vw5bX7h6hVPz_AAAlWCNOlm>63MJ$W16N%)j*Pe z08H%O`xAaut>!ra>8WaK+UUJsD|duE&p)->?O&>TA%w6x&8_6>MImK%t&08ac+hAx zRsnqU-nU4;izNbZ7;_j^-K|-;h@M9wBC>-~FFK0~5qTVilox0_8eq&T@BM(}hZ@ME zx~@!M(tTed-dgIk=bYOh`8!%D0FW%KwOs&vi)2^T*Vfu@9bf_hfFwzdopT!i9%A+x z6#yQjX?i0`lJB#MIrU}i_xtU#7j2U4sOqKiJ{P000>X1^@s6#OZ}&00004b3#c}2nYxW zdSoDdPY zM*JZkL`e}@bwYqEM+5+%sw=MC38Fsu%P%7N!Jyr4e*;J+lb?5YcZXh8{Qyi2#-np4 zL1xEdQms}u#*O82`4=#ZTWiO6GG6Boq~FQe(V(?AZ#>T{tgo+!ai(E2HJi=W%*@Oa&+`(%d7>V2siUfo!Z5s1 zuh+NtTgk+i(ONqvBCD#pA|iQJJ>Bo8k0SC)M2eM4P000>X1^@s6#OZ}&00004b3#c}2nYxW zd zr9AS32TnDJOB+%W`~xxtoW;`7%@lML2gQJ>OASam#K_C#IOGR0TZzW094 z%jKTHC71j!%&f&)t#%vOQPqdQ9pDDg23p3Lqkg~NC>D!f|4CA>*Oxn;&NE;axEiys z2R!$Ee=n2C^cI$s%jH{%MB*iIchL@klPHSPVHlpyl?=b&HE{17f)Py-*$jf*udeQWL0xGrRP$-AzAF=i{5%U!qDZp37QN~N;ul&I<>;xBm-mOang zaY}#(7Zi-xb`_Qwyn{(pRoBK{h8|bPSTo| zh{#)21;F#Xb>O}4`zxyY&UFy**;No@%u&q7^YF?k>G%5$2D9iQ^sKcnTtx#=tyT|! z(2+R literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_plus_disabled.png b/dist/icons/overlay/osk_button_plus_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c23e9d95d2a1abc572252e8d6ff8b0ff94e19151 GIT binary patch literal 664 zcmV;J0%!e+P)P000>X1^@s6#OZ}&00004b3#c}2nYxW zd6N)mftqQ$M>>AiEl z=X~DfzI!fQ=tBQXa#qmy{Y7I;URCdj$W7oH&{WkU5jk)ir7`^%2wD1hs_AF|o(uSTPh48w3~ zr0Li}7LleEG8h$O5M#_+RRtgjf+gU6uh*Mb)pxc=z-L>+h{!=q;mlIimsW`5I5qmS z=sXG%iNteTQUI#e>JIS8;yD3&^ndp&XBkv=dwqTVu?=DX!Z6$f4lSO`z*QRnc+=^0 zp4b5J=M>%pR^tjL^g5YLKFDUXKjKVg_)shskK66`ZD5D#dlabZcDvnPn+5>O%~-G3 y7osT24@N}>xK96O{A7%IrK&a0^S(`MKGbj2k-*l{Sdi8L0000*!*o8`PDptBz8v1TB)fSxF@tX;p{NmYYE01WGuz+Zs zZ{%$N4u{jhgx-nr_YJ?T6BZemzX_KI0EFHmEzMo=1z%?GR?51`4RE){2t&+xUIXYK<2hMY&lw;V^k+@thuV~tlt~|lq_~B z1Y8$YL!lXd7ue#GxEH>tpv}@+XdwCc0}Kj|^aAX>x>P7Vmlf>;N9Jd_8*4B!$pM;Y zoD9Na9nO{&?}3kKk=zR*V&P^w$g*$-sDt)>0XgN2eoXS#$WTSYn$9@BlVzN{+DccT z=oZmtSBgAEd^Pv@9O>1KjK*FFTN)!Xis+J?IBy#o1Io_y;ZM6eR9}|13!GE?!RHNh z1%xss+{LeOc9D2RDVBpIgJN;%voqQ)>vz{Gw zjS>RZhIK${fHg*k2estxynSc=x1e^*!#7CkrLS5MVDPYA{`pV)aI zF6z`QoXe3*I~J`=Y01f;9Ce*wp=M06#uIN6w~6=5O+_kBa3I_>yO*&CWw+u^f6?gC zg=-|;p>*cX1E;MDqJH9}erlhvEO5~e7vZv{?~s9FtBjfDP@!S{0xxGYGd{5VF>FLo z_JQ(`GZ_P<5Y0KU-V$!_g!<(zD*UoX#^^O|;yxes_Ebriz-j-i0A z~h4)QZxLA$4Cc?skJcMX|h#%%sgZ=Z$J-{1_&=!uhrkFFXg4f&%larHae*h2`CRe|2A>s8AN1AB4D0>S$#Gla_#kndqk384FBGRK3(}N z+$OB2nz-f(42Nll6WRKXJTu0H|LDRt}~P9`n28jyKhl<>xK(aS#Bu0R>V zn6Z^_HZ@+`NuA;?cDNl-kzs;QLb=Af_Yk<_;Vxc$buFzWu?|&&N!C6I`c>_y_4Teu zY#MAy6CGAtJUa>ro=1+1{8GH^MKbX1(;FQ32<}X#zS5jzB*ZEDRS~RjbkhpYvQQO& zm7D&}RQT|SLmaNeo|@@bMoYrbH?k$NSy~S|Zng0OfuF#Mla85v2{M-BG?|t23tCfE zsn<#y9s1Lc@oreRH}*HS)5ex*jkVcNhVVU2M)gD(Tg_?pACB+~vgSQr^sca>;LDuQ z4x+8mgqa74&i2wRlo>Vqcs-ekF5`W+seC9Nb`;}kmYT2}G*rhg$vNPYs%N8ZY{1b# z-x00!@&40Tf0Yo?Ai%OUcGbul)n}yaLLV9(C%OGMzH<(8MS| z_cc6eA+W1D^T+c?V6DCZrJkQx;}pnR5X5xOl02Q+{nEWC@CfLIJYvMW%>WLGMz*FrzkxgrfwrU e!vDk2K~kx8ub7>PVmJQ-0+3b?mNgdW`~Lxl>4P5t literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_dark.png b/dist/icons/overlay/osk_button_shift_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c9cc5cd9afe9e8d81c1d8ad711329d715d5df606 GIT binary patch literal 2003 zcmb7F{Xf%-7yrzLMH|y*mvLim*jC~u595}H%|jMuD}|WGP%@brzRKqIFr#T)moAEM zr$VDuQ(`F(MOrC&Sb6Fhi*ieS>E7M{;QKwV*ZZ8;d7pFMznydPLxcTc2Id9;0KftR zNMYJ|T8kX0p4OjGx_fH_gyR!PhiWYc8Yk4|Yf=ODasU8+_g{cY32=;-G)pJ%NvE+{ z>Acvqc!0;_IV7bV>d5L$ z@0#|W`9aN~=GNDhz!&om2H;;_%+Gy4ifyJU84MxUqM6V4<(De)lhE1BC;{N1(#A>Ya~yj4CJO=J2Q(MUh8FSAE2QI%{MR^4yrs0?dJ5BM6n*5_`lz21&ny5Qa_{cMUi(vO~mYa=| zME>;K&ep*@X21zsm1jIU;?2y@kfjZUxZ;mSsde0F2e6kbgPNz6gT7@|oeM(5QWj$k z9?(AHWM{Z%9dwU5bw5E?NaqgcsHsQzPG-&_quk-IHy|Ks*l0NxcU5UNoOKfd{33`q za|s#!xmwEoo|B5w~te#fCYKbf6ZHe+%`=BSl66Y+~xQT7pfe`1uSHkNWHw-Z?QF)u~zZT z_W%=RH|0@X5Ia}JZxb!E<)T}70Gyv&%5!&D+&7%_mtQ^kp4d-3>7$cfgQ2~A-C~|z zglYn^Bj1GDD|aiuI2pp-b7V%(e?7H*YDRn?r~ue_QB}Wx!K3XFs}8C$Vv!oGp(5R% z6=14k)Kj2Yi!_*mLRI^y*=y6|S9dMc-grVh8Rhfku5XQLrq6Zd(*u*4+9ur**zzwd z?;3v4KuzTS4x3nrLviK4?u(;*ly38 z(@+L4ohh;&cHaZiBfP%`85tt#qn4PW@g|N;PhHb6f%ej$wb*aHQSx6o_UkAzlOjQO zDQ4oymN7s?#2bW}W3Xh&lMys)@iEU)vm8Nv7}>}XLY|lJ$H|Jg@GS6>mAVJ}Xe-8By!i*4IDH>CwcZ&H3qUlDQm>`~h;MeX@Zl4C&=SIBkY1C^}&J_f@2^(>)9M#paoT3pVzj^c)J z79SQ1DJBwMX2(&naA{NNj{c_-?GZ7;5s8G=r}tRL9Ctmu+1tQSQ}1or9WD~G3x`+M z8cPtEZ#A|Up^Sa!Z5=l4_!d343m4WW$_0E4_M=SbJn_mm>XeZ3XMWITn2WKBN@58E zQqYa*XOalmEs1ez>SY16OfD4#X#OSN;jy$thWe4pPL+vxSC&C)PW_hOr?~OAA{q?( zl@yog2Wp!(^Q($X{m!vY^nGU#N~_frzqObm?4krD~^F_rXstcMdTIC4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U1dAc}; zSoFT_+bG!NAmDnx%4C71X@cmj78mmn2Pa`Z*%-#CMn>BilV)3;5h&j7rpC8JcLAF>)x8K)9wiqv7gEK3(cW*RfUi!T@M7P1(fJu3kxw-Fg@6f$=v(@z% U>umVE8|Xp?Pgg&ebxsLQ0Bts2Bme*a literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_lock_on.png b/dist/icons/overlay/osk_button_shift_lock_on.png new file mode 100644 index 0000000000000000000000000000000000000000..09077ab015f84fdee0e2303dcfeb2eb1e7b8480a GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1V0JzX3_ zEPCHgP~tt^C@8W>{+ok_*`-47FH{7W); zlckOQ)N`^`Z{^>6=Q`A6e7W~Np>l4iRAb;JN|g8RT9(yDXc))_(rxg-QKu2h*HJvm zek&pmt|RX10+%&B#!=cpTi4%r(fJ)Je9=77vrB8r+NT6B(@eEEn*X6r4;^K%yPlK8 zPG!-rzcbCYLwLC;=L475+DZbsIwd9S$R6L)?Jn%L>4I%LH>hssEzAc}Bntfju2Fex zTmz1>T#Zuq90xF-CxnchjO`Jw+SU7uS-O~3$j;pxyc`7p{;lE_K+_z73UFU%h#Pl4{yB(s;XDp5d;rJ@boTgsSGZO6*>L_ZqHygW_rOlnaggCDy z=T2EM3mS9>>Km&V|IPClHG3smbQZ8@*qc*i>37__y!Y5R;g zj%PYMq^^`-=^H%2B&5bb?%~1LW!O6-l(#mKlcDmpO(6r8MhZh^ zM;*o&Y7ZOUQ;v*PRUWFddr%SO^y`xUgnYC33t>X0t2zns7K<`5@JEyDF1Mt&vxtlq%Hc%~YE z-=!h?h3*!rF4C`kIi`oZQJORlqxBc_M-)#29cW}HbQYg@#`XAN4^TohO_QjvA-u5? z{NCCa8gRbsa7w8tId`TsgkE;|bi~tSdJ76WiOUVVoubTu&SZSe{{?b)!md!okky6I zqd*OBk0)|?J1>W~fwwk0a0wY8YDcnei^DZmc6eVkf7hg`76h5ESVWao3GO1y8>j@d z74ND=!9!gARYQ%xGgeZ~R{LR+T>T20H41IzYf@2&Du0rHHzdqz4lFOOZ0bYfZKYG- z(vD>M&BVe%+uf1n)XN{!M-0tSgS4fpNFNIoD;~l6qUeiTUn5Bka>1{CgL8c?SabL@ z*?x3^PTM5qQDnTlW?59^2>;0oG%^Xd2$#4uzRYdw5iKALnTXc3WVmhn=%jNZZxr@v zZH*!r>A%|OV}>qpZ}t`vcM=`b=mr z^QIr@X%k8Dsp&NxAH&V1Zz3th6U#dO^E#n2`K46jn@iKz>J){0s4QKPdqgA50^MQA`BQade1@LNdOXD^F8 zd2!-TmR&hEA=BTP;fys(J&hEVSf24jw YMJ6Iy=w)?(;H86zjz{qK5BjD2157^Pw*UYD literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/osk_button_shift_on_dark.png b/dist/icons/overlay/osk_button_shift_on_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..58e0d9cf41efc02d579aa2bfde0104e37d6849ca GIT binary patch literal 1937 zcmcIl2~$(q628{}VFCf;Lxjj~Fa`u!7}|qVy(;>3jjzB$lD_%`{trJvLz}(_r>z| z&%2Impjg^w4c9#Hvu;$Wc1;^~@5VS@_i}!84yCh;QI}s4e)B5UEMOF zo?PPBw3EAM;3vpHPHtfq8Cl(k~lQn&_MeR=2$lo0rNWUpCAY z3utsAMASK{ncYtuSJ#UtN8VbG?R{;ze%-7PxumAapXPh9Tt7K@wG#IlO@;Fzi5HN` zn9SD16dFlwezJeVj+)!*8>!DsF@jd;gA7gTn}^pT2+7*YpyhxrfE|W*2ef=r7kyTV3@u;IZ6EXPXPQC#8Wko*E{kTC0 zE`qGpeN!fK$K(ti=Vkv-I$`(6+*vb-6heDr)6seO4N6VQ_CBF8`$qNOP&I9e!frmL z+CRtk7#h$K(8a;QKBTVqGB^h}2jES)_~(&_SUq>X0-zg$H|>(27fioR)$G9gd{=@z zcxTBmrTce*Y{lS=$@6b#aIaL@oPLF6oF{30xbMo2SW4s^*RYB|l*~f7so;ji){Jgs*2i@yVrVlMo=?#E0#g)_nnT zm;t?RiEZF`edE`@Sv?DT#L1axO7^z$hH^&DihAmL=!wyfFH&J@>x}mQesh}fwC#Nv zKWO<~e-QXQS|_D>k^jr91!*JYeKxG*C^i0MR8rfR_6hNF`+my!7wm!Ll2foiC#=Qo z+8FOi6}^ciSA2!YE{bBOZ3Kmb{x##1xNbjT<*~(As8)lMNzFJinT}?k9HQ zy128*K<@rFyCaJiPl!7@Uf&wQ*2NrK#(y~4`AZ`GqhR4t&n40Xwuw@lL%hRWXl5z-Xv<_R+A*;^Pnr&|QkgitJlT60_`|n7XPmj&AQW_e={v*!pLGn;fJQJLY{)<0w<1Sl6oBIQV&79!y5v_`0od-jLu%K1Mzf}B$vwrDC%$hS7Xn7%&zf=3R^^Ul+ zG)zXdZnvI)Fl1m=7E`?#Mp@`qnTr<21m@%WW`{X)*}GPmPC6#X{7}2B*mGWhNE59Y zQiui6hHFNU7oy-&&pUynpE1x2meiQ2pS#yH0O0fkxr9as4ejxd3Bm^IN=&~zHoHtU zNawv)+#^oTG2t7=N*^9O7!nsNH%4__3%O85_Q6oNhD=T+KSGkJb*8e~V%6Y`vqVv> zVX+59MVPo7`zswdTMQH&#MG6U?$CPZP4}!)CH=nZA3c1z4I*pyNYk6c=i%=YfP4bI J+dSib{6ENObEyCT literal 0 HcmV?d00001 diff --git a/dist/icons/overlay/overlay.qrc b/dist/icons/overlay/overlay.qrc new file mode 100644 index 0000000000..d5a21ce10d --- /dev/null +++ b/dist/icons/overlay/overlay.qrc @@ -0,0 +1,64 @@ + + + arrow_left.png + arrow_left_dark.png + arrow_right.png + arrow_right_dark.png + button_minus.png + button_minus_dark.png + button_plus.png + button_plus_dark.png + button_A.png + button_A_dark.png + button_B.png + button_B_dark.png + button_X.png + button_X_dark.png + button_Y.png + button_Y_dark.png + button_L.png + button_L_dark.png + button_R.png + button_R_dark.png + button_press_stick.png + button_press_stick_dark.png + osk_button_B.png + osk_button_B_disabled.png + osk_button_B_dark.png + osk_button_B_dark_disabled.png + osk_button_Y.png + osk_button_Y_disabled.png + osk_button_Y_dark.png + osk_button_Y_dark_disabled.png + osk_button_backspace.png + osk_button_backspace_dark.png + osk_button_plus.png + osk_button_plus_disabled.png + osk_button_plus_dark.png + osk_button_plus_dark_disabled.png + osk_button_shift.png + osk_button_shift_dark.png + osk_button_shift_on.png + osk_button_shift_on_dark.png + osk_button_shift_lock_on.png + osk_button_shift_lock_off.png + controller_dual_joycon.png + controller_dual_joycon_dark.png + controller_pro.png + controller_pro_dark.png + controller_handheld.png + controller_handheld_dark.png + controller_single_joycon_left.png + controller_single_joycon_left_dark.png + controller_single_joycon_right.png + controller_single_joycon_right_dark.png + controller_single_joycon_left_a.png + controller_single_joycon_left_a_dark.png + controller_single_joycon_left_b.png + controller_single_joycon_left_b_dark.png + controller_single_joycon_left_x.png + controller_single_joycon_left_x_dark.png + controller_single_joycon_left_y.png + controller_single_joycon_left_y_dark.png + + From f6e6913f8ff5f533e69a5831a81ca8f15f709baf Mon Sep 17 00:00:00 2001 From: Its-Rei Date: Sat, 20 Mar 2021 07:53:00 -0400 Subject: [PATCH 09/14] qt_themes: Add styles for the On-Screen Keyboard and OverlayDialog --- dist/qt_themes/default/style.qss | 377 +++++++++++++++ dist/qt_themes/qdarkstyle/style.qss | 399 +++++++++++++++- .../qdarkstyle_midnight_blue/style.qss | 439 +++++++++++++++++- 3 files changed, 1193 insertions(+), 22 deletions(-) diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 836dd25ca4..3bc92b69d5 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -281,3 +281,380 @@ QWidget#controllerPlayer7, QWidget#controllerPlayer8 { background: transparent; } + +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(51, 51, 51, .9); +} + + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(51, 51, 51, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(240, 240, 240, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(44, 44, 44, 1); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(44, 44, 44, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::vertical { + background: #cdcdcd; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent; + border-radius: 4px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::horizoncal { + background: #cdcdcd; + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent; + border-radius: 4px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::handle { + background: #fff; + border-radius: 4px; + min-height: 5px; + min-width: 5px; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-page, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-page { + background: none; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + padding: 35px 65px; +} + + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + color: #888; +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(49, 79, 239, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(49, 79, 239, 1); + background: rgba(255, 255, 255, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + background: rgba(232, 232, 232, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(218, 218, 218, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(44, 44, 44, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(240, 240, 240, 1); + background: rgba(49, 79, 239, 1); + border: 2px solid rgba(240, 240, 240, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(0, 0, 0, 1); + background: rgba(255, 255, 255, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(148, 250, 202, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B.png); + qproperty-icon: url(:/overlay/osk_button_backspace.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(164, 164, 164, 1); + background-color: rgba(218, 218, 218, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(164, 164, 164, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_disabled.png); +} diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 2a1e8ddebe..8ce6d75f75 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1560,7 +1560,400 @@ QWidget#controllerPlayer8 { background: transparent; } -/* touchscreen mapping widget */ -TouchScreenPreview { - qproperty-dotHighlightColor: #3daee9; +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(41, 41, 41, .9); +} + + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(41, 41, 41, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(71, 69, 71, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(255, 255, 255, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QWidget { + background: transparent; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar { + background: #2a2929; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line { + border-image: none; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; + padding: 0px; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + color: #fff; + padding: 35px 65px; +} + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog, +QWidget#mainOSK, +QWidget#headerOSK, +QWidget#normalOSK, +QWidget#shiftOSK, +QWidget#numOSK, +QWidget#subOSK, +QWidget#inputOSK, +QWidget#inputBoxOSK, +QWidget#charactersOSK, +QWidget#charactersBoxOSK, +QWidget#legendOSK, +QWidget#legendOSK QWidget, +QWidget#legendOSKshift, +QWidget#legendOSKshift QWidget, +QWidget#legendOSKnum, +QWidget#legendOSKnum QWidget { + background: transparent; +} + +QWidget#contentDialog QLabel, +QWidget#legendOSK QLabel, +QWidget#legendOSKshift QLabel, +QWidget#legendOSKnum QLabel { + color: rgba(255, 255, 255, 1); +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(1, 253, 201, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(1, 253, 201, 1); + background: rgba(58, 61, 66, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + color: rgba(255, 255, 255, 1); + background: rgba(80, 79, 80, 1); + border: 2px solid rgba(71, 69, 71, 1); + padding: 0px; + min-width: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(95, 94, 95, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(255, 255, 255, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(0, 0, 0, 1); + background: rgba(1, 253, 201, 1); + border: 2px solid rgba(71, 69, 71, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(255, 255, 255, 1); + background: rgba(58, 61, 66, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(150, 150, 150, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B_dark.png); + qproperty-icon: url(:/overlay/osk_button_backspace_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(44, 44, 44, 1); + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(144, 144, 144, 1); + background-color: rgba(95, 94, 95, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(144, 144, 144, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QFrame, +QDialog#QtSoftwareKeyboardDialog QFrame[frameShape="0"], +QDialog#OverlayDialog QFrame, +QDialog#OverlayDialog QFrame[frameShape="0"] { + border-radius: 0px; + border: none; } diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index a640374550..64e1ecbcc6 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -1,10 +1,10 @@ /* --------------------------------------------------------------------------- - Created by the qtsass compiler v0.1.1 + Created by the qtsass compiler v0.1.1 - The definitions are in the "qdarkstyle.qss._styles.scss" module + The definitions are in the "qdarkstyle.qss._styles.scss" module - WARNING! All changes made in this file will be lost! + WARNING! All changes made in this file will be lost! --------------------------------------------------------------------------- */ /* QDarkStyleSheet ----------------------------------------------------------- @@ -15,34 +15,34 @@ It is based on three selecting colors, three greyish (background) colors plus three whitish (foreground) colors. Each set of widgets of the same type have a header like this: - ------------------ - GroupName -------- - ------------------ + ------------------ + GroupName -------- + ------------------ And each widget is separated with a header like this: - QWidgetName ------ + QWidgetName ------ This makes more easy to find and change some css field. The basic configuration is described bellow. - BACKGROUND ----------- + BACKGROUND ----------- - Light (unpressed) - Normal (border, disabled, pressed, checked, toolbars, menus) - Dark (background) + Light (unpressed) + Normal (border, disabled, pressed, checked, toolbars, menus) + Dark (background) - FOREGROUND ----------- + FOREGROUND ----------- - Light (texts/labels) - Normal (not used yet) - Dark (disabled texts) + Light (texts/labels) + Normal (not used yet) + Dark (disabled texts) - SELECTION ------------ + SELECTION ------------ - Light (selection/hover/active) - Normal (selected) - Dark (selected disabled) + Light (selection/hover/active) + Normal (selected) + Dark (selected disabled) If a stranger configuration is required because of a bugfix or anything else, keep the comment on the line above so nobody changes it, including the @@ -2483,3 +2483,404 @@ QWidget#controllerPlayer7, QWidget#controllerPlayer8 { background: transparent; } + +QDialog#QtSoftwareKeyboardDialog, +QStackedWidget#topOSK { + background: rgba(15, 25, 34, .9); +} + +QDialog#OverlayDialog, +QStackedWidget#stackedDialog { + background: rgba(15, 25, 34, .7); +} + +QWidget#boxOSK, +QWidget#lineOSK, +QWidget#richDialog, +QWidget#lineDialog { + background: transparent; +} + +QStackedWidget#bottomOSK, +QWidget#contentDialog, +QWidget#contentRichDialog { + background: rgba(31, 41, 51, 1); +} + +QWidget#contentDialog, +QWidget#contentRichDialog { + margin: 5px; + border-radius: 6px; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog { + margin: 5px; + border-top: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#legendOSKnum { + border-top: 1px solid rgba(255, 255, 255, 1); +} + +QStackedWidget#stackedDialog QTextBrowser QWidget { + background: transparent; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar { + background: #19232d; + border: none; +} + +QStackedWidget#stackedDialog QTextBrowser QScrollBar::sub-line, +QStackedWidget#stackedDialog QTextBrowser QScrollBar::add-line { + border-image: none; +} + +QWidget#mainOSK QStackedWidget, +QDialog#OverlayDialog QStackedWidget { + border: none; + padding: 0px; +} + +QWidget#inputOSK { + border-bottom: 3px solid rgba(255, 255, 255, .9); +} + +QWidget#inputOSK QLineEdit { + background: transparent; + border: none; + color: #ccc; + padding: 0px; +} + +QWidget#inputBoxOSK { + border: 2px solid rgba(255, 255, 255, .9); +} + +QWidget#inputBoxOSK QTextEdit { + background: transparent; + border: none; + color: #ccc; +} + +QWidget#richDialog QTextBrowser { + background: transparent; + border: none; + color: #fff; + padding: 35px 65px; +} + +QWidget#lineOSK QLabel#label_header { + color: #f0f0f0; +} + +QWidget#lineOSK QLabel#label_sub, +QWidget#lineOSK QLabel#label_characters, +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich, +QWidget#boxOSK QLabel#label_characters_box { + color: #ccc; +} + +QWidget#buttonsDialog, +QWidget#buttonsRichDialog, +QWidget#mainOSK, +QWidget#headerOSK, +QWidget#normalOSK, +QWidget#shiftOSK, +QWidget#numOSK, +QWidget#subOSK, +QWidget#inputOSK, +QWidget#inputBoxOSK, +QWidget#charactersOSK, +QWidget#charactersBoxOSK, +QWidget#legendOSK, +QWidget#legendOSK QWidget, +QWidget#legendOSKshift, +QWidget#legendOSKshift QWidget, +QWidget#legendOSKnum, +QWidget#legendOSKnum QWidget { + background: transparent; +} + +QWidget#contentDialog QLabel, +QWidget#legendOSK QLabel, +QWidget#legendOSKshift QLabel, +QWidget#legendOSKnum QLabel { + color: rgba(255, 255, 255, 1); +} + +QWidget#contentDialog QLabel#label_dialog { + padding: 20px 65px; +} + +QWidget#contentDialog QLabel#label_title, +QWidget#contentRichDialog QLabel#label_title_rich { + padding: 0px 65px; +} + +QDialog#OverlayDialog QPushButton { + color: rgba(1, 253, 201, 1); + background: transparent; + border: none; + padding: 0px; + min-width: 0px; +} + +QDialog#OverlayDialog QPushButton:focus, +QDialog#OverlayDialog QPushButton:hover { + color: rgba(1, 253, 201, 1); + background: rgba(18, 33, 46, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#OverlayDialog QPushButton:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(110, 122, 130, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QLabel { + padding: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton { + color: rgba(255, 255, 255, 1); + background: rgba(40, 51, 60, 1); + border: 2px solid rgba(31, 41, 51, 1); + border-radius: 0px; + padding: 0px; + min-width: 0px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background: rgba(55, 66, 75, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + color: rgba(240, 240, 240, 1); + background: rgba(255, 255, 255, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(0, 0, 0, 1); + background: rgba(1, 253, 201, 1); + border: 2px solid rgba(31, 41, 51, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus, + +QDialog#QtSoftwareKeyboardDialog QPushButton:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover { + color: rgba(255, 255, 255, 1); + background: rgba(18, 33, 46, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; + outline: none; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed { + color: rgba(240, 240, 240, 1); + background: rgba(110, 122, 130, 1); + border: 5px solid rgba(56, 189, 225, 1); + border-radius: 6px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_B_dark.png); + qproperty-icon: url(:/overlay/osk_button_backspace_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift { + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_Y_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num { + color: rgba(44, 44, 44, 1); + background-position: right top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_plus_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift { + background-position: left top; + background-repeat: no-repeat; + background-origin: content; + background-image: url(:/overlay/osk_button_shift_lock_off.png); + qproperty-icon: url(:/overlay/osk_button_shift_on_dark.png); + qproperty-iconSize: 36px; +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_bracket, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_left_parenthesis, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_right_parenthesis { + padding-bottom: 7px; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#titleOSK QLabel { + background: transparent; + color: #ccc; +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_L, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_L_num { + image: url(:/overlay/button_L_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_left_num { + image: url(:/overlay/arrow_left_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_R, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_R_num { + image: url(:/overlay/button_R_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#arrow_right_num { + image: url(:/overlay/arrow_right_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick, +QDialog#QtSoftwareKeyboardDialog QWidget#button_press_stick_shift { + image: url(:/overlay/button_press_stick_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_X, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_X_num { + image: url(:/overlay/button_X_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QWidget#button_A, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_shift, +QDialog#QtSoftwareKeyboardDialog QWidget#button_A_num { + image: url(:/overlay/button_A_dark.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + color: rgba(144, 144, 144, 1); + background-color: rgba(55, 66, 75, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_slash:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_percent:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_1:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_2:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_3:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_4:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_5:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_6:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_7:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled { + color: rgba(144, 144, 144, 1); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled { + background-image: url(:/overlay/osk_button_plus_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled { + background-image: url(:/overlay/osk_button_B_dark_disabled.png); +} + +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:disabled, +QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:disabled { + background-image: url(:/overlay/osk_button_Y_dark_disabled.png); +} From aa3adf6c3fc20171abcbd2678ed7ad6b3bd21a8e Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 20 Mar 2021 07:55:59 -0400 Subject: [PATCH 10/14] input_interpreter: Fix button hold being interpreted incorrectly on init We reset all the button states to 0 except the first index (which has all the buttons as pressed) to prevent a button hold being interpreted as a button that was pressed once on the first poll. --- src/core/frontend/input_interpreter.cpp | 15 ++++++++++++++- src/core/frontend/input_interpreter.h | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp index ec5fe660ed..9f6a90e8f1 100644 --- a/src/core/frontend/input_interpreter.cpp +++ b/src/core/frontend/input_interpreter.cpp @@ -12,7 +12,9 @@ InputInterpreter::InputInterpreter(Core::System& system) : npad{system.ServiceManager() .GetService("hid") ->GetAppletResource() - ->GetController(Service::HID::HidController::NPad)} {} + ->GetController(Service::HID::HidController::NPad)} { + ResetButtonStates(); +} InputInterpreter::~InputInterpreter() = default; @@ -25,6 +27,17 @@ void InputInterpreter::PollInput() { button_states[current_index] = button_state; } +void InputInterpreter::ResetButtonStates() { + previous_index = 0; + current_index = 0; + + button_states[0] = 0xFFFFFFFF; + + for (std::size_t i = 1; i < button_states.size(); ++i) { + button_states[i] = 0; + } +} + bool InputInterpreter::IsButtonPressed(HIDButton button) const { return (button_states[current_index] & (1U << static_cast(button))) != 0; } diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h index 73fc47ffbf..9495e3daf0 100644 --- a/src/core/frontend/input_interpreter.h +++ b/src/core/frontend/input_interpreter.h @@ -66,6 +66,9 @@ public: /// Gets a button state from HID and inserts it into the array of button states. void PollInput(); + /// Resets all the button states to their defaults. + void ResetButtonStates(); + /** * Checks whether the button is pressed. * From 4a5f9f5a6d3bfd8796f52d6c778a8abb3922ff40 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 26 Mar 2021 05:26:16 -0400 Subject: [PATCH 11/14] main: Move meta type registration into its own function Moves the existing meta type registration into its own function and adds registration of common integral, floating point and string types. This function is also now called in the constructor of the GMainWindow instead of on starting a game. --- src/yuzu/main.cpp | 58 +++++++++++++++++++++++++++++++++++++++++------ src/yuzu/main.h | 16 +++++++++++-- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e9d6e74211..422b3cff66 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -227,6 +227,8 @@ GMainWindow::GMainWindow() SetDiscordEnabled(UISettings::values.enable_discord_presence); discord_rpc->Update(); + RegisterMetaTypes(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -375,6 +377,55 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::RegisterMetaTypes() { + // Register integral and floating point types + qRegisterMetaType("u8"); + qRegisterMetaType("u16"); + qRegisterMetaType("u32"); + qRegisterMetaType("u64"); + qRegisterMetaType("u128"); + qRegisterMetaType("s8"); + qRegisterMetaType("s16"); + qRegisterMetaType("s32"); + qRegisterMetaType("s64"); + qRegisterMetaType("f32"); + qRegisterMetaType("f64"); + + // Register string types + qRegisterMetaType("std::string"); + qRegisterMetaType("std::wstring"); + qRegisterMetaType("std::u8string"); + qRegisterMetaType("std::u16string"); + qRegisterMetaType("std::u32string"); + qRegisterMetaType("std::string_view"); + qRegisterMetaType("std::wstring_view"); + qRegisterMetaType("std::u8string_view"); + qRegisterMetaType("std::u16string_view"); + qRegisterMetaType("std::u32string_view"); + + // Register applet types + + // Controller Applet + qRegisterMetaType("Core::Frontend::ControllerParameters"); + + // Software Keyboard Applet + qRegisterMetaType( + "Core::Frontend::KeyboardInitializeParameters"); + qRegisterMetaType( + "Core::Frontend::InlineAppearParameters"); + qRegisterMetaType("Core::Frontend::InlineTextParameters"); + qRegisterMetaType("Service::AM::Applets::SwkbdResult"); + qRegisterMetaType( + "Service::AM::Applets::SwkbdTextCheckResult"); + qRegisterMetaType("Service::AM::Applets::SwkbdReplyType"); + + // Web Browser Applet + qRegisterMetaType("Service::AM::Applets::WebExitReason"); + + // Register loader types + qRegisterMetaType("Core::System::ResultStatus"); +} + void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); @@ -2166,13 +2217,6 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); - qRegisterMetaType("Core::Frontend::ControllerParameters"); - qRegisterMetaType("Core::System::ResultStatus"); - qRegisterMetaType("std::string"); - qRegisterMetaType>("std::optional"); - qRegisterMetaType("std::string_view"); - qRegisterMetaType("Service::AM::Applets::WebExitReason"); - connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6429549ae7..d8849f85b0 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -37,9 +37,13 @@ enum class GameListRemoveTarget; enum class InstalledEntryType; class GameListPlaceholder; +class QtSoftwareKeyboardDialog; + namespace Core::Frontend { struct ControllerParameters; -struct SoftwareKeyboardParameters; +struct InlineAppearParameters; +struct InlineTextParameters; +struct KeyboardInitializeParameters; } // namespace Core::Frontend namespace DiscordRPC { @@ -57,8 +61,11 @@ class InputSubsystem; } namespace Service::AM::Applets { +enum class SwkbdResult : u32; +enum class SwkbdTextCheckResult : u32; +enum class SwkbdReplyType : u32; enum class WebExitReason : u32; -} +} // namespace Service::AM::Applets enum class EmulatedDirectoryTarget { NAND, @@ -143,6 +150,8 @@ public slots: void OnAppFocusStateChanged(Qt::ApplicationState state); private: + void RegisterMetaTypes(); + void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); @@ -329,6 +338,9 @@ private: // Disables the web applet for the rest of the emulated session bool disable_web_applet{}; + // Applets + QtSoftwareKeyboardDialog* software_keyboard = nullptr; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; From 4143675b2df288534e6e1a3f06a87d88dbaba257 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 20 Mar 2021 07:57:31 -0400 Subject: [PATCH 12/14] overlay_dialog: Add an overlay text dialog that accepts controller input An OverlayDialog is an interactive dialog that accepts controller input (while a game is running) This dialog attempts to replicate the look and feel of the Nintendo Switch's overlay dialogs and provide some extra features such as embedding HTML/Rich Text content in a QTextBrowser. The OverlayDialog provides 2 modes: one to embed regular text into a QLabel and another to embed HTML/Rich Text content into a QTextBrowser. Co-authored-by: Its-Rei --- src/yuzu/CMakeLists.txt | 3 + src/yuzu/main.cpp | 6 +- src/yuzu/util/overlay_dialog.cpp | 249 +++++++++++++++++++ src/yuzu/util/overlay_dialog.h | 107 ++++++++ src/yuzu/util/overlay_dialog.ui | 404 +++++++++++++++++++++++++++++++ 5 files changed, 768 insertions(+), 1 deletion(-) create mode 100644 src/yuzu/util/overlay_dialog.cpp create mode 100644 src/yuzu/util/overlay_dialog.h create mode 100644 src/yuzu/util/overlay_dialog.ui diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index b025ced1c8..3e00ff39ff 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -143,6 +143,9 @@ add_executable(yuzu uisettings.h util/limitable_input_dialog.cpp util/limitable_input_dialog.h + util/overlay_dialog.cpp + util/overlay_dialog.h + util/overlay_dialog.ui util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h util/url_request_interceptor.cpp diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 422b3cff66..3d4558739e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -101,6 +101,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/perf_stats.h" #include "core/telemetry_session.h" #include "input_common/main.h" +#include "util/overlay_dialog.h" #include "video_core/gpu.h" #include "video_core/shader_notify.h" #include "yuzu/about_dialog.h" @@ -2266,7 +2267,10 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) { } void GMainWindow::ErrorDisplayDisplayError(QString body) { - QMessageBox::critical(this, tr("Error Display"), body); + OverlayDialog dialog(render_window, Core::System::GetInstance(), body, QString{}, tr("OK"), + Qt::AlignLeft | Qt::AlignVCenter); + dialog.exec(); + emit ErrorDisplayFinished(); } diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp new file mode 100644 index 0000000000..95b1485450 --- /dev/null +++ b/src/yuzu/util/overlay_dialog.cpp @@ -0,0 +1,249 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_overlay_dialog.h" +#include "yuzu/util/overlay_dialog.h" + +namespace { + +constexpr float BASE_TITLE_FONT_SIZE = 14.0f; +constexpr float BASE_FONT_SIZE = 18.0f; +constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; + +} // Anonymous namespace + +OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text, + const QString& body_text, const QString& left_button_text, + const QString& right_button_text, Qt::Alignment alignment, + bool use_rich_text_) + : QDialog(parent), ui{std::make_unique()}, use_rich_text{use_rich_text_} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_TranslucentBackground); + + if (use_rich_text) { + InitializeRichTextDialog(title_text, body_text, left_button_text, right_button_text, + alignment); + } else { + InitializeRegularTextDialog(title_text, body_text, left_button_text, right_button_text, + alignment); + } + + MoveAndResizeWindow(); + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + + StartInputThread(); + } +} + +OverlayDialog::~OverlayDialog() { + StopInputThread(); +} + +void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment) { + ui->stackedDialog->setCurrentIndex(0); + + ui->label_title->setText(title_text); + ui->label_dialog->setText(body_text); + ui->button_cancel->setText(left_button_text); + ui->button_ok_label->setText(right_button_text); + + ui->label_dialog->setAlignment(alignment); + + if (title_text.isEmpty()) { + ui->label_title->hide(); + ui->verticalLayout_2->setStretch(0, 0); + ui->verticalLayout_2->setStretch(1, 219); + ui->verticalLayout_2->setStretch(2, 82); + } + + if (left_button_text.isEmpty()) { + ui->button_cancel->hide(); + ui->button_cancel->setEnabled(false); + } + + if (right_button_text.isEmpty()) { + ui->button_ok_label->hide(); + ui->button_ok_label->setEnabled(false); + } + + connect( + ui->button_cancel, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::reject(); + }, + Qt::QueuedConnection); + connect( + ui->button_ok_label, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::accept(); + }, + Qt::QueuedConnection); +} + +void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment) { + ui->stackedDialog->setCurrentIndex(1); + + ui->label_title_rich->setText(title_text); + ui->text_browser_dialog->setText(body_text); + ui->button_cancel_rich->setText(left_button_text); + ui->button_ok_rich->setText(right_button_text); + + // TODO (Morph/Rei): Replace this with something that works better + ui->text_browser_dialog->setAlignment(alignment); + + if (title_text.isEmpty()) { + ui->label_title_rich->hide(); + ui->verticalLayout_3->setStretch(0, 0); + ui->verticalLayout_3->setStretch(1, 438); + ui->verticalLayout_3->setStretch(2, 82); + } + + if (left_button_text.isEmpty()) { + ui->button_cancel_rich->hide(); + ui->button_cancel_rich->setEnabled(false); + } + + if (right_button_text.isEmpty()) { + ui->button_ok_rich->hide(); + ui->button_ok_rich->setEnabled(false); + } + + connect( + ui->button_cancel_rich, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::reject(); + }, + Qt::QueuedConnection); + connect( + ui->button_ok_rich, &QPushButton::clicked, this, + [this](bool) { + StopInputThread(); + QDialog::accept(); + }, + Qt::QueuedConnection); +} + +void OverlayDialog::MoveAndResizeWindow() { + const auto pos = parentWidget()->mapToGlobal(parentWidget()->rect().topLeft()); + const auto width = static_cast(parentWidget()->width()); + const auto height = static_cast(parentWidget()->height()); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto body_text_font_size = + BASE_FONT_SIZE * (((width / BASE_WIDTH) + (height / BASE_HEIGHT)) / 2.0f) / dpi_scale; + const auto button_text_font_size = BASE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont title_text_font(QStringLiteral("MS Shell Dlg 2"), title_text_font_size, QFont::Normal); + QFont body_text_font(QStringLiteral("MS Shell Dlg 2"), body_text_font_size, QFont::Normal); + QFont button_text_font(QStringLiteral("MS Shell Dlg 2"), button_text_font_size, QFont::Normal); + + if (use_rich_text) { + ui->label_title_rich->setFont(title_text_font); + ui->text_browser_dialog->setFont(body_text_font); + ui->button_cancel_rich->setFont(button_text_font); + ui->button_ok_rich->setFont(button_text_font); + } else { + ui->label_title->setFont(title_text_font); + ui->label_dialog->setFont(body_text_font); + ui->button_cancel->setFont(button_text_font); + ui->button_ok_label->setFont(button_text_font); + } + + QDialog::move(pos); + QDialog::resize(width, height); +} + +template +void OverlayDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void OverlayDialog::TranslateButtonPress(HIDButton button) { + QPushButton* left_button = use_rich_text ? ui->button_cancel_rich : ui->button_cancel; + QPushButton* right_button = use_rich_text ? ui->button_ok_rich : ui->button_ok_label; + + // TODO (Morph): Handle QTextBrowser text scrolling + // TODO (Morph): focusPrevious/NextChild() doesn't work well with the rich text dialog, fix it + + switch (button) { + case HIDButton::A: + case HIDButton::B: + if (left_button->hasFocus()) { + left_button->click(); + } else if (right_button->hasFocus()) { + right_button->click(); + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + focusPreviousChild(); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + focusNextChild(); + break; + default: + break; + } +} + +void OverlayDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&OverlayDialog::InputThread, this); +} + +void OverlayDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } +} + +void OverlayDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h new file mode 100644 index 0000000000..e8c388bd01 --- /dev/null +++ b/src/yuzu/util/overlay_dialog.h @@ -0,0 +1,107 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/common_types.h" + +enum class HIDButton : u8; + +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class OverlayDialog; +} + +/** + * An OverlayDialog is an interactive dialog that accepts controller input (while a game is running) + * This dialog attempts to replicate the look and feel of the Nintendo Switch's overlay dialogs and + * provide some extra features such as embedding HTML/Rich Text content in a QTextBrowser. + * The OverlayDialog provides 2 modes: one to embed regular text into a QLabel and another to embed + * HTML/Rich Text content into a QTextBrowser. + */ +class OverlayDialog final : public QDialog { + Q_OBJECT + +public: + explicit OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text, + const QString& body_text, const QString& left_button_text, + const QString& right_button_text, + Qt::Alignment alignment = Qt::AlignCenter, bool use_rich_text_ = false); + ~OverlayDialog() override; + +private: + /** + * Initializes a text dialog with a QLabel storing text. + * Only use this for short text as the dialog buttons would be squashed with longer text. + * + * @param title_text Title text to be displayed + * @param body_text Main text to be displayed + * @param left_button_text Left button text. If empty, the button is hidden and disabled + * @param right_button_text Right button text. If empty, the button is hidden and disabled + * @param alignment Main text alignment + */ + void InitializeRegularTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, + const QString& right_button_text, Qt::Alignment alignment); + + /** + * Initializes a text dialog with a QTextBrowser storing text. + * This is ideal for longer text or rich text content. A scrollbar is shown for longer text. + * + * @param title_text Title text to be displayed + * @param body_text Main text to be displayed + * @param left_button_text Left button text. If empty, the button is hidden and disabled + * @param right_button_text Right button text. If empty, the button is hidden and disabled + * @param alignment Main text alignment + */ + void InitializeRichTextDialog(const QString& title_text, const QString& body_text, + const QString& left_button_text, const QString& right_button_text, + Qt::Alignment alignment); + + /// Moves and resizes the dialog to be fully overlayed on top of the parent window. + void MoveAndResizeWindow(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Translates a button press to focus or click either the left or right buttons. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + bool use_rich_text; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; +}; diff --git a/src/yuzu/util/overlay_dialog.ui b/src/yuzu/util/overlay_dialog.ui new file mode 100644 index 0000000000..278e2f219e --- /dev/null +++ b/src/yuzu/util/overlay_dialog.ui @@ -0,0 +1,404 @@ + + + OverlayDialog + + + + 0 + 0 + 1280 + 720 + + + + Dialog + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 14 + + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 18 + + + + Qt::AlignCenter + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 18 + + + + Cancel + + + + + + + + 0 + 0 + + + + + 18 + + + + OK + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 14 + + + + + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 18 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 18 + + + + Cancel + + + + + + + + 0 + 0 + + + + + 18 + + + + OK + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + From b45930a0edbf762310f85fafa3724dc4766c2197 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 26 Mar 2021 06:07:27 -0400 Subject: [PATCH 13/14] error: Make the error code as the title text of the OverlayDialog Co-authored-by: Its-Rei --- src/yuzu/applets/error.cpp | 22 ++++++++++++---------- src/yuzu/applets/error.h | 2 +- src/yuzu/main.cpp | 6 +++--- src/yuzu/main.h | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp index 8ee03ddb3d..085688cd47 100644 --- a/src/yuzu/applets/error.cpp +++ b/src/yuzu/applets/error.cpp @@ -19,11 +19,11 @@ QtErrorDisplay::~QtErrorDisplay() = default; void QtErrorDisplay::ShowError(ResultCode error, std::function finished) const { callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occurred.\nPlease try again or contact the developer of the " - "software.\n\nError Code: %1-%2 (0x%3)") + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error has occurred.\nPlease try again or contact the developer of the software.")); } void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, @@ -32,13 +32,14 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( - tr("An error occurred on %1 at %2.\nPlease try again or contact the " - "developer of the software.\n\nError Code: %3-%4 (0x%5)") - .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) - .arg(date_time.toString(QStringLiteral("h:mm:ss A"))) + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); + .arg(error.raw, 8, 16, QChar::fromLatin1('0')), + tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " + "software.") + .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) + .arg(date_time.toString(QStringLiteral("h:mm:ss A")))); } void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, @@ -46,10 +47,11 @@ void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_te std::function finished) const { callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occurred.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") + tr("Error Code: %1-%2 (0x%3)") .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) - .arg(error.raw, 8, 16, QChar::fromLatin1('0')) + .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))); } diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h index b0932d8957..8bd895a32d 100644 --- a/src/yuzu/applets/error.h +++ b/src/yuzu/applets/error.h @@ -24,7 +24,7 @@ public: std::function finished) const override; signals: - void MainWindowDisplayError(QString error) const; + void MainWindowDisplayError(QString error_code, QString error_text) const; private: void MainWindowFinishedError(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 3d4558739e..ce83dee278 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -2266,9 +2266,9 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) { BootGame(last_filename_booted, program_index); } -void GMainWindow::ErrorDisplayDisplayError(QString body) { - OverlayDialog dialog(render_window, Core::System::GetInstance(), body, QString{}, tr("OK"), - Qt::AlignLeft | Qt::AlignVCenter); +void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { + OverlayDialog dialog(render_window, Core::System::GetInstance(), error_code, error_text, + QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); dialog.exec(); emit ErrorDisplayFinished(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index d8849f85b0..4c8a879d2c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -143,7 +143,7 @@ public slots: void OnExecuteProgram(std::size_t program_index); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); - void ErrorDisplayDisplayError(QString body); + void ErrorDisplayDisplayError(QString error_code, QString error_text); void ProfileSelectorSelectProfile(); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local); From 7eff91ff20765ba4e7f94a92de6fc0ffa2fc4f2f Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 27 Mar 2021 15:15:32 -0400 Subject: [PATCH 14/14] applets/swkbd: Implement the Qt Software Keyboard frontend The Qt Software Keyboard frontend attempts to mimic the software keyboard rendered by the Nintendo Switch. This frontend implements multiple keyboard types, such as the normal software keyboard, the numeric pad software keyboard and the inline software keyboard. Keyboard and controller input is also supported in this frontend. Keyboard input is handled as native keyboard input, and so the on-screen keyboard cannot be navigated with the keyboard arrow keys as the arrow keys are used to move the text cursor. Controller input is translated into mouse hover movements on the onscreen keyboard or their respective button actions (B for backspace, A for entering the selected button, L/R for moving the text cursor, etc). The text check dialogs can also be confirmed with controller input through the use of the OverlayDialog Massive thanks to Rei for creating all the UI for the various keyboards and OverlayDialog. This would not have been possible without his excellent work. Co-authored-by: Its-Rei --- src/yuzu/CMakeLists.txt | 1 + src/yuzu/applets/software_keyboard.cpp | 1635 ++++++++++- src/yuzu/applets/software_keyboard.h | 267 +- src/yuzu/applets/software_keyboard.ui | 3503 ++++++++++++++++++++++++ src/yuzu/main.cpp | 112 + src/yuzu/main.h | 14 + 6 files changed, 5518 insertions(+), 14 deletions(-) create mode 100644 src/yuzu/applets/software_keyboard.ui diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3e00ff39ff..cc0790e077 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(yuzu applets/profile_select.h applets/software_keyboard.cpp applets/software_keyboard.h + applets/software_keyboard.ui applets/web_browser.cpp applets/web_browser.h bootmanager.cpp diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index da0fed774e..fd3368479d 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -1,18 +1,1641 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_software_keyboard.h" #include "yuzu/applets/software_keyboard.h" #include "yuzu/main.h" +#include "yuzu/util/overlay_dialog.h" -QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator() {} +namespace { -QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {} +using namespace Service::AM::Applets; -QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(QWidget* parent) : QDialog(parent) {} +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; -QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; +} // Anonymous namespace -QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {} +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_) + : QDialog(parent), ui{std::make_unique()}, system{system_}, + is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); + + keyboard_buttons = {{ + {{ + { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + }, + { + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + }, + { + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_return, + }, + { + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + }, + { + ui->button_shift, + ui->button_shift, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_ok, + }, + }}, + {{ + { + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + }, + { + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + }, + { + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_return_shift, + }, + { + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + }, + { + ui->button_shift_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_ok_shift, + }, + }}, + }}; + + numberpad_buttons = {{ + { + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + }, + { + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + }, + { + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_ok_num, + }, + { + nullptr, + ui->button_0_num, + nullptr, + ui->button_ok_num, + }, + }}; + + all_buttons = { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + ui->button_shift, + ui->button_space, + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_0_num, + }; + + SetupMouseHover(); + + if (!initialize_parameters.ok_text.empty()) { + ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); + } + + ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); + ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + + current_text = initialize_parameters.initial_text; + cursor_position = initialize_parameters.initial_cursor_position; + + SetTextDrawType(); + + for (auto* button : all_buttons) { + connect(button, &QPushButton::clicked, this, [this, button](bool) { + if (is_inline) { + InlineKeyboardButtonClicked(button); + } else { + NormalKeyboardButtonClicked(button); + } + }); + } + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + } +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { + StopInputThread(); +} + +void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { + if (isVisible()) { + return; + } + + MoveAndResizeWindow(pos, size); + + SetKeyboardType(); + SetPasswordMode(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + case SwkbdTextCheckResult::Silent: + default: + break; + case SwkbdTextCheckResult::Failure: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + QString{}, tr("OK"), Qt::AlignCenter); + dialog.exec(); + + StartInputThread(); + break; + } + case SwkbdTextCheckResult::Confirm: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + tr("Cancel"), tr("OK"), Qt::AlignCenter); + if (dialog.exec() == QDialog::Accepted) { + emit SubmitNormalText(SwkbdResult::Ok, current_text); + break; + } + + StartInputThread(); + break; + } + } +} + +void QtSoftwareKeyboardDialog::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { + MoveAndResizeWindow(pos, size); + + ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); + + ui->headerOSK->hide(); + ui->subOSK->hide(); + ui->inputOSK->hide(); + ui->charactersOSK->hide(); + ui->inputBoxOSK->hide(); + ui->charactersBoxOSK->hide(); + + initialize_parameters.max_text_length = appear_parameters.max_text_length; + initialize_parameters.min_text_length = appear_parameters.min_text_length; + initialize_parameters.type = appear_parameters.type; + initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; + initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; + initialize_parameters.enable_return_button = appear_parameters.enable_return_button; + initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + + SetKeyboardType(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::HideInlineKeyboard() { + StopInputThread(); + QDialog::hide(); +} + +void QtSoftwareKeyboardDialog::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + current_text = text_parameters.input_text; + cursor_position = text_parameters.cursor_position; + + SetBackspaceOkEnabled(); +} + +void QtSoftwareKeyboardDialog::ExitKeyboard() { + StopInputThread(); + QDialog::done(QDialog::Accepted); +} + +void QtSoftwareKeyboardDialog::open() { + QDialog::open(); + + row = 0; + column = 0; + + const auto* const curr_button = + keyboard_buttons[static_cast(bottom_osk_index)][row][column]; + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + + StartInputThread(); +} + +void QtSoftwareKeyboardDialog::reject() { + // Pressing the ESC key in a dialog calls QDialog::reject(). + // We will override this behavior to the "Cancel" action on the software keyboard. + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, current_text); + } +} + +void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { + if (!is_inline) { + QDialog::keyPressEvent(event); + return; + } + + const auto entered_key = event->key(); + + switch (entered_key) { + case Qt::Key_Escape: + QDialog::keyPressEvent(event); + return; + case Qt::Key_Backspace: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Return: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Left: + MoveTextCursorDirection(Direction::Left); + return; + case Qt::Key_Right: + MoveTextCursorDirection(Direction::Right); + return; + default: + break; + } + + const auto entered_text = event->text(); + + if (entered_text.isEmpty()) { + return; + } + + InlineTextInsertString(entered_text.toStdU16String()); +} + +void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { + QDialog::move(pos); + QDialog::resize(size); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + RescaleKeyboardElements(size.width(), size.height(), dpi_scale); +} + +void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { + const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto char_button_font_size = + BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto label_button_font_size = + BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); + QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); + QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); + QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); + QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, + QFont::Normal); + + ui->label_header->setFont(header_font); + ui->label_sub->setFont(sub_font); + ui->line_edit_osk->setFont(editor_font); + ui->text_edit_osk->setFont(editor_font); + ui->label_characters->setFont(sub_font); + ui->label_characters_box->setFont(sub_font); + + ui->label_shift->setFont(label_button_font); + ui->label_shift_shift->setFont(label_button_font); + ui->label_cancel->setFont(label_button_font); + ui->label_cancel_shift->setFont(label_button_font); + ui->label_cancel_num->setFont(label_button_font); + ui->label_enter->setFont(label_button_font); + ui->label_enter_shift->setFont(label_button_font); + ui->label_enter_num->setFont(label_button_font); + + for (auto* button : all_buttons) { + if (button == ui->button_return || button == ui->button_return_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || + button == ui->button_ok_num) { + button->setFont(label_button_font); + continue; + } + + button->setFont(char_button_font); + } +} + +void QtSoftwareKeyboardDialog::SetKeyboardType() { + switch (initialize_parameters.type) { + case SwkbdType::Normal: + case SwkbdType::Qwerty: + case SwkbdType::Unknown3: + case SwkbdType::Latin: + case SwkbdType::SimplifiedChinese: + case SwkbdType::TraditionalChinese: + case SwkbdType::Korean: + default: { + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 320); + ui->verticalLayout_2->setStretch(1, 400); + + ui->gridLineOSK->setRowStretch(5, 94); + ui->gridBoxOSK->setRowStretch(2, 81); + break; + } + case SwkbdType::NumberPad: { + bottom_osk_index = BottomOSKIndex::NumberPad; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 370); + ui->verticalLayout_2->setStretch(1, 350); + + ui->gridLineOSK->setRowStretch(5, 144); + ui->gridBoxOSK->setRowStretch(2, 131); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetPasswordMode() { + switch (initialize_parameters.password_mode) { + case SwkbdPasswordMode::Disabled: + default: + ui->line_edit_osk->setEchoMode(QLineEdit::Normal); + break; + case SwkbdPasswordMode::Enabled: + ui->line_edit_osk->setEchoMode(QLineEdit::Password); + break; + } +} + +void QtSoftwareKeyboardDialog::SetTextDrawType() { + switch (initialize_parameters.text_draw_type) { + case SwkbdTextDrawType::Line: + case SwkbdTextDrawType::DownloadCode: { + ui->topOSK->setCurrentIndex(0); + + if (initialize_parameters.max_text_length <= 10) { + ui->gridLineOSK->setColumnStretch(0, 390); + ui->gridLineOSK->setColumnStretch(1, 500); + ui->gridLineOSK->setColumnStretch(2, 390); + } else { + ui->gridLineOSK->setColumnStretch(0, 130); + ui->gridLineOSK->setColumnStretch(1, 1020); + ui->gridLineOSK->setColumnStretch(2, 130); + } + + if (is_inline) { + return; + } + + connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { + const auto is_valid = ValidateInputText(changed_string); + + const auto text_length = static_cast(changed_string.length()); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, + [this](int old_cursor_position, int new_cursor_position) { + ui->button_backspace->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_shift->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_num->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::returnPressed, [this] { + 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; + } + }); + + ui->line_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); + ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + case SwkbdTextDrawType::Box: + default: { + ui->topOSK->setCurrentIndex(1); + + if (is_inline) { + return; + } + + connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { + if (static_cast(ui->text_edit_osk->toPlainText().length()) > + initialize_parameters.max_text_length) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } + + const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); + + const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->text_edit_osk->setFocus(); + }); + + connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { + const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + + ui->text_edit_osk->setFocus(); + }); + + ui->text_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 + ? QTextCursor::Start + : QTextCursor::End); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetControllerImage() { + const auto controller_type = Settings::values.players.GetValue()[8].connected + ? Settings::values.players.GetValue()[8].controller_type + : Settings::values.players.GetValue()[0].controller_type; + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_dark"); + } else { + return QString{}; + } + }(); + + switch (controller_type) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + break; + case Settings::ControllerType::DualJoyconDetached: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + break; + case Settings::ControllerType::LeftJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::RightJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::Handheld: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + default: { + for (const auto& keys : keyboard_buttons) { + for (const auto& rows : keys) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + } + + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + ui->button_space->setDisabled(key_disable_flags.space); + ui->button_space_shift->setDisabled(key_disable_flags.space); + + ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); + + ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); + + ui->button_slash->setDisabled(key_disable_flags.slash); + + ui->button_1->setDisabled(key_disable_flags.numbers); + ui->button_2->setDisabled(key_disable_flags.numbers); + ui->button_3->setDisabled(key_disable_flags.numbers); + ui->button_4->setDisabled(key_disable_flags.numbers); + ui->button_5->setDisabled(key_disable_flags.numbers); + ui->button_6->setDisabled(key_disable_flags.numbers); + ui->button_7->setDisabled(key_disable_flags.numbers); + ui->button_8->setDisabled(key_disable_flags.numbers); + ui->button_9->setDisabled(key_disable_flags.numbers); + ui->button_0->setDisabled(key_disable_flags.numbers); + + ui->button_return->setEnabled(initialize_parameters.enable_return_button); + ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); + break; + } + case BottomOSKIndex::NumberPad: { + for (const auto& rows : numberpad_buttons) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { + if (is_inline) { + ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(current_text.size() >= + initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + } else { + const auto text_length = [this] { + if (ui->topOSK->currentIndex() == 1) { + return static_cast(ui->text_edit_osk->toPlainText().length()); + } else { + return static_cast(ui->line_edit_osk->text().length()); + } + }(); + + const auto normal_cursor_position = [this] { + if (ui->topOSK->currentIndex() == 1) { + return ui->text_edit_osk->textCursor().position(); + } else { + return ui->line_edit_osk->cursorPosition(); + } + }(); + + ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + } +} + +bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + const auto input_text_length = static_cast(input_text.length()); + + if (input_text_length < initialize_parameters.min_text_length || + input_text_length > initialize_parameters.max_text_length) { + return false; + } + + if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { + return false; + } + + if ((key_disable_flags.at || key_disable_flags.username) && + input_text.contains(QLatin1Char{'@'})) { + return false; + } + + if ((key_disable_flags.percent || key_disable_flags.username) && + input_text.contains(QLatin1Char{'%'})) { + return false; + } + + if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { + return false; + } + + if ((key_disable_flags.backslash || key_disable_flags.username) && + input_text.contains(QLatin1Char('\\'))) { + return false; + } + + if (key_disable_flags.numbers && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { + return false; + } + + if (bottom_osk_index == BottomOSKIndex::NumberPad && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + return false; + } + + return true; +} + +void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + bottom_osk_index = BottomOSKIndex::UpperCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + 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("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); + + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + } else { + caps_lock_enabled = true; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_on.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + 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) { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Ok, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Ok, ui->line_edit_osk->text().toStdU16String()); + } + return; + } + + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(button->text()); + } else { + ui->line_edit_osk->insert(button->text()); + } + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { + if (!button->isEnabled()) { + return; + } + + if (button == ui->button_ampersand) { + InlineTextInsertString(u"&"); + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + InlineTextInsertString(u"\n"); + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + InlineTextInsertString(u" "); + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (cursor_position <= 0 || current_text.empty()) { + cursor_position = 0; + return; + } + + --cursor_position; + + current_text.erase(cursor_position, 1); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); + return; + } + + InlineTextInsertString(button->text().toStdU16String()); + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { + if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { + return; + } + + current_text.insert(cursor_position, string); + + cursor_position += static_cast(string.size()); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); +} + +void QtSoftwareKeyboardDialog::SetupMouseHover() { + // setFocus() has a bug where continuously changing focus will cause the focus UI to + // mysteriously disappear. A workaround we have found is using the mouse to hover over + // the buttons to act in place of the button focus. As a result, we will have to set + // a blank cursor when hovering over all the buttons and set a no focus policy so the + // buttons do not stay in focus in addition to the mouse hover. + for (auto* button : all_buttons) { + button->setCursor(QCursor(Qt::BlankCursor)); + button->setFocusPolicy(Qt::NoFocus); + } +} + +template +void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +template +void QtSoftwareKeyboardDialog::HandleButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { + switch (button) { + case HIDButton::A: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); + break; + case BottomOSKIndex::NumberPad: + numberpad_buttons[row][column]->click(); + break; + default: + break; + } + break; + case HIDButton::B: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + break; + case HIDButton::X: + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->line_edit_osk->text().toStdU16String()); + } + } + break; + case HIDButton::Y: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_space->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_space_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::LStick: + case HIDButton::RStick: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_shift->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_shift_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::L: + MoveTextCursorDirection(Direction::Left); + break; + case HIDButton::R: + MoveTextCursorDirection(Direction::Right); + break; + case HIDButton::Plus: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + case HIDButton::RStickLeft: + MoveButtonDirection(Direction::Left); + break; + case HIDButton::DUp: + case HIDButton::LStickUp: + case HIDButton::RStickUp: + MoveButtonDirection(Direction::Up); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + case HIDButton::RStickRight: + MoveButtonDirection(Direction::Right); + break; + case HIDButton::DDown: + case HIDButton::LStickDown: + case HIDButton::RStickDown: + MoveButtonDirection(Direction::Down); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { + // Changes the row or column index depending on the direction. + auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { + switch (direction) { + case Direction::Left: + column = (column + max_columns - 1) % max_columns; + break; + case Direction::Up: + row = (row + max_rows - 1) % max_rows; + break; + case Direction::Right: + column = (column + 1) % max_columns; + break; + case Direction::Down: + row = (row + 1) % max_rows; + break; + default: + break; + } + }; + + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: { + const auto index = static_cast(bottom_osk_index); + + const auto* const prev_button = keyboard_buttons[index][row][column]; + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + auto* curr_button = keyboard_buttons[index][row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + curr_button = keyboard_buttons[index][row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + case BottomOSKIndex::NumberPad: { + const auto* const prev_button = numberpad_buttons[row][column]; + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + auto* curr_button = numberpad_buttons[row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + curr_button = numberpad_buttons[row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { + switch (direction) { + case Direction::Left: + if (is_inline) { + if (cursor_position <= 0) { + cursor_position = 0; + } else { + --cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Left); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); + } + } + break; + case Direction::Right: + if (is_inline) { + if (cursor_position >= static_cast(current_text.size())) { + cursor_position = static_cast(current_text.size()); + } else { + ++cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Right); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); + } + } + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); +} + +void QtSoftwareKeyboardDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } + + if (input_interpreter) { + input_interpreter->ResetButtonStates(); + } +} + +void QtSoftwareKeyboardDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + HandleButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, + &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, + &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, + &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); +} QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + submit_inline_callback = std::move(submit_inline_callback_); + } else { + submit_normal_callback = std::move(submit_normal_callback_); + } + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(initialize_parameters.ok_text), + Common::UTF16ToUTF8(initialize_parameters.header_text), + Common::UTF16ToUTF8(initialize_parameters.sub_text), + Common::UTF16ToUTF8(initialize_parameters.guide_text), + Common::UTF16ToUTF8(initialize_parameters.initial_text), + initialize_parameters.max_text_length, initialize_parameters.min_text_length, + initialize_parameters.initial_cursor_position, initialize_parameters.type, + initialize_parameters.password_mode, initialize_parameters.text_draw_type, + initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, + initialize_parameters.enable_backspace_button, + initialize_parameters.enable_return_button, + initialize_parameters.disable_cancel_button); + + emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); +} + +void QtSoftwareKeyboard::ShowNormalKeyboard() const { + emit MainWindowShowNormalKeyboard(); +} + +void QtSoftwareKeyboard::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + emit MainWindowShowTextCheckDialog(text_check_result, text_check_message); +} + +void QtSoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); +} + +void QtSoftwareKeyboard::HideInlineKeyboard() const { + emit MainWindowHideInlineKeyboard(); +} + +void QtSoftwareKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + emit MainWindowInlineTextChanged(std::move(text_parameters)); +} + +void QtSoftwareKeyboard::ExitKeyboard() const { + emit MainWindowExitKeyboard(); +} + +void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const { + submit_normal_callback(result, submitted_text); +} + +void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, + s32 cursor_position) const { + submit_inline_callback(reply_type, submitted_text, cursor_position); +} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 8427c0a6ca..1a03c098ce 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -1,28 +1,228 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include +#include +#include +#include + #include #include #include "core/frontend/applets/software_keyboard.h" -class GMainWindow; +enum class HIDButton : u8; -class QtSoftwareKeyboardValidator final : public QValidator { -public: - explicit QtSoftwareKeyboardValidator(); - State validate(QString& input, int& pos) const override; -}; +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class QtSoftwareKeyboardDialog; +} + +class GMainWindow; class QtSoftwareKeyboardDialog final : public QDialog { Q_OBJECT public: - QtSoftwareKeyboardDialog(QWidget* parent); + QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_); ~QtSoftwareKeyboardDialog() override; + + void ShowNormalKeyboard(QPoint pos, QSize size); + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, + QSize size); + + void HideInlineKeyboard(); + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + + void ExitKeyboard(); + +signals: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + +public slots: + void open() override; + void reject() override; + +protected: + /// We override the keyPressEvent for inputting text into the inline software keyboard. + void keyPressEvent(QKeyEvent* event) override; + +private: + enum class Direction { + Left, + Up, + Right, + Down, + }; + + enum class BottomOSKIndex { + LowerCase, + UpperCase, + NumberPad, + }; + + /** + * Moves and resizes the window to a specified position and size. + * + * @param pos Top-left window position + * @param size Window size + */ + void MoveAndResizeWindow(QPoint pos, QSize size); + + /** + * Rescales all keyboard elements to account for High DPI displays. + * + * @param width Window width + * @param height Window height + * @param dpi_scale Display scaling factor + */ + void RescaleKeyboardElements(float width, float height, float dpi_scale); + + /// Sets the keyboard type based on initialize_parameters. + void SetKeyboardType(); + + /// Sets the password mode based on initialize_parameters. + void SetPasswordMode(); + + /// Sets the text draw type based on initialize_parameters. + void SetTextDrawType(); + + /// Sets the controller image at the bottom left of the software keyboard. + void SetControllerImage(); + + /// Disables buttons based on initialize_parameters. + void DisableKeyboardButtons(); + + /// Changes whether the backspace or/and ok buttons should be enabled or disabled. + void SetBackspaceOkEnabled(); + + /** + * Validates the input text sent in based on the parameters in initialize_parameters. + * + * @param input_text Input text + * + * @returns True if the input text is valid, false otherwise. + */ + bool ValidateInputText(const QString& input_text); + + /// Switches between LowerCase and UpperCase (Shift and Caps Lock) + void ChangeBottomOSKIndex(); + + /// Processes a keyboard button click from the UI as normal keyboard input. + void NormalKeyboardButtonClicked(QPushButton* button); + + /// Processes a keyboard button click from the UI as inline keyboard input. + void InlineKeyboardButtonClicked(QPushButton* button); + + /** + * Inserts a string of arbitrary length into the current_text at the current cursor position. + * This is only used for the inline software keyboard. + */ + void InlineTextInsertString(std::u16string_view string); + + /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. + void SetupMouseHover(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonHold(); + + /** + * Translates a button press to focus or click a keyboard button. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + /** + * Moves the focus of a button in a certain direction. + * + * @param direction The direction to move. + */ + void MoveButtonDirection(Direction direction); + + /** + * Moves the text cursor in a certain direction. + * + * @param direction The direction to move. + */ + void MoveTextCursorDirection(Direction direction); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + Core::System& system; + + // True if it is the inline software keyboard. + bool is_inline; + + // Common software keyboard initialize parameters. + Core::Frontend::KeyboardInitializeParameters initialize_parameters; + + // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. + std::u16string current_text; + s32 cursor_position{0}; + + static constexpr std::size_t NUM_ROWS_NORMAL = 5; + static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; + static constexpr std::size_t NUM_ROWS_NUMPAD = 4; + static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; + + // Stores the normal keyboard layout. + std::array, NUM_ROWS_NORMAL>, 2> + keyboard_buttons; + // Stores the numberpad keyboard layout. + std::array, NUM_ROWS_NUMPAD> numberpad_buttons; + + // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. + std::array all_buttons; + + std::size_t row{0}; + std::size_t column{0}; + + BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; + std::atomic caps_lock_enabled{false}; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; }; class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { @@ -31,4 +231,55 @@ class QtSoftwareKeyboard final : public QObject, public Core::Frontend::Software public: explicit QtSoftwareKeyboard(GMainWindow& parent); ~QtSoftwareKeyboard() override; + + void InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +signals: + void MainWindowInitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; + + void MainWindowShowNormalKeyboard() const; + + void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const; + + void MainWindowShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const; + + void MainWindowHideInlineKeyboard() const; + + void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; + + void MainWindowExitKeyboard() const; + +private: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; }; diff --git a/src/yuzu/applets/software_keyboard.ui b/src/yuzu/applets/software_keyboard.ui new file mode 100644 index 0000000000..b0a1fcde9d --- /dev/null +++ b/src/yuzu/applets/software_keyboard.ui @@ -0,0 +1,3503 @@ + + + QtSoftwareKeyboardDialog + + + + 0 + 0 + 1280 + 720 + + + + Software Keyboard + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 17 + + + + 0/32 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 26 + 50 + false + + + + Qt::StrongFocus + + + + + + 32 + + + Enter Text + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 23 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 17 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 17 + + + + 0/500 + + + + + + + + + + + 0 + + + 14 + + + 9 + + + 14 + + + 9 + + + + + + 26 + + + + Qt::StrongFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Shift + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + - + + + + + + + + 1 + 1 + + + + + 28 + + + + ' + + + + + + + + 1 + 1 + + + + + 28 + + + + / + + + + + + + + 1 + 1 + + + + + 28 + + + + ! + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + w + + + + + + + + 1 + 1 + + + + + 28 + + + + r + + + + + + + + 1 + 1 + + + + + 28 + + + + e + + + + + + + + 1 + 1 + + + + + 28 + + + + q + + + + + + + + 1 + 1 + + + + + 28 + + + + u + + + + + + + + 1 + 1 + + + + + 28 + + + + y + + + + + + + + 1 + 1 + + + + + 28 + + + + t + + + + + + + + 1 + 1 + + + + + 28 + + + + o + + + + + + + + 1 + 1 + + + + + 28 + + + + p + + + + + + + + 1 + 1 + + + + + 28 + + + + i + + + + + + + + 1 + 1 + + + + + 28 + + + + a + + + + + + + + 1 + 1 + + + + + 28 + + + + s + + + + + + + + 1 + 1 + + + + + 28 + + + + d + + + + + + + + 1 + 1 + + + + + 28 + + + + f + + + + + + + + 1 + 1 + + + + + 28 + + + + h + + + + + + + + 1 + 1 + + + + + 28 + + + + j + + + + + + + + 1 + 1 + + + + + 28 + + + + g + + + + + + + + 1 + 1 + + + + + 28 + + + + k + + + + + + + + 1 + 1 + + + + + 28 + + + + l + + + + + + + + 1 + 1 + + + + + 28 + + + + : + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + z + + + + + + + + 1 + 1 + + + + + 28 + + + + c + + + + + + + + 1 + 1 + + + + + 28 + + + + x + + + + + + + + 1 + 1 + + + + + 28 + + + + v + + + + + + + + 1 + 1 + + + + + 28 + + + + m + + + + + + + + 1 + 1 + + + + + 28 + + + + , + + + + + + + + 1 + 1 + + + + + 28 + + + + n + + + + + + + + 1 + 1 + + + + + 28 + + + + b + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + ? + + + + + + + + 1 + 1 + + + + + 28 + + + + . + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Caps Lock + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + _ + + + + + + + + 1 + 1 + + + + + 28 + + + + " + + + + + + + + 1 + 1 + + + + + 28 + + + + @ + + + + + + + + 1 + 1 + + + + + 28 + + + + = + + + + + + + + 1 + 1 + + + + + 28 + + + + && + + + + + + + + 1 + 1 + + + + + 28 + + + + * + + + + + + + + 1 + 1 + + + + + 28 + + + + ) + + + + + + + + 1 + 1 + + + + + 28 + + + + ( + + + + + + + + 1 + 1 + + + + + 28 + + + + W + + + + + + + + 1 + 1 + + + + + 28 + + + + R + + + + + + + + 1 + 1 + + + + + 28 + + + + E + + + + + + + + 1 + 1 + + + + + 28 + + + + Q + + + + + + + + 1 + 1 + + + + + 28 + + + + U + + + + + + + + 1 + 1 + + + + + 28 + + + + Y + + + + + + + + 1 + 1 + + + + + 28 + + + + T + + + + + + + + 1 + 1 + + + + + 28 + + + + O + + + + + + + + 1 + 1 + + + + + 28 + + + + P + + + + + + + + 1 + 1 + + + + + 28 + + + + I + + + + + + + + 1 + 1 + + + + + 28 + + + + A + + + + + + + + 1 + 1 + + + + + 28 + + + + S + + + + + + + + 1 + 1 + + + + + 28 + + + + D + + + + + + + + 1 + 1 + + + + + 28 + + + + F + + + + + + + + 1 + 1 + + + + + 28 + + + + H + + + + + + + + 1 + 1 + + + + + 28 + + + + J + + + + + + + + 1 + 1 + + + + + 28 + + + + G + + + + + + + + 1 + 1 + + + + + 28 + + + + K + + + + + + + + 1 + 1 + + + + + 28 + + + + L + + + + + + + + 1 + 1 + + + + + 28 + + + + ; + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + Z + + + + + + + + 1 + 1 + + + + + 28 + + + + C + + + + + + + + 1 + 1 + + + + + 28 + + + + X + + + + + + + + 1 + 1 + + + + + 28 + + + + V + + + + + + + + 1 + 1 + + + + + 28 + + + + M + + + + + + + + 1 + 1 + + + + + 28 + + + + < + + + + + + + + 1 + 1 + + + + + 28 + + + + N + + + + + + + + 1 + 1 + + + + + 28 + + + + B + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + > + + + + + + + + 1 + 1 + + + + + 28 + + + + # + + + + + + + + 1 + 1 + + + + + 28 + + + + ] + + + + + + + + 1 + 1 + + + + + 28 + + + + $ + + + + + + + + 1 + 1 + + + + + 28 + + + + [ + + + + + + + + 1 + 1 + + + + + 28 + + + + ^ + + + + + + + + 1 + 1 + + + + + 28 + + + + % + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + button_1 + button_2 + button_3 + button_4 + button_5 + button_6 + button_7 + button_8 + button_9 + button_0 + button_minus + button_backspace + button_q + button_w + button_e + button_r + button_t + button_y + button_u + button_i + button_o + button_p + button_slash + button_return + button_a + button_s + button_d + button_f + button_g + button_h + button_j + button_k + button_l + button_colon + button_apostrophe + button_z + button_x + button_c + button_v + button_b + button_n + button_m + button_comma + button_dot + button_question + button_exclamation + button_ok + button_shift + button_space + button_hash + button_left_bracket + button_right_bracket + button_dollar + button_percent + button_circumflex + button_ampersand + button_asterisk + button_left_parenthesis + button_right_parenthesis + button_underscore + button_backspace_shift + button_q_shift + button_w_shift + button_e_shift + button_r_shift + button_t_shift + button_y_shift + button_u_shift + button_i_shift + button_o_shift + button_p_shift + button_at + button_return_shift + button_a_shift + button_s_shift + button_d_shift + button_f_shift + button_g_shift + button_h_shift + button_j_shift + button_k_shift + button_l_shift + button_semicolon + button_quotation + button_z_shift + button_x_shift + button_c_shift + button_v_shift + button_b_shift + button_n_shift + button_m_shift + button_less_than + button_greater_than + button_plus + button_equal + button_ok_shift + button_shift_shift + button_space_shift + button_1_num + button_2_num + button_3_num + button_backspace_num + button_4_num + button_5_num + button_6_num + button_ok_num + button_7_num + button_8_num + button_9_num + button_0_num + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ce83dee278..5f6cdc0c6f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -466,6 +466,114 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } +void GMainWindow::SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) { + if (software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is already initialized!"); + return; + } + + software_keyboard = new QtSoftwareKeyboardDialog(render_window, Core::System::GetInstance(), + is_inline, std::move(initialize_parameters)); + + if (is_inline) { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitInlineText, this, + [this](Service::AM::Applets::SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + emit SoftwareKeyboardSubmitInlineText(reply_type, submitted_text, cursor_position); + }, + Qt::QueuedConnection); + } else { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitNormalText, this, + [this](Service::AM::Applets::SwkbdResult result, std::u16string submitted_text) { + emit SoftwareKeyboardSubmitNormalText(result, submitted_text); + }, + Qt::QueuedConnection); + } +} + +void GMainWindow::SoftwareKeyboardShowNormal() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = layout.screen.left; + const auto y = layout.screen.top; + const auto w = layout.screen.GetWidth(); + const auto h = layout.screen.GetHeight(); + + software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardShowTextCheck( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->ShowTextCheckDialog(text_check_result, text_check_message); +} + +void GMainWindow::SoftwareKeyboardShowInline( + Core::Frontend::InlineAppearParameters appear_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = + static_cast(layout.screen.left + (0.5f * layout.screen.GetWidth() * + ((2.0f * appear_parameters.key_top_translate_x) + + (1.0f - appear_parameters.key_top_scale_x)))); + const auto y = + static_cast(layout.screen.top + (layout.screen.GetHeight() * + ((2.0f * appear_parameters.key_top_translate_y) + + (1.0f - appear_parameters.key_top_scale_y)))); + const auto w = static_cast(layout.screen.GetWidth() * appear_parameters.key_top_scale_x); + const auto h = static_cast(layout.screen.GetHeight() * appear_parameters.key_top_scale_y); + + software_keyboard->ShowInlineKeyboard(std::move(appear_parameters), + render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardHideInline() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->HideInlineKeyboard(); +} + +void GMainWindow::SoftwareKeyboardInlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->InlineTextChanged(std::move(text_parameters)); +} + +void GMainWindow::SoftwareKeyboardExit() { + if (!software_keyboard) { + return; + } + + software_keyboard->ExitKeyboard(); + + software_keyboard = nullptr; +} + void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE @@ -1009,6 +1117,10 @@ void GMainWindow::ConnectWidgetEvents() { connect(this, &GMainWindow::EmulationStopping, render_window, &GRenderWindow::OnEmulationStopping); + // Software Keyboard Applet + connect(this, &GMainWindow::EmulationStarting, this, &GMainWindow::SoftwareKeyboardExit); + connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit); + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4c8a879d2c..7f1e50a5b0 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -135,6 +135,11 @@ signals: void ProfileSelectorFinishedSelection(std::optional uuid); + void SoftwareKeyboardSubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text); + void SoftwareKeyboardSubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position); + void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); @@ -143,6 +148,15 @@ public slots: void OnExecuteProgram(std::size_t program_index); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); + void SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); + void SoftwareKeyboardShowNormal(); + void SoftwareKeyboardShowTextCheck(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + void SoftwareKeyboardShowInline(Core::Frontend::InlineAppearParameters appear_parameters); + void SoftwareKeyboardHideInline(); + void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + void SoftwareKeyboardExit(); void ErrorDisplayDisplayError(QString error_code, QString error_text); void ProfileSelectorSelectProfile(); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,