code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QObject>

#include "core/frontend/applets/error.h"

class GMainWindow;

class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet {
    Q_OBJECT

public:
    explicit QtErrorDisplay(GMainWindow& parent);
    ~QtErrorDisplay() override;

    void Close() const override;
    void ShowError(Result error, FinishedCallback finished) const override;
    void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
                                FinishedCallback finished) const override;
    void ShowCustomErrorText(Result error, std::string dialog_text, std::string fullscreen_text,
                             FinishedCallback finished) const override;

signals:
    void MainWindowDisplayError(QString error_code, QString error_text) const;
    void MainWindowRequestExit() const;

private:
    void MainWindowFinishedError();

    mutable FinishedCallback callback;
};


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <mutex>
#include <QApplication>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QTreeView>
#include <QVBoxLayout>
#include "common/fs/path_util.h"
#include "common/string_util.h"
#include "core/constants.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "yuzu/applets/qt_profile_select.h"
#include "yuzu/main.h"
#include "yuzu/util/controller_navigation.h"

namespace {
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
    return QtProfileSelectionDialog::tr(
               "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
                         "00112233-4455-6677-8899-AABBCCDDEEFF))")
        .arg(username, QString::fromStdString(uuid.FormattedString()));
}

QString GetImagePath(Common::UUID uuid) {
    const auto path =
        Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
        fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormattedString());
    return QString::fromStdString(Common::FS::PathToUTF8String(path));
}

QPixmap GetIcon(Common::UUID uuid) {
    QPixmap icon{GetImagePath(uuid)};

    if (!icon) {
        icon.fill(Qt::black);
        icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(),
                          static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
    }

    return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
} // Anonymous namespace

QtProfileSelectionDialog::QtProfileSelectionDialog(
    Core::System& system, QWidget* parent,
    const Core::Frontend::ProfileSelectParameters& parameters)
    : QDialog(parent), profile_manager{system.GetProfileManager()} {
    outer_layout = new QVBoxLayout;

    instruction_label = new QLabel();

    scroll_area = new QScrollArea;

    buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
    connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
    connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);

    outer_layout->addWidget(instruction_label);
    outer_layout->addWidget(scroll_area);
    outer_layout->addWidget(buttons);

    layout = new QVBoxLayout;
    tree_view = new QTreeView;
    item_model = new QStandardItemModel(tree_view);
    tree_view->setModel(item_model);
    controller_navigation = new ControllerNavigation(system.HIDCore(), this);

    tree_view->setAlternatingRowColors(true);
    tree_view->setSelectionMode(QHeaderView::SingleSelection);
    tree_view->setSelectionBehavior(QHeaderView::SelectRows);
    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
    tree_view->setSortingEnabled(true);
    tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
    tree_view->setUniformRowHeights(true);
    tree_view->setIconSize({64, 64});
    tree_view->setContextMenuPolicy(Qt::NoContextMenu);

    item_model->insertColumns(0, 1);
    item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));

    // We must register all custom types with the Qt Automoc system so that we are able to use it
    // with signals/slots. In this case, QList falls under the umbrella of custom types.
    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");

    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->addWidget(tree_view);

    scroll_area->setLayout(layout);

    connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
    connect(tree_view, &QTreeView::doubleClicked, this, &QtProfileSelectionDialog::accept);
    connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
            [this](Qt::Key key) {
                if (!this->isActiveWindow()) {
                    return;
                }
                QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
                QCoreApplication::postEvent(tree_view, event);
                SelectUser(tree_view->currentIndex());
            });

    const auto& profiles = profile_manager.GetAllUsers();
    for (const auto& user : profiles) {
        Service::Account::ProfileBase profile{};
        if (!profile_manager.GetProfileBase(user, profile))
            continue;

        const auto username = Common::StringFromFixedZeroTerminatedBuffer(
            reinterpret_cast<const char*>(profile.username.data()), profile.username.size());

        list_items.push_back(QList<QStandardItem*>{new QStandardItem{
            GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
    }

    for (const auto& item : list_items)
        item_model->appendRow(item);

    setLayout(outer_layout);
    SetWindowTitle(parameters);
    SetDialogPurpose(parameters);
    resize(550, 400);
}

QtProfileSelectionDialog::~QtProfileSelectionDialog() {
    controller_navigation->UnloadController();
};

int QtProfileSelectionDialog::exec() {
    // Skip profile selection when there's only one.
    if (profile_manager.GetUserCount() == 1) {
        user_index = 0;
        return QDialog::Accepted;
    }
    return QDialog::exec();
}

void QtProfileSelectionDialog::accept() {
    QDialog::accept();
}

void QtProfileSelectionDialog::reject() {
    user_index = 0;
    QDialog::reject();
}

int QtProfileSelectionDialog::GetIndex() const {
    return user_index;
}

void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) {
    user_index = index.row();
}

void QtProfileSelectionDialog::SetWindowTitle(
    const Core::Frontend::ProfileSelectParameters& parameters) {
    using Service::AM::Frontend::UiMode;
    switch (parameters.mode) {
    case UiMode::UserCreator:
    case UiMode::UserCreatorForStarter:
        setWindowTitle(tr("Profile Creator"));
        return;
    case UiMode::EnsureNetworkServiceAccountAvailable:
        setWindowTitle(tr("Profile Selector"));
        return;
    case UiMode::UserIconEditor:
        setWindowTitle(tr("Profile Icon Editor"));
        return;
    case UiMode::UserNicknameEditor:
        setWindowTitle(tr("Profile Nickname Editor"));
        return;
    case UiMode::NintendoAccountAuthorizationRequestContext:
    case UiMode::IntroduceExternalNetworkServiceAccount:
    case UiMode::IntroduceExternalNetworkServiceAccountForRegistration:
    case UiMode::NintendoAccountNnidLinker:
    case UiMode::LicenseRequirementsForNetworkService:
    case UiMode::LicenseRequirementsForNetworkServiceWithUserContextImpl:
    case UiMode::UserCreatorForImmediateNaLoginTest:
    case UiMode::UserQualificationPromoter:
    case UiMode::UserSelector:
    default:
        setWindowTitle(tr("Profile Selector"));
    }
}

void QtProfileSelectionDialog::SetDialogPurpose(
    const Core::Frontend::ProfileSelectParameters& parameters) {
    using Service::AM::Frontend::UserSelectionPurpose;

    switch (parameters.purpose) {
    case UserSelectionPurpose::GameCardRegistration:
        instruction_label->setText(tr("Who will receive the points?"));
        return;
    case UserSelectionPurpose::EShopLaunch:
        instruction_label->setText(tr("Who is using Nintendo eShop?"));
        return;
    case UserSelectionPurpose::EShopItemShow:
        instruction_label->setText(tr("Who is making this purchase?"));
        return;
    case UserSelectionPurpose::PicturePost:
        instruction_label->setText(tr("Who is posting?"));
        return;
    case UserSelectionPurpose::NintendoAccountLinkage:
        instruction_label->setText(tr("Select a user to link to a Nintendo Account."));
        return;
    case UserSelectionPurpose::SettingsUpdate:
        instruction_label->setText(tr("Change settings for which user?"));
        return;
    case UserSelectionPurpose::SaveDataDeletion:
        instruction_label->setText(tr("Format data for which user?"));
        return;
    case UserSelectionPurpose::UserMigration:
        instruction_label->setText(tr("Which user will be transferred to another console?"));
        return;
    case UserSelectionPurpose::SaveDataTransfer:
        instruction_label->setText(tr("Send save data for which user?"));
        return;
    case UserSelectionPurpose::General:
    default:
        instruction_label->setText(tr("Select a user:"));
        return;
    }
}

QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
    connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
            &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
    connect(this, &QtProfileSelector::MainWindowRequestExit, &parent,
            &GMainWindow::ProfileSelectorRequestExit, Qt::QueuedConnection);
    connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
            &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
}

QtProfileSelector::~QtProfileSelector() = default;

void QtProfileSelector::Close() const {
    callback = {};
    emit MainWindowRequestExit();
}

void QtProfileSelector::SelectProfile(
    SelectProfileCallback callback_,
    const Core::Frontend::ProfileSelectParameters& parameters) const {
    callback = std::move(callback_);
    emit MainWindowSelectProfile(parameters);
}

void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
    if (callback) {
        callback(uuid);
    }
}

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <vector>
#include <QDialog>
#include <QList>
#include "core/frontend/applets/profile_select.h"

class ControllerNavigation;
class GMainWindow;
class QDialogButtonBox;
class QGraphicsScene;
class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;

namespace Core {
class System;
}

namespace Service::Account {
class ProfileManager;
}

class QtProfileSelectionDialog final : public QDialog {
    Q_OBJECT

public:
    explicit QtProfileSelectionDialog(Core::System& system, QWidget* parent,
                                      const Core::Frontend::ProfileSelectParameters& parameters);
    ~QtProfileSelectionDialog() override;

    int exec() override;
    void accept() override;
    void reject() override;

    int GetIndex() const;

private:
    void SelectUser(const QModelIndex& index);

    void SetWindowTitle(const Core::Frontend::ProfileSelectParameters& parameters);
    void SetDialogPurpose(const Core::Frontend::ProfileSelectParameters& parameters);

    int user_index = 0;

    QVBoxLayout* layout;
    QTreeView* tree_view;
    QStandardItemModel* item_model;
    QGraphicsScene* scene;

    std::vector<QList<QStandardItem*>> list_items;

    QVBoxLayout* outer_layout;
    QLabel* instruction_label;
    QScrollArea* scroll_area;
    QDialogButtonBox* buttons;

    Service::Account::ProfileManager& profile_manager;
    ControllerNavigation* controller_navigation = nullptr;
};

class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
    Q_OBJECT

public:
    explicit QtProfileSelector(GMainWindow& parent);
    ~QtProfileSelector() override;

    void Close() const override;
    void SelectProfile(SelectProfileCallback callback_,
                       const Core::Frontend::ProfileSelectParameters& parameters) const override;

signals:
    void MainWindowSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters) const;
    void MainWindowRequestExit() const;

private:
    void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);

    mutable SelectProfileCallback callback;
};


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <QCursor>
#include <QKeyEvent>
#include <QScreen>

#include "common/logging/log.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/frontend/input_interpreter.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "ui_qt_software_keyboard.h"
#include "yuzu/applets/qt_software_keyboard.h"
#include "yuzu/main.h"
#include "yuzu/util/overlay_dialog.h"

namespace {

using namespace Service::AM::Frontend;

constexpr float BASE_HEADER_FONT_SIZE = 23.0f;
constexpr float BASE_SUB_FONT_SIZE = 17.0f;
constexpr float BASE_EDITOR_FONT_SIZE = 26.0f;
constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f;
constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f;
constexpr float BASE_ICON_BUTTON_SIZE = 36.0f;
[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f;
constexpr float BASE_HEIGHT = 720.0f;

} // Anonymous namespace

QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
    QWidget* parent, Core::System& system_, bool is_inline_,
    Core::Frontend::KeyboardInitializeParameters initialize_parameters_)
    : QDialog(parent), ui{std::make_unique<Ui::QtSoftwareKeyboardDialog>()}, system{system_},
      is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} {
    ui->setupUi(this);

    setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint |
                   Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint);
    setWindowModality(Qt::WindowModal);
    setAttribute(Qt::WA_DeleteOnClose);
    setAttribute(Qt::WA_TranslucentBackground);

    keyboard_buttons = {{
        {{
            {
                ui->button_1,
                ui->button_2,
                ui->button_3,
                ui->button_4,
                ui->button_5,
                ui->button_6,
                ui->button_7,
                ui->button_8,
                ui->button_9,
                ui->button_0,
                ui->button_minus,
                ui->button_backspace,
            },
            {
                ui->button_q,
                ui->button_w,
                ui->button_e,
                ui->button_r,
                ui->button_t,
                ui->button_y,
                ui->button_u,
                ui->button_i,
                ui->button_o,
                ui->button_p,
                ui->button_slash,
                ui->button_return,
            },
            {
                ui->button_a,
                ui->button_s,
                ui->button_d,
                ui->button_f,
                ui->button_g,
                ui->button_h,
                ui->button_j,
                ui->button_k,
                ui->button_l,
                ui->button_colon,
                ui->button_apostrophe,
                ui->button_return,
            },
            {
                ui->button_z,
                ui->button_x,
                ui->button_c,
                ui->button_v,
                ui->button_b,
                ui->button_n,
                ui->button_m,
                ui->button_comma,
                ui->button_dot,
                ui->button_question,
                ui->button_exclamation,
                ui->button_ok,
            },
            {
                ui->button_shift,
                ui->button_shift,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_space,
                ui->button_ok,
            },
        }},
        {{
            {
                ui->button_hash,
                ui->button_left_bracket,
                ui->button_right_bracket,
                ui->button_dollar,
                ui->button_percent,
                ui->button_circumflex,
                ui->button_ampersand,
                ui->button_asterisk,
                ui->button_left_parenthesis,
                ui->button_right_parenthesis,
                ui->button_underscore,
                ui->button_backspace_shift,
            },
            {
                ui->button_q_shift,
                ui->button_w_shift,
                ui->button_e_shift,
                ui->button_r_shift,
                ui->button_t_shift,
                ui->button_y_shift,
                ui->button_u_shift,
                ui->button_i_shift,
                ui->button_o_shift,
                ui->button_p_shift,
                ui->button_at,
                ui->button_return_shift,
            },
            {
                ui->button_a_shift,
                ui->button_s_shift,
                ui->button_d_shift,
                ui->button_f_shift,
                ui->button_g_shift,
                ui->button_h_shift,
                ui->button_j_shift,
                ui->button_k_shift,
                ui->button_l_shift,
                ui->button_semicolon,
                ui->button_quotation,
                ui->button_return_shift,
            },
            {
                ui->button_z_shift,
                ui->button_x_shift,
                ui->button_c_shift,
                ui->button_v_shift,
                ui->button_b_shift,
                ui->button_n_shift,
                ui->button_m_shift,
                ui->button_less_than,
                ui->button_greater_than,
                ui->button_plus,
                ui->button_equal,
                ui->button_ok_shift,
            },
            {
                ui->button_shift_shift,
                ui->button_shift_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_space_shift,
                ui->button_ok_shift,
            },
        }},
    }};

    numberpad_buttons = {{
        {
            ui->button_1_num,
            ui->button_2_num,
            ui->button_3_num,
            ui->button_backspace_num,
        },
        {
            ui->button_4_num,
            ui->button_5_num,
            ui->button_6_num,
            ui->button_ok_num,
        },
        {
            ui->button_7_num,
            ui->button_8_num,
            ui->button_9_num,
            ui->button_ok_num,
        },
        {
            ui->button_left_optional_num,
            ui->button_0_num,
            ui->button_right_optional_num,
            ui->button_ok_num,
        },
    }};

    all_buttons = {
        ui->button_1,
        ui->button_2,
        ui->button_3,
        ui->button_4,
        ui->button_5,
        ui->button_6,
        ui->button_7,
        ui->button_8,
        ui->button_9,
        ui->button_0,
        ui->button_minus,
        ui->button_backspace,
        ui->button_q,
        ui->button_w,
        ui->button_e,
        ui->button_r,
        ui->button_t,
        ui->button_y,
        ui->button_u,
        ui->button_i,
        ui->button_o,
        ui->button_p,
        ui->button_slash,
        ui->button_return,
        ui->button_a,
        ui->button_s,
        ui->button_d,
        ui->button_f,
        ui->button_g,
        ui->button_h,
        ui->button_j,
        ui->button_k,
        ui->button_l,
        ui->button_colon,
        ui->button_apostrophe,
        ui->button_z,
        ui->button_x,
        ui->button_c,
        ui->button_v,
        ui->button_b,
        ui->button_n,
        ui->button_m,
        ui->button_comma,
        ui->button_dot,
        ui->button_question,
        ui->button_exclamation,
        ui->button_ok,
        ui->button_shift,
        ui->button_space,
        ui->button_hash,
        ui->button_left_bracket,
        ui->button_right_bracket,
        ui->button_dollar,
        ui->button_percent,
        ui->button_circumflex,
        ui->button_ampersand,
        ui->button_asterisk,
        ui->button_left_parenthesis,
        ui->button_right_parenthesis,
        ui->button_underscore,
        ui->button_backspace_shift,
        ui->button_q_shift,
        ui->button_w_shift,
        ui->button_e_shift,
        ui->button_r_shift,
        ui->button_t_shift,
        ui->button_y_shift,
        ui->button_u_shift,
        ui->button_i_shift,
        ui->button_o_shift,
        ui->button_p_shift,
        ui->button_at,
        ui->button_return_shift,
        ui->button_a_shift,
        ui->button_s_shift,
        ui->button_d_shift,
        ui->button_f_shift,
        ui->button_g_shift,
        ui->button_h_shift,
        ui->button_j_shift,
        ui->button_k_shift,
        ui->button_l_shift,
        ui->button_semicolon,
        ui->button_quotation,
        ui->button_z_shift,
        ui->button_x_shift,
        ui->button_c_shift,
        ui->button_v_shift,
        ui->button_b_shift,
        ui->button_n_shift,
        ui->button_m_shift,
        ui->button_less_than,
        ui->button_greater_than,
        ui->button_plus,
        ui->button_equal,
        ui->button_ok_shift,
        ui->button_shift_shift,
        ui->button_space_shift,
        ui->button_1_num,
        ui->button_2_num,
        ui->button_3_num,
        ui->button_backspace_num,
        ui->button_4_num,
        ui->button_5_num,
        ui->button_6_num,
        ui->button_ok_num,
        ui->button_7_num,
        ui->button_8_num,
        ui->button_9_num,
        ui->button_left_optional_num,
        ui->button_0_num,
        ui->button_right_optional_num,
    };

    SetupMouseHover();

    if (!initialize_parameters.ok_text.empty()) {
        ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text));
    }

    ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text));
    ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text));

    ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key});
    ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key});

    current_text = initialize_parameters.initial_text;
    cursor_position = initialize_parameters.initial_cursor_position;

    SetTextDrawType();

    for (auto* button : all_buttons) {
        connect(button, &QPushButton::clicked, this, [this, button](bool) {
            if (is_inline) {
                InlineKeyboardButtonClicked(button);
            } else {
                NormalKeyboardButtonClicked(button);
            }
        });
    }

    // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
    if (system.IsPoweredOn()) {
        input_interpreter = std::make_unique<InputInterpreter>(system);
    }
}

QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() {
    StopInputThread();
}

void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) {
    if (isVisible()) {
        return;
    }

    MoveAndResizeWindow(pos, size);

    SetKeyboardType();
    SetPasswordMode();
    SetControllerImage();
    DisableKeyboardButtons();
    SetBackspaceOkEnabled();

    open();
}

void QtSoftwareKeyboardDialog::ShowTextCheckDialog(
    Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
    std::u16string text_check_message) {
    switch (text_check_result) {
    case SwkbdTextCheckResult::Success:
    case SwkbdTextCheckResult::Silent:
    default:
        break;
    case SwkbdTextCheckResult::Failure: {
        StopInputThread();

        OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
                             QString{}, tr("OK"), Qt::AlignCenter);
        dialog.exec();

        StartInputThread();
        break;
    }
    case SwkbdTextCheckResult::Confirm: {
        StopInputThread();

        OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message),
                             tr("Cancel"), tr("OK"), Qt::AlignCenter);
        if (dialog.exec() != QDialog::Accepted) {
            StartInputThread();
            break;
        }

        const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
                                                          : ui->line_edit_osk->text();
        auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());

        emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str), true);
        break;
    }
    }
}

void QtSoftwareKeyboardDialog::ShowInlineKeyboard(
    Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) {
    MoveAndResizeWindow(pos, size);

    ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);"));

    ui->headerOSK->hide();
    ui->subOSK->hide();
    ui->inputOSK->hide();
    ui->charactersOSK->hide();
    ui->inputBoxOSK->hide();
    ui->charactersBoxOSK->hide();

    initialize_parameters.max_text_length = appear_parameters.max_text_length;
    initialize_parameters.min_text_length = appear_parameters.min_text_length;
    initialize_parameters.type = appear_parameters.type;
    initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags;
    initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button;
    initialize_parameters.enable_return_button = appear_parameters.enable_return_button;
    initialize_parameters.disable_cancel_button = appear_parameters.disable_cancel_button;

    SetKeyboardType();
    SetControllerImage();
    DisableKeyboardButtons();
    SetBackspaceOkEnabled();

    open();
}

void QtSoftwareKeyboardDialog::HideInlineKeyboard() {
    StopInputThread();
    QDialog::hide();
}

void QtSoftwareKeyboardDialog::InlineTextChanged(
    Core::Frontend::InlineTextParameters text_parameters) {
    current_text = text_parameters.input_text;
    cursor_position = text_parameters.cursor_position;

    SetBackspaceOkEnabled();
}

void QtSoftwareKeyboardDialog::ExitKeyboard() {
    StopInputThread();
    QDialog::done(QDialog::Accepted);
}

void QtSoftwareKeyboardDialog::open() {
    QDialog::open();

    row = 0;
    column = 0;

    switch (bottom_osk_index) {
    case BottomOSKIndex::LowerCase:
    case BottomOSKIndex::UpperCase: {
        const auto* const curr_button =
            keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column];

        // This is a workaround for setFocus() randomly not showing focus in the UI
        QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
        break;
    }
    case BottomOSKIndex::NumberPad: {
        const auto* const curr_button = numberpad_buttons[row][column];

        // This is a workaround for setFocus() randomly not showing focus in the UI
        QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
        break;
    }
    default:
        break;
    }

    StartInputThread();
}

void QtSoftwareKeyboardDialog::reject() {
    // Pressing the ESC key in a dialog calls QDialog::reject().
    // We will override this behavior to the "Cancel" action on the software keyboard.
    TranslateButtonPress(Core::HID::NpadButton::X);
}

void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) {
    if (!is_inline) {
        QDialog::keyPressEvent(event);
        return;
    }

    const auto entered_key = event->key();

    switch (entered_key) {
    case Qt::Key_Escape:
        QDialog::keyPressEvent(event);
        return;
    case Qt::Key_Backspace:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_backspace->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_backspace_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
            ui->button_backspace_num->click();
            break;
        default:
            break;
        }
        return;
    case Qt::Key_Return:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_ok->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_ok_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
            ui->button_ok_num->click();
            break;
        default:
            break;
        }
        return;
    case Qt::Key_Left:
        MoveTextCursorDirection(Direction::Left);
        return;
    case Qt::Key_Right:
        MoveTextCursorDirection(Direction::Right);
        return;
    default:
        break;
    }

    const auto entered_text = event->text();

    if (entered_text.isEmpty()) {
        return;
    }

    InlineTextInsertString(Common::U16StringFromBuffer(entered_text.utf16(), entered_text.size()));
}

void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) {
    QDialog::move(pos);
    QDialog::resize(size);

    // High DPI
    const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f;

    RescaleKeyboardElements(size.width(), size.height(), dpi_scale);
}

void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) {
    const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
    const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
    const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
    const auto char_button_font_size =
        BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
    const auto label_button_font_size =
        BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;

    QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal);
    QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal);
    QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal);
    QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal);
    QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size,
                            QFont::Normal);

    ui->label_header->setFont(header_font);
    ui->label_sub->setFont(sub_font);
    ui->line_edit_osk->setFont(editor_font);
    ui->text_edit_osk->setFont(editor_font);
    ui->label_characters->setFont(sub_font);
    ui->label_characters_box->setFont(sub_font);

    ui->label_shift->setFont(label_button_font);
    ui->label_shift_shift->setFont(label_button_font);
    ui->label_cancel->setFont(label_button_font);
    ui->label_cancel_shift->setFont(label_button_font);
    ui->label_cancel_num->setFont(label_button_font);
    ui->label_enter->setFont(label_button_font);
    ui->label_enter_shift->setFont(label_button_font);
    ui->label_enter_num->setFont(label_button_font);

    for (auto* button : all_buttons) {
        if (button == ui->button_return || button == ui->button_return_shift) {
            button->setFont(label_button_font);
            continue;
        }

        if (button == ui->button_space || button == ui->button_space_shift) {
            button->setFont(label_button_font);
            continue;
        }

        if (button == ui->button_shift || button == ui->button_shift_shift) {
            button->setFont(label_button_font);
            button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
                                (height / BASE_HEIGHT));
            continue;
        }

        if (button == ui->button_backspace || button == ui->button_backspace_shift ||
            button == ui->button_backspace_num) {
            button->setFont(label_button_font);
            button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) *
                                (height / BASE_HEIGHT));
            continue;
        }

        if (button == ui->button_ok || button == ui->button_ok_shift ||
            button == ui->button_ok_num) {
            button->setFont(label_button_font);
            continue;
        }

        button->setFont(char_button_font);
    }
}

void QtSoftwareKeyboardDialog::SetKeyboardType() {
    switch (initialize_parameters.type) {
    case SwkbdType::Normal:
    case SwkbdType::Qwerty:
    case SwkbdType::Unknown3:
    case SwkbdType::Latin:
    case SwkbdType::SimplifiedChinese:
    case SwkbdType::TraditionalChinese:
    case SwkbdType::Korean:
    default: {
        bottom_osk_index = BottomOSKIndex::LowerCase;
        ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));

        ui->verticalLayout_2->setStretch(0, 320);
        ui->verticalLayout_2->setStretch(1, 400);

        ui->gridLineOSK->setRowStretch(5, 94);
        ui->gridBoxOSK->setRowStretch(2, 81);
        break;
    }
    case SwkbdType::NumberPad: {
        bottom_osk_index = BottomOSKIndex::NumberPad;
        ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));

        ui->verticalLayout_2->setStretch(0, 370);
        ui->verticalLayout_2->setStretch(1, 350);

        ui->gridLineOSK->setRowStretch(5, 144);
        ui->gridBoxOSK->setRowStretch(2, 131);
        break;
    }
    }
}

void QtSoftwareKeyboardDialog::SetPasswordMode() {
    switch (initialize_parameters.password_mode) {
    case SwkbdPasswordMode::Disabled:
    default:
        ui->line_edit_osk->setEchoMode(QLineEdit::Normal);
        break;
    case SwkbdPasswordMode::Enabled:
        ui->line_edit_osk->setEchoMode(QLineEdit::Password);
        break;
    }
}

void QtSoftwareKeyboardDialog::SetTextDrawType() {
    switch (initialize_parameters.text_draw_type) {
    case SwkbdTextDrawType::Line:
    case SwkbdTextDrawType::DownloadCode: {
        ui->topOSK->setCurrentIndex(0);

        if (initialize_parameters.max_text_length <= 10) {
            ui->gridLineOSK->setColumnStretch(0, 390);
            ui->gridLineOSK->setColumnStretch(1, 500);
            ui->gridLineOSK->setColumnStretch(2, 390);
        } else {
            ui->gridLineOSK->setColumnStretch(0, 130);
            ui->gridLineOSK->setColumnStretch(1, 1020);
            ui->gridLineOSK->setColumnStretch(2, 130);
        }

        if (is_inline) {
            return;
        }

        connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) {
            const auto is_valid = ValidateInputText(changed_string);

            const auto text_length = static_cast<u32>(changed_string.length());

            ui->label_characters->setText(QStringLiteral("%1/%2")
                                              .arg(text_length)
                                              .arg(initialize_parameters.max_text_length));

            ui->button_ok->setEnabled(is_valid);
            ui->button_ok_shift->setEnabled(is_valid);
            ui->button_ok_num->setEnabled(is_valid);

            ui->line_edit_osk->setFocus();
        });

        connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged,
                [this](int old_cursor_position, int new_cursor_position) {
                    ui->button_backspace->setEnabled(
                        initialize_parameters.enable_backspace_button && new_cursor_position > 0);
                    ui->button_backspace_shift->setEnabled(
                        initialize_parameters.enable_backspace_button && new_cursor_position > 0);
                    ui->button_backspace_num->setEnabled(
                        initialize_parameters.enable_backspace_button && new_cursor_position > 0);

                    ui->line_edit_osk->setFocus();
                });

        connect(
            ui->line_edit_osk, &QLineEdit::returnPressed, this,
            [this] { TranslateButtonPress(Core::HID::NpadButton::Plus); }, Qt::QueuedConnection);

        ui->line_edit_osk->setPlaceholderText(
            QString::fromStdU16String(initialize_parameters.guide_text));
        ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
        ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length);
        ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position);

        ui->label_characters->setText(QStringLiteral("%1/%2")
                                          .arg(initialize_parameters.initial_text.size())
                                          .arg(initialize_parameters.max_text_length));
        break;
    }
    case SwkbdTextDrawType::Box:
    default: {
        ui->topOSK->setCurrentIndex(1);

        if (is_inline) {
            return;
        }

        connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] {
            if (static_cast<u32>(ui->text_edit_osk->toPlainText().length()) >
                initialize_parameters.max_text_length) {
                auto text_cursor = ui->text_edit_osk->textCursor();
                ui->text_edit_osk->setTextCursor(text_cursor);
                text_cursor.deletePreviousChar();
            }

            const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText());

            const auto text_length = static_cast<u32>(ui->text_edit_osk->toPlainText().length());

            ui->label_characters_box->setText(QStringLiteral("%1/%2")
                                                  .arg(text_length)
                                                  .arg(initialize_parameters.max_text_length));

            ui->button_ok->setEnabled(is_valid);
            ui->button_ok_shift->setEnabled(is_valid);
            ui->button_ok_num->setEnabled(is_valid);

            ui->text_edit_osk->setFocus();
        });

        connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] {
            const auto new_cursor_position = ui->text_edit_osk->textCursor().position();

            ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
                                             new_cursor_position > 0);
            ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
                                                   new_cursor_position > 0);
            ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
                                                 new_cursor_position > 0);

            ui->text_edit_osk->setFocus();
        });

        ui->text_edit_osk->setPlaceholderText(
            QString::fromStdU16String(initialize_parameters.guide_text));
        ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text));
        ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0
                                          ? QTextCursor::Start
                                          : QTextCursor::End);

        ui->label_characters_box->setText(QStringLiteral("%1/%2")
                                              .arg(initialize_parameters.initial_text.size())
                                              .arg(initialize_parameters.max_text_length));
        break;
    }
    }
}

void QtSoftwareKeyboardDialog::SetControllerImage() {
    const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
    const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
    const auto controller_type =
        handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex();

    const QString theme = [] {
        if (QIcon::themeName().contains(QStringLiteral("dark")) ||
            QIcon::themeName().contains(QStringLiteral("midnight"))) {
            return QStringLiteral("_dark");
        } else {
            return QString{};
        }
    }();

    switch (controller_type) {
    case Core::HID::NpadStyleIndex::Fullkey:
    case Core::HID::NpadStyleIndex::GameCube:
        ui->icon_controller->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
        ui->icon_controller_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
        ui->icon_controller_num->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
        break;
    case Core::HID::NpadStyleIndex::JoyconDual:
        ui->icon_controller->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
        ui->icon_controller_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
        ui->icon_controller_num->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
        break;
    case Core::HID::NpadStyleIndex::JoyconLeft:
        ui->icon_controller->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
                .arg(theme));
        ui->icon_controller_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
                .arg(theme));
        ui->icon_controller_num->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
                .arg(theme));
        break;
    case Core::HID::NpadStyleIndex::JoyconRight:
        ui->icon_controller->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
                .arg(theme));
        ui->icon_controller_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
                .arg(theme));
        ui->icon_controller_num->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
                .arg(theme));
        break;
    case Core::HID::NpadStyleIndex::Handheld:
        ui->icon_controller->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
        ui->icon_controller_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
        ui->icon_controller_num->setStyleSheet(
            QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
        break;
    default:
        break;
    }
}

void QtSoftwareKeyboardDialog::DisableKeyboardButtons() {
    switch (bottom_osk_index) {
    case BottomOSKIndex::LowerCase:
    case BottomOSKIndex::UpperCase:
    default: {
        for (const auto& keys : keyboard_buttons) {
            for (const auto& rows : keys) {
                for (auto* button : rows) {
                    if (!button) {
                        continue;
                    }

                    button->setEnabled(true);
                }
            }
        }

        const auto& key_disable_flags = initialize_parameters.key_disable_flags;

        ui->button_space->setDisabled(key_disable_flags.space);
        ui->button_space_shift->setDisabled(key_disable_flags.space);

        ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username);

        ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username);

        ui->button_slash->setDisabled(key_disable_flags.slash);

        ui->button_1->setDisabled(key_disable_flags.numbers);
        ui->button_2->setDisabled(key_disable_flags.numbers);
        ui->button_3->setDisabled(key_disable_flags.numbers);
        ui->button_4->setDisabled(key_disable_flags.numbers);
        ui->button_5->setDisabled(key_disable_flags.numbers);
        ui->button_6->setDisabled(key_disable_flags.numbers);
        ui->button_7->setDisabled(key_disable_flags.numbers);
        ui->button_8->setDisabled(key_disable_flags.numbers);
        ui->button_9->setDisabled(key_disable_flags.numbers);
        ui->button_0->setDisabled(key_disable_flags.numbers);

        ui->button_return->setEnabled(initialize_parameters.enable_return_button);
        ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button);
        break;
    }
    case BottomOSKIndex::NumberPad: {
        for (const auto& rows : numberpad_buttons) {
            for (auto* button : rows) {
                if (!button) {
                    continue;
                }

                button->setEnabled(true);
            }
        }

        const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0';
        const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0';

        ui->button_left_optional_num->setEnabled(enable_left_optional);
        ui->button_left_optional_num->setVisible(enable_left_optional);

        ui->button_right_optional_num->setEnabled(enable_right_optional);
        ui->button_right_optional_num->setVisible(enable_right_optional);
        break;
    }
    }
}

void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() {
    if (is_inline) {
        ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length);
        ui->button_ok_shift->setEnabled(current_text.size() >=
                                        initialize_parameters.min_text_length);
        ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length);

        ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
                                         cursor_position > 0);
        ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
                                               cursor_position > 0);
        ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
                                             cursor_position > 0);
    } else {
        const auto text_length = [this] {
            if (ui->topOSK->currentIndex() == 1) {
                return static_cast<u32>(ui->text_edit_osk->toPlainText().length());
            } else {
                return static_cast<u32>(ui->line_edit_osk->text().length());
            }
        }();

        const auto normal_cursor_position = [this] {
            if (ui->topOSK->currentIndex() == 1) {
                return ui->text_edit_osk->textCursor().position();
            } else {
                return ui->line_edit_osk->cursorPosition();
            }
        }();

        ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length);
        ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length);
        ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length);

        ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button &&
                                         normal_cursor_position > 0);
        ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button &&
                                               normal_cursor_position > 0);
        ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button &&
                                             normal_cursor_position > 0);
    }
}

bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) {
    const auto& key_disable_flags = initialize_parameters.key_disable_flags;

    const auto input_text_length = static_cast<u32>(input_text.length());

    if (input_text_length < initialize_parameters.min_text_length ||
        input_text_length > initialize_parameters.max_text_length) {
        return false;
    }

    if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) {
        return false;
    }

    if ((key_disable_flags.at || key_disable_flags.username) &&
        input_text.contains(QLatin1Char{'@'})) {
        return false;
    }

    if ((key_disable_flags.percent || key_disable_flags.username) &&
        input_text.contains(QLatin1Char{'%'})) {
        return false;
    }

    if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) {
        return false;
    }

    if ((key_disable_flags.backslash || key_disable_flags.username) &&
        input_text.contains(QLatin1Char('\\'))) {
        return false;
    }

    if (key_disable_flags.numbers &&
        std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) {
        return false;
    }

    if (bottom_osk_index == BottomOSKIndex::NumberPad &&
        std::any_of(input_text.begin(), input_text.end(), [this](QChar c) {
            return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} &&
                   c != QChar{initialize_parameters.right_optional_symbol_key};
        })) {
        return false;
    }

    return true;
}

void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() {
    switch (bottom_osk_index) {
    case BottomOSKIndex::LowerCase:
        bottom_osk_index = BottomOSKIndex::UpperCase;
        ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));

        ui->button_shift_shift->setStyleSheet(
            QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
                           "\nimage-position: left;"));

        ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
        ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());
        break;
    case BottomOSKIndex::UpperCase:
        if (caps_lock_enabled) {
            caps_lock_enabled = false;

            ui->button_shift_shift->setStyleSheet(
                QStringLiteral("image: url(:/overlay/osk_button_shift_lock_off.png);"
                               "\nimage-position: left;"));

            ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
            ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());

            ui->label_shift_shift->setText(QStringLiteral("Caps Lock"));

            bottom_osk_index = BottomOSKIndex::LowerCase;
            ui->bottomOSK->setCurrentIndex(static_cast<int>(bottom_osk_index));
        } else {
            caps_lock_enabled = true;

            ui->button_shift_shift->setStyleSheet(
                QStringLiteral("image: url(:/overlay/osk_button_shift_lock_on.png);"
                               "\nimage-position: left;"));

            ui->button_shift_shift->setIconSize(ui->button_shift->iconSize());
            ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize());

            ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off"));
        }
        break;
    case BottomOSKIndex::NumberPad:
    default:
        break;
    }
}

void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) {
    if (button == ui->button_ampersand) {
        if (ui->topOSK->currentIndex() == 1) {
            ui->text_edit_osk->insertPlainText(QStringLiteral("&"));
        } else {
            ui->line_edit_osk->insert(QStringLiteral("&"));
        }
        return;
    }

    if (button == ui->button_return || button == ui->button_return_shift) {
        if (ui->topOSK->currentIndex() == 1) {
            ui->text_edit_osk->insertPlainText(QStringLiteral("\n"));
        } else {
            ui->line_edit_osk->insert(QStringLiteral("\n"));
        }
        return;
    }

    if (button == ui->button_space || button == ui->button_space_shift) {
        if (ui->topOSK->currentIndex() == 1) {
            ui->text_edit_osk->insertPlainText(QStringLiteral(" "));
        } else {
            ui->line_edit_osk->insert(QStringLiteral(" "));
        }
        return;
    }

    if (button == ui->button_shift || button == ui->button_shift_shift) {
        ChangeBottomOSKIndex();
        return;
    }

    if (button == ui->button_backspace || button == ui->button_backspace_shift ||
        button == ui->button_backspace_num) {
        if (ui->topOSK->currentIndex() == 1) {
            auto text_cursor = ui->text_edit_osk->textCursor();
            ui->text_edit_osk->setTextCursor(text_cursor);
            text_cursor.deletePreviousChar();
        } else {
            ui->line_edit_osk->backspace();
        }
        return;
    }

    if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
        const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
                                                          : ui->line_edit_osk->text();
        auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());

        emit SubmitNormalText(SwkbdResult::Ok, std::move(text_str));
        return;
    }

    if (ui->topOSK->currentIndex() == 1) {
        ui->text_edit_osk->insertPlainText(button->text());
    } else {
        ui->line_edit_osk->insert(button->text());
    }

    // Revert the keyboard to lowercase if the shift key is active.
    if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
        // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
        // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
        caps_lock_enabled = true;
        ChangeBottomOSKIndex();
    }
}

void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) {
    if (!button->isEnabled()) {
        return;
    }

    if (button == ui->button_ampersand) {
        InlineTextInsertString(u"&");
        return;
    }

    if (button == ui->button_return || button == ui->button_return_shift) {
        InlineTextInsertString(u"\n");
        return;
    }

    if (button == ui->button_space || button == ui->button_space_shift) {
        InlineTextInsertString(u" ");
        return;
    }

    if (button == ui->button_shift || button == ui->button_shift_shift) {
        ChangeBottomOSKIndex();
        return;
    }

    if (button == ui->button_backspace || button == ui->button_backspace_shift ||
        button == ui->button_backspace_num) {
        if (cursor_position <= 0 || current_text.empty()) {
            cursor_position = 0;
            return;
        }

        --cursor_position;

        current_text.erase(cursor_position, 1);

        SetBackspaceOkEnabled();

        emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
        return;
    }

    if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) {
        emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position);
        return;
    }

    const auto button_text = button->text();
    InlineTextInsertString(Common::U16StringFromBuffer(button_text.utf16(), button_text.size()));

    // Revert the keyboard to lowercase if the shift key is active.
    if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) {
        // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase
        // if bottom_osk_index is UpperCase and caps_lock_enabled is true.
        caps_lock_enabled = true;
        ChangeBottomOSKIndex();
    }
}

void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) {
    if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) {
        return;
    }

    current_text.insert(cursor_position, string);

    cursor_position += static_cast<s32>(string.size());

    SetBackspaceOkEnabled();

    emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position);
}

void QtSoftwareKeyboardDialog::SetupMouseHover() {
    // setFocus() has a bug where continuously changing focus will cause the focus UI to
    // mysteriously disappear. A workaround we have found is using the mouse to hover over
    // the buttons to act in place of the button focus. As a result, we will have to set
    // a blank cursor when hovering over all the buttons and set a no focus policy so the
    // buttons do not stay in focus in addition to the mouse hover.
    for (auto* button : all_buttons) {
        button->setCursor(QCursor(Qt::BlankCursor));
        button->setFocusPolicy(Qt::NoFocus);
    }
}

template <Core::HID::NpadButton... T>
void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
    const auto f = [this](Core::HID::NpadButton button) {
        if (input_interpreter->IsButtonPressedOnce(button)) {
            TranslateButtonPress(button);
        }
    };

    (f(T), ...);
}

template <Core::HID::NpadButton... T>
void QtSoftwareKeyboardDialog::HandleButtonHold() {
    const auto f = [this](Core::HID::NpadButton button) {
        if (input_interpreter->IsButtonHeld(button)) {
            TranslateButtonPress(button);
        }
    };

    (f(T), ...);
}

void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) {
    switch (button) {
    case Core::HID::NpadButton::A:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
        case BottomOSKIndex::UpperCase:
            keyboard_buttons[static_cast<std::size_t>(bottom_osk_index)][row][column]->click();
            break;
        case BottomOSKIndex::NumberPad:
            numberpad_buttons[row][column]->click();
            break;
        default:
            break;
        }
        break;
    case Core::HID::NpadButton::B:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_backspace->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_backspace_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
            ui->button_backspace_num->click();
            break;
        default:
            break;
        }
        break;
    case Core::HID::NpadButton::X:
        if (is_inline) {
            emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
        } else {
            const auto text = ui->topOSK->currentIndex() == 1 ? ui->text_edit_osk->toPlainText()
                                                              : ui->line_edit_osk->text();
            auto text_str = Common::U16StringFromBuffer(text.utf16(), text.size());

            emit SubmitNormalText(SwkbdResult::Cancel, std::move(text_str));
        }
        break;
    case Core::HID::NpadButton::Y:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_space->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_space_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
        default:
            break;
        }
        break;
    case Core::HID::NpadButton::StickL:
    case Core::HID::NpadButton::StickR:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_shift->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_shift_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
        default:
            break;
        }
        break;
    case Core::HID::NpadButton::L:
        MoveTextCursorDirection(Direction::Left);
        break;
    case Core::HID::NpadButton::R:
        MoveTextCursorDirection(Direction::Right);
        break;
    case Core::HID::NpadButton::Plus:
        switch (bottom_osk_index) {
        case BottomOSKIndex::LowerCase:
            ui->button_ok->click();
            break;
        case BottomOSKIndex::UpperCase:
            ui->button_ok_shift->click();
            break;
        case BottomOSKIndex::NumberPad:
            ui->button_ok_num->click();
            break;
        default:
            break;
        }
        break;
    case Core::HID::NpadButton::Left:
    case Core::HID::NpadButton::StickLLeft:
    case Core::HID::NpadButton::StickRLeft:
        MoveButtonDirection(Direction::Left);
        break;
    case Core::HID::NpadButton::Up:
    case Core::HID::NpadButton::StickLUp:
    case Core::HID::NpadButton::StickRUp:
        MoveButtonDirection(Direction::Up);
        break;
    case Core::HID::NpadButton::Right:
    case Core::HID::NpadButton::StickLRight:
    case Core::HID::NpadButton::StickRRight:
        MoveButtonDirection(Direction::Right);
        break;
    case Core::HID::NpadButton::Down:
    case Core::HID::NpadButton::StickLDown:
    case Core::HID::NpadButton::StickRDown:
        MoveButtonDirection(Direction::Down);
        break;
    default:
        break;
    }
}

void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) {
    // Changes the row or column index depending on the direction.
    auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) {
        switch (direction) {
        case Direction::Left:
            column = (column + max_columns - 1) % max_columns;
            break;
        case Direction::Up:
            row = (row + max_rows - 1) % max_rows;
            break;
        case Direction::Right:
            column = (column + 1) % max_columns;
            break;
        case Direction::Down:
            row = (row + 1) % max_rows;
            break;
        default:
            break;
        }
    };

    // Store the initial row and column.
    const auto initial_row = row;
    const auto initial_column = column;

    switch (bottom_osk_index) {
    case BottomOSKIndex::LowerCase:
    case BottomOSKIndex::UpperCase: {
        const auto index = static_cast<std::size_t>(bottom_osk_index);

        const auto* const prev_button = keyboard_buttons[index][row][column];
        move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
        auto* curr_button = keyboard_buttons[index][row][column];

        while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
            // If we returned back to where we started from, break the loop.
            if (row == initial_row && column == initial_column) {
                break;
            }

            move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL);
            curr_button = keyboard_buttons[index][row][column];
        }

        // This is a workaround for setFocus() randomly not showing focus in the UI
        QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
        break;
    }
    case BottomOSKIndex::NumberPad: {
        const auto* const prev_button = numberpad_buttons[row][column];
        move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
        auto* curr_button = numberpad_buttons[row][column];

        while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) {
            // If we returned back to where we started from, break the loop.
            if (row == initial_row && column == initial_column) {
                break;
            }

            move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD);
            curr_button = numberpad_buttons[row][column];
        }

        // This is a workaround for setFocus() randomly not showing focus in the UI
        QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center()));
        break;
    }
    default:
        break;
    }
}

void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) {
    switch (direction) {
    case Direction::Left:
        if (is_inline) {
            if (cursor_position <= 0) {
                cursor_position = 0;
            } else {
                --cursor_position;
                emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
            }
        } else {
            if (ui->topOSK->currentIndex() == 1) {
                ui->text_edit_osk->moveCursor(QTextCursor::Left);
            } else {
                ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1);
            }
        }
        break;
    case Direction::Right:
        if (is_inline) {
            if (cursor_position >= static_cast<s32>(current_text.size())) {
                cursor_position = static_cast<s32>(current_text.size());
            } else {
                ++cursor_position;
                emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position);
            }
        } else {
            if (ui->topOSK->currentIndex() == 1) {
                ui->text_edit_osk->moveCursor(QTextCursor::Right);
            } else {
                ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1);
            }
        }
        break;
    default:
        break;
    }
}

void QtSoftwareKeyboardDialog::StartInputThread() {
    if (input_thread_running) {
        return;
    }

    input_thread_running = true;

    input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this);
}

void QtSoftwareKeyboardDialog::StopInputThread() {
    input_thread_running = false;

    if (input_thread.joinable()) {
        input_thread.join();
    }

    if (input_interpreter) {
        input_interpreter->ResetButtonStates();
    }
}

void QtSoftwareKeyboardDialog::InputThread() {
    while (input_thread_running) {
        input_interpreter->PollInput();

        HandleButtonPressedOnce<
            Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
            Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
            Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
            Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
            Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
            Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
            Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
            Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
            Core::HID::NpadButton::StickRDown>();

        HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
                         Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
                         Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
                         Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
                         Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
                         Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
                         Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
                         Core::HID::NpadButton::StickRDown>();

        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
    connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window,
            &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window,
            &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window,
            &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window,
            &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window,
            &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window,
            &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection);
    connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window,
            &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection);
    connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this,
            &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection);
    connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this,
            &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection);
}

QtSoftwareKeyboard::~QtSoftwareKeyboard() = default;

void QtSoftwareKeyboard::InitializeKeyboard(
    bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
    SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) {
    if (is_inline) {
        submit_inline_callback = std::move(submit_inline_callback_);
    } else {
        submit_normal_callback = std::move(submit_normal_callback_);
    }

    LOG_INFO(Service_AM,
             "\nKeyboardInitializeParameters:"
             "\nok_text={}"
             "\nheader_text={}"
             "\nsub_text={}"
             "\nguide_text={}"
             "\ninitial_text={}"
             "\nmax_text_length={}"
             "\nmin_text_length={}"
             "\ninitial_cursor_position={}"
             "\ntype={}"
             "\npassword_mode={}"
             "\ntext_draw_type={}"
             "\nkey_disable_flags={}"
             "\nuse_blur_background={}"
             "\nenable_backspace_button={}"
             "\nenable_return_button={}"
             "\ndisable_cancel_button={}",
             Common::UTF16ToUTF8(initialize_parameters.ok_text),
             Common::UTF16ToUTF8(initialize_parameters.header_text),
             Common::UTF16ToUTF8(initialize_parameters.sub_text),
             Common::UTF16ToUTF8(initialize_parameters.guide_text),
             Common::UTF16ToUTF8(initialize_parameters.initial_text),
             initialize_parameters.max_text_length, initialize_parameters.min_text_length,
             initialize_parameters.initial_cursor_position, initialize_parameters.type,
             initialize_parameters.password_mode, initialize_parameters.text_draw_type,
             initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background,
             initialize_parameters.enable_backspace_button,
             initialize_parameters.enable_return_button,
             initialize_parameters.disable_cancel_button);

    emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters));
}

void QtSoftwareKeyboard::ShowNormalKeyboard() const {
    emit MainWindowShowNormalKeyboard();
}

void QtSoftwareKeyboard::ShowTextCheckDialog(
    Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
    std::u16string text_check_message) const {
    emit MainWindowShowTextCheckDialog(text_check_result, std::move(text_check_message));
}

void QtSoftwareKeyboard::ShowInlineKeyboard(
    Core::Frontend::InlineAppearParameters appear_parameters) const {
    LOG_INFO(Service_AM,
             "\nInlineAppearParameters:"
             "\nmax_text_length={}"
             "\nmin_text_length={}"
             "\nkey_top_scale_x={}"
             "\nkey_top_scale_y={}"
             "\nkey_top_translate_x={}"
             "\nkey_top_translate_y={}"
             "\ntype={}"
             "\nkey_disable_flags={}"
             "\nkey_top_as_floating={}"
             "\nenable_backspace_button={}"
             "\nenable_return_button={}"
             "\ndisable_cancel_button={}",
             appear_parameters.max_text_length, appear_parameters.min_text_length,
             appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y,
             appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y,
             appear_parameters.type, appear_parameters.key_disable_flags.raw,
             appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button,
             appear_parameters.enable_return_button, appear_parameters.disable_cancel_button);

    emit MainWindowShowInlineKeyboard(std::move(appear_parameters));
}

void QtSoftwareKeyboard::HideInlineKeyboard() const {
    emit MainWindowHideInlineKeyboard();
}

void QtSoftwareKeyboard::InlineTextChanged(
    Core::Frontend::InlineTextParameters text_parameters) const {
    LOG_INFO(Service_AM,
             "\nInlineTextParameters:"
             "\ninput_text={}"
             "\ncursor_position={}",
             Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position);

    emit MainWindowInlineTextChanged(std::move(text_parameters));
}

void QtSoftwareKeyboard::ExitKeyboard() const {
    emit MainWindowExitKeyboard();
}

void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Frontend::SwkbdResult result,
                                          std::u16string submitted_text, bool confirmed) const {
    submit_normal_callback(result, submitted_text, confirmed);
}

void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
                                          std::u16string submitted_text,
                                          s32 cursor_position) const {
    submit_inline_callback(reply_type, submitted_text, cursor_position);
}


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <atomic>
#include <memory>
#include <thread>

#include <QDialog>
#include <QValidator>

#include "core/frontend/applets/software_keyboard.h"

class InputInterpreter;

namespace Core {
class System;
}

namespace Core::HID {
enum class NpadButton : u64;
}

namespace Ui {
class QtSoftwareKeyboardDialog;
}

class GMainWindow;

class QtSoftwareKeyboardDialog final : public QDialog {
    Q_OBJECT

public:
    QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_,
                             Core::Frontend::KeyboardInitializeParameters initialize_parameters_);
    ~QtSoftwareKeyboardDialog() override;

    void ShowNormalKeyboard(QPoint pos, QSize size);

    void ShowTextCheckDialog(Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
                             std::u16string text_check_message);

    void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos,
                            QSize size);

    void HideInlineKeyboard();

    void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters);

    void ExitKeyboard();

signals:
    void SubmitNormalText(Service::AM::Frontend::SwkbdResult result, std::u16string submitted_text,
                          bool confirmed = false) const;

    void SubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
                          std::u16string submitted_text, s32 cursor_position) const;

public slots:
    void open() override;
    void reject() override;

protected:
    /// We override the keyPressEvent for inputting text into the inline software keyboard.
    void keyPressEvent(QKeyEvent* event) override;

private:
    enum class Direction {
        Left,
        Up,
        Right,
        Down,
    };

    enum class BottomOSKIndex {
        LowerCase,
        UpperCase,
        NumberPad,
    };

    /**
     * Moves and resizes the window to a specified position and size.
     *
     * @param pos Top-left window position
     * @param size Window size
     */
    void MoveAndResizeWindow(QPoint pos, QSize size);

    /**
     * Rescales all keyboard elements to account for High DPI displays.
     *
     * @param width Window width
     * @param height Window height
     * @param dpi_scale Display scaling factor
     */
    void RescaleKeyboardElements(float width, float height, float dpi_scale);

    /// Sets the keyboard type based on initialize_parameters.
    void SetKeyboardType();

    /// Sets the password mode based on initialize_parameters.
    void SetPasswordMode();

    /// Sets the text draw type based on initialize_parameters.
    void SetTextDrawType();

    /// Sets the controller image at the bottom left of the software keyboard.
    void SetControllerImage();

    /// Disables buttons based on initialize_parameters.
    void DisableKeyboardButtons();

    /// Changes whether the backspace or/and ok buttons should be enabled or disabled.
    void SetBackspaceOkEnabled();

    /**
     * Validates the input text sent in based on the parameters in initialize_parameters.
     *
     * @param input_text Input text
     *
     * @returns True if the input text is valid, false otherwise.
     */
    bool ValidateInputText(const QString& input_text);

    /// Switches between LowerCase and UpperCase (Shift and Caps Lock)
    void ChangeBottomOSKIndex();

    /// Processes a keyboard button click from the UI as normal keyboard input.
    void NormalKeyboardButtonClicked(QPushButton* button);

    /// Processes a keyboard button click from the UI as inline keyboard input.
    void InlineKeyboardButtonClicked(QPushButton* button);

    /**
     * Inserts a string of arbitrary length into the current_text at the current cursor position.
     * This is only used for the inline software keyboard.
     */
    void InlineTextInsertString(std::u16string_view string);

    /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once.
    void SetupMouseHover();

    /**
     * Handles button presses and converts them into keyboard input.
     *
     * @tparam HIDButton The list of buttons that can be converted into keyboard input.
     */
    template <Core::HID::NpadButton... T>
    void HandleButtonPressedOnce();

    /**
     * Handles button holds and converts them into keyboard input.
     *
     * @tparam HIDButton The list of buttons that can be converted into keyboard input.
     */
    template <Core::HID::NpadButton... T>
    void HandleButtonHold();

    /**
     * Translates a button press to focus or click a keyboard button.
     *
     * @param button The button press to process.
     */
    void TranslateButtonPress(Core::HID::NpadButton button);

    /**
     * Moves the focus of a button in a certain direction.
     *
     * @param direction The direction to move.
     */
    void MoveButtonDirection(Direction direction);

    /**
     * Moves the text cursor in a certain direction.
     *
     * @param direction The direction to move.
     */
    void MoveTextCursorDirection(Direction direction);

    void StartInputThread();
    void StopInputThread();

    /// The thread where input is being polled and processed.
    void InputThread();

    std::unique_ptr<Ui::QtSoftwareKeyboardDialog> ui;

    Core::System& system;

    // True if it is the inline software keyboard.
    bool is_inline;

    // Common software keyboard initialize parameters.
    Core::Frontend::KeyboardInitializeParameters initialize_parameters;

    // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden.
    std::u16string current_text;
    s32 cursor_position{0};

    static constexpr std::size_t NUM_ROWS_NORMAL = 5;
    static constexpr std::size_t NUM_COLUMNS_NORMAL = 12;
    static constexpr std::size_t NUM_ROWS_NUMPAD = 4;
    static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4;

    // Stores the normal keyboard layout.
    std::array<std::array<std::array<QPushButton*, NUM_COLUMNS_NORMAL>, NUM_ROWS_NORMAL>, 2>
        keyboard_buttons;
    // Stores the numberpad keyboard layout.
    std::array<std::array<QPushButton*, NUM_COLUMNS_NUMPAD>, NUM_ROWS_NUMPAD> numberpad_buttons;

    // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons.
    std::array<QPushButton*, 112> all_buttons;

    std::size_t row{0};
    std::size_t column{0};

    BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase};
    std::atomic<bool> caps_lock_enabled{false};

    std::unique_ptr<InputInterpreter> input_interpreter;

    std::thread input_thread;

    std::atomic<bool> input_thread_running{};
};

class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet {
    Q_OBJECT

public:
    explicit QtSoftwareKeyboard(GMainWindow& parent);
    ~QtSoftwareKeyboard() override;

    void Close() const override {
        ExitKeyboard();
    }

    void InitializeKeyboard(bool is_inline,
                            Core::Frontend::KeyboardInitializeParameters initialize_parameters,
                            SubmitNormalCallback submit_normal_callback_,
                            SubmitInlineCallback submit_inline_callback_) override;

    void ShowNormalKeyboard() const override;

    void ShowTextCheckDialog(Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
                             std::u16string text_check_message) const override;

    void ShowInlineKeyboard(
        Core::Frontend::InlineAppearParameters appear_parameters) const override;

    void HideInlineKeyboard() const override;

    void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override;

    void ExitKeyboard() const override;

signals:
    void MainWindowInitializeKeyboard(
        bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const;

    void MainWindowShowNormalKeyboard() const;

    void MainWindowShowTextCheckDialog(
        Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
        std::u16string text_check_message) const;

    void MainWindowShowInlineKeyboard(
        Core::Frontend::InlineAppearParameters appear_parameters) const;

    void MainWindowHideInlineKeyboard() const;

    void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const;

    void MainWindowExitKeyboard() const;

private:
    void SubmitNormalText(Service::AM::Frontend::SwkbdResult result, std::u16string submitted_text,
                          bool confirmed) const;

    void SubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
                          std::u16string submitted_text, s32 cursor_position) const;

    mutable SubmitNormalCallback submit_normal_callback;
    mutable SubmitInlineCallback submit_inline_callback;
};