mirror of
https://git.suyu.dev/suyu/suyu
synced 2025-09-10 16:26:32 -05:00
modifying all the files to match the app
This commit is contained in:
@@ -1,274 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/hle/service/nfc/common/device.h"
|
||||
#include "core/hle/service/nfp/nfp_result.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
#include "input_common/main.h"
|
||||
#include "ui_qt_amiibo_settings.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/web_backend.h"
|
||||
#endif
|
||||
#include "yuzu/applets/qt_amiibo_settings.h"
|
||||
#include "yuzu/main.h"
|
||||
|
||||
QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
|
||||
Core::Frontend::CabinetParameters parameters_,
|
||||
InputCommon::InputSubsystem* input_subsystem_,
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device_)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()),
|
||||
input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)},
|
||||
parameters(std::move(parameters_)) {
|
||||
ui->setupUi(this);
|
||||
|
||||
LoadInfo();
|
||||
|
||||
resize(0, 0);
|
||||
}
|
||||
|
||||
QtAmiiboSettingsDialog::~QtAmiiboSettingsDialog() = default;
|
||||
|
||||
int QtAmiiboSettingsDialog::exec() {
|
||||
if (!is_initialized) {
|
||||
return QDialog::Rejected;
|
||||
}
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
std::string QtAmiiboSettingsDialog::GetName() const {
|
||||
return ui->amiiboCustomNameValue->text().toStdString();
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::LoadInfo() {
|
||||
if (input_subsystem->GetVirtualAmiibo()->ReloadAmiibo() !=
|
||||
InputCommon::VirtualAmiibo::Info::Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound &&
|
||||
nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) {
|
||||
return;
|
||||
}
|
||||
nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All);
|
||||
|
||||
LoadAmiiboInfo();
|
||||
LoadAmiiboData();
|
||||
LoadAmiiboGameInfo();
|
||||
|
||||
ui->amiiboDirectoryValue->setText(
|
||||
QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath()));
|
||||
|
||||
SetSettingsDescription();
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::LoadAmiiboInfo() {
|
||||
Service::NFP::ModelInfo model_info{};
|
||||
const auto model_result = nfp_device->GetModelInfo(model_info);
|
||||
|
||||
if (model_result.IsFailure()) {
|
||||
ui->amiiboImageLabel->setVisible(false);
|
||||
ui->amiiboInfoGroup->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto amiibo_id =
|
||||
fmt::format("{:04x}{:02x}{:02x}{:04x}{:02x}02", Common::swap16(model_info.character_id),
|
||||
model_info.character_variant, model_info.amiibo_type, model_info.model_number,
|
||||
model_info.series);
|
||||
|
||||
LOG_DEBUG(Frontend, "Loading amiibo id {}", amiibo_id);
|
||||
// Note: This function is not being used until we host the images on our server
|
||||
// LoadAmiiboApiInfo(amiibo_id);
|
||||
ui->amiiboImageLabel->setVisible(false);
|
||||
ui->amiiboInfoGroup->setVisible(false);
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::LoadAmiiboApiInfo(std::string_view amiibo_id) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
// TODO: Host this data on our website
|
||||
WebService::Client client{"https://amiiboapi.com", {}, {}};
|
||||
WebService::Client image_client{"https://raw.githubusercontent.com", {}, {}};
|
||||
const auto url_path = fmt::format("/api/amiibo/?id={}", amiibo_id);
|
||||
|
||||
const auto amiibo_json = client.GetJson(url_path, true).returned_data;
|
||||
if (amiibo_json.empty()) {
|
||||
ui->amiiboImageLabel->setVisible(false);
|
||||
ui->amiiboInfoGroup->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string amiibo_series{};
|
||||
std::string amiibo_name{};
|
||||
std::string amiibo_image_url{};
|
||||
std::string amiibo_type{};
|
||||
|
||||
const auto parsed_amiibo_json_json = nlohmann::json::parse(amiibo_json).at("amiibo");
|
||||
parsed_amiibo_json_json.at("amiiboSeries").get_to(amiibo_series);
|
||||
parsed_amiibo_json_json.at("name").get_to(amiibo_name);
|
||||
parsed_amiibo_json_json.at("image").get_to(amiibo_image_url);
|
||||
parsed_amiibo_json_json.at("type").get_to(amiibo_type);
|
||||
|
||||
ui->amiiboSeriesValue->setText(QString::fromStdString(amiibo_series));
|
||||
ui->amiiboNameValue->setText(QString::fromStdString(amiibo_name));
|
||||
ui->amiiboTypeValue->setText(QString::fromStdString(amiibo_type));
|
||||
|
||||
if (amiibo_image_url.size() < 34) {
|
||||
ui->amiiboImageLabel->setVisible(false);
|
||||
}
|
||||
|
||||
const auto image_url_path = amiibo_image_url.substr(34, amiibo_image_url.size() - 34);
|
||||
const auto image_data = image_client.GetImage(image_url_path, true).returned_data;
|
||||
|
||||
if (image_data.empty()) {
|
||||
ui->amiiboImageLabel->setVisible(false);
|
||||
}
|
||||
|
||||
QPixmap pixmap;
|
||||
pixmap.loadFromData(reinterpret_cast<const u8*>(image_data.data()),
|
||||
static_cast<uint>(image_data.size()));
|
||||
pixmap = pixmap.scaled(250, 350, Qt::AspectRatioMode::KeepAspectRatio,
|
||||
Qt::TransformationMode::SmoothTransformation);
|
||||
ui->amiiboImageLabel->setPixmap(pixmap);
|
||||
#endif
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::LoadAmiiboData() {
|
||||
Service::NFP::RegisterInfo register_info{};
|
||||
Service::NFP::CommonInfo common_info{};
|
||||
const auto register_result = nfp_device->GetRegisterInfo(register_info);
|
||||
const auto common_result = nfp_device->GetCommonInfo(common_info);
|
||||
|
||||
if (register_result.IsFailure()) {
|
||||
ui->creationDateValue->setDisabled(true);
|
||||
ui->modificationDateValue->setDisabled(true);
|
||||
ui->amiiboCustomNameValue->setReadOnly(false);
|
||||
ui->amiiboOwnerValue->setReadOnly(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.mode == Service::NFP::CabinetMode::StartNicknameAndOwnerSettings) {
|
||||
ui->creationDateValue->setDisabled(true);
|
||||
ui->modificationDateValue->setDisabled(true);
|
||||
}
|
||||
|
||||
const auto amiibo_name = std::string(register_info.amiibo_name.data());
|
||||
const auto owner_name =
|
||||
Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
|
||||
const auto creation_date =
|
||||
QDate(register_info.creation_date.year, register_info.creation_date.month,
|
||||
register_info.creation_date.day);
|
||||
|
||||
ui->amiiboCustomNameValue->setText(QString::fromStdString(amiibo_name));
|
||||
ui->amiiboOwnerValue->setText(QString::fromStdString(owner_name));
|
||||
ui->amiiboCustomNameValue->setReadOnly(true);
|
||||
ui->amiiboOwnerValue->setReadOnly(true);
|
||||
ui->creationDateValue->setDate(creation_date);
|
||||
|
||||
if (common_result.IsFailure()) {
|
||||
ui->modificationDateValue->setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modification_date =
|
||||
QDate(common_info.last_write_date.year, common_info.last_write_date.month,
|
||||
common_info.last_write_date.day);
|
||||
ui->modificationDateValue->setDate(modification_date);
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::LoadAmiiboGameInfo() {
|
||||
u32 application_area_id{};
|
||||
const auto application_result = nfp_device->GetApplicationAreaId(application_area_id);
|
||||
|
||||
if (application_result.IsFailure()) {
|
||||
ui->gameIdValue->setVisible(false);
|
||||
ui->gameIdLabel->setText(tr("No game data present"));
|
||||
return;
|
||||
}
|
||||
|
||||
SetGameDataName(application_area_id);
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::SetGameDataName(u32 application_area_id) {
|
||||
static constexpr std::array<std::pair<u32, const char*>, 12> game_name_list = {
|
||||
// 3ds, wii u
|
||||
std::pair<u32, const char*>{0x10110E00, "Super Smash Bros (3DS/WiiU)"},
|
||||
{0x00132600, "Mario & Luigi: Paper Jam"},
|
||||
{0x0014F000, "Animal Crossing: Happy Home Designer"},
|
||||
{0x00152600, "Chibi-Robo!: Zip Lash"},
|
||||
{0x10161f00, "Mario Party 10"},
|
||||
{0x1019C800, "The Legend of Zelda: Twilight Princess HD"},
|
||||
// switch
|
||||
{0x10162B00, "Splatoon 2"},
|
||||
{0x1016e100, "Shovel Knight: Treasure Trove"},
|
||||
{0x1019C800, "The Legend of Zelda: Breath of the Wild"},
|
||||
{0x34F80200, "Super Smash Bros. Ultimate"},
|
||||
{0x38600500, "Splatoon 3"},
|
||||
{0x3B440400, "The Legend of Zelda: Link's Awakening"},
|
||||
};
|
||||
|
||||
for (const auto& [game_id, game_name] : game_name_list) {
|
||||
if (application_area_id == game_id) {
|
||||
ui->gameIdValue->setText(QString::fromStdString(game_name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto application_area_string = fmt::format("{:016x}", application_area_id);
|
||||
ui->gameIdValue->setText(QString::fromStdString(application_area_string));
|
||||
}
|
||||
|
||||
void QtAmiiboSettingsDialog::SetSettingsDescription() {
|
||||
switch (parameters.mode) {
|
||||
case Service::NFP::CabinetMode::StartFormatter:
|
||||
ui->cabinetActionDescriptionLabel->setText(
|
||||
tr("The following amiibo data will be formatted:"));
|
||||
break;
|
||||
case Service::NFP::CabinetMode::StartGameDataEraser:
|
||||
ui->cabinetActionDescriptionLabel->setText(tr("The following game data will removed:"));
|
||||
break;
|
||||
case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
|
||||
ui->cabinetActionDescriptionLabel->setText(tr("Set nickname and owner:"));
|
||||
break;
|
||||
case Service::NFP::CabinetMode::StartRestorer:
|
||||
ui->cabinetActionDescriptionLabel->setText(tr("Do you wish to restore this amiibo?"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) {
|
||||
connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent,
|
||||
&GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
|
||||
connect(this, &QtAmiiboSettings::MainWindowRequestExit, &parent,
|
||||
&GMainWindow::AmiiboSettingsRequestExit, Qt::QueuedConnection);
|
||||
connect(&parent, &GMainWindow::AmiiboSettingsFinished, this,
|
||||
&QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtAmiiboSettings::~QtAmiiboSettings() = default;
|
||||
|
||||
void QtAmiiboSettings::Close() const {
|
||||
callback = {};
|
||||
emit MainWindowRequestExit();
|
||||
}
|
||||
|
||||
void QtAmiiboSettings::ShowCabinetApplet(
|
||||
const Core::Frontend::CabinetCallback& callback_,
|
||||
const Core::Frontend::CabinetParameters& parameters,
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const {
|
||||
callback = std::move(callback_);
|
||||
emit MainWindowShowAmiiboSettings(parameters, nfp_device);
|
||||
}
|
||||
|
||||
void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) {
|
||||
if (callback) {
|
||||
callback(is_success, name);
|
||||
}
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
|
||||
class GMainWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class QtAmiiboSettingsDialog;
|
||||
}
|
||||
|
||||
namespace Service::NFC {
|
||||
class NfcDevice;
|
||||
} // namespace Service::NFC
|
||||
|
||||
class QtAmiiboSettingsDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
|
||||
InputCommon::InputSubsystem* input_subsystem_,
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device_);
|
||||
~QtAmiiboSettingsDialog() override;
|
||||
|
||||
int exec() override;
|
||||
|
||||
std::string GetName() const;
|
||||
|
||||
private:
|
||||
void LoadInfo();
|
||||
void LoadAmiiboInfo();
|
||||
void LoadAmiiboApiInfo(std::string_view amiibo_id);
|
||||
void LoadAmiiboData();
|
||||
void LoadAmiiboGameInfo();
|
||||
void SetGameDataName(u32 application_area_id);
|
||||
void SetSettingsDescription();
|
||||
|
||||
std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui;
|
||||
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device;
|
||||
|
||||
// Parameters sent in from the backend HLE applet.
|
||||
Core::Frontend::CabinetParameters parameters;
|
||||
|
||||
// If false amiibo settings failed to load
|
||||
bool is_initialized{};
|
||||
};
|
||||
|
||||
class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApplet {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtAmiiboSettings(GMainWindow& parent);
|
||||
~QtAmiiboSettings() override;
|
||||
|
||||
void Close() const override;
|
||||
void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_,
|
||||
const Core::Frontend::CabinetParameters& parameters,
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters,
|
||||
std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const;
|
||||
void MainWindowRequestExit() const;
|
||||
|
||||
private:
|
||||
void MainWindowFinished(bool is_success, const std::string& name);
|
||||
|
||||
mutable Core::Frontend::CabinetCallback callback;
|
||||
};
|
@@ -1,494 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QtAmiiboSettingsDialog</class>
|
||||
<widget class="QDialog" name="QtAmiiboSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>839</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Amiibo Settings</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="mainControllerApplet" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1" stretch="0,3,0">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="topControllerApplet" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="cabinetActionDescriptionLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="middleControllerApplet" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="amiiboImageLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>350</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>236</width>
|
||||
<height>350</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="amiiboInfoGroup">
|
||||
<property name="title">
|
||||
<string>Amiibo Info</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="amiiboSeriesLabel">
|
||||
<property name="text">
|
||||
<string>Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="amiiboSeriesValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="amiiboTypeLabel">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="amiiboTypeValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="amiiboNameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="amiiboNameValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="amiiboDataGroup">
|
||||
<property name="title">
|
||||
<string>Amiibo Data</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="amiiboCustomNameLabel">
|
||||
<property name="text">
|
||||
<string>Custom Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="amiiboCustomNameValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="amiiboOwnerLabel">
|
||||
<property name="text">
|
||||
<string>Owner</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="amiiboOwnerValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="creationDateLabel">
|
||||
<property name="text">
|
||||
<string>Creation Date</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDateTimeEdit" name="creationDateValue">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumDate">
|
||||
<date>
|
||||
<year>1970</year>
|
||||
<month>1</month>
|
||||
<day>1</day>
|
||||
</date>
|
||||
</property>
|
||||
<property name="displayFormat">
|
||||
<string>dd/MM/yyyy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="modificationDateLabel">
|
||||
<property name="text">
|
||||
<string>Modification Date</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDateTimeEdit" name="modificationDateValue">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumDate">
|
||||
<date>
|
||||
<year>1970</year>
|
||||
<month>1</month>
|
||||
<day>1</day>
|
||||
</date>
|
||||
</property>
|
||||
<property name="displayFormat">
|
||||
<string>dd/MM/yyyy </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameDataGroup">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Game Data</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gameIdLabel">
|
||||
<property name="text">
|
||||
<string>Game Id</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="gameIdValue">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="MountAmiiboGroup">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Mount Amiibo</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="amiiboDirectoryButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Maximum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="amiiboDirectoryLabel">
|
||||
<property name="text">
|
||||
<string>File Path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="amiiboDirectoryValue"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="bottomControllerApplet" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignBottom">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QtAmiiboSettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QtAmiiboSettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@@ -1,778 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
#include "ui_qt_controller.h"
|
||||
#include "yuzu/applets/qt_controller.h"
|
||||
#include "yuzu/configuration/configure_input.h"
|
||||
#include "yuzu/configuration/configure_input_profile_dialog.h"
|
||||
#include "yuzu/configuration/configure_motion_touch.h"
|
||||
#include "yuzu/configuration/configure_vibration.h"
|
||||
#include "yuzu/configuration/input_profiles.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void UpdateController(Core::HID::EmulatedController* controller,
|
||||
Core::HID::NpadStyleIndex controller_type, bool connected) {
|
||||
if (controller->IsConnected(true)) {
|
||||
controller->Disconnect();
|
||||
}
|
||||
controller->SetNpadStyleIndex(controller_type);
|
||||
if (connected) {
|
||||
controller->Connect(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the given controller type is compatible with the given parameters.
|
||||
bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type,
|
||||
Core::Frontend::ControllerParameters parameters) {
|
||||
switch (controller_type) {
|
||||
case Core::HID::NpadStyleIndex::Fullkey:
|
||||
return parameters.allow_pro_controller;
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
return parameters.allow_dual_joycons;
|
||||
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||
return parameters.allow_left_joycon;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
return parameters.allow_right_joycon;
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
return parameters.enable_single_mode && parameters.allow_handheld;
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
return parameters.allow_gamecube_controller;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
QWidget* parent, Core::Frontend::ControllerParameters parameters_,
|
||||
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
|
||||
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
|
||||
input_profiles(std::make_unique<InputProfiles>()), system{system_} {
|
||||
ui->setupUi(this);
|
||||
|
||||
player_widgets = {
|
||||
ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
|
||||
ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
|
||||
};
|
||||
|
||||
player_groupboxes = {
|
||||
ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
|
||||
ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
|
||||
ui->groupPlayer7Connected, ui->groupPlayer8Connected,
|
||||
};
|
||||
|
||||
connected_controller_icons = {
|
||||
ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
|
||||
ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
|
||||
};
|
||||
|
||||
led_patterns_boxes = {{
|
||||
{ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
|
||||
ui->checkboxPlayer1LED4},
|
||||
{ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
|
||||
ui->checkboxPlayer2LED4},
|
||||
{ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
|
||||
ui->checkboxPlayer3LED4},
|
||||
{ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
|
||||
ui->checkboxPlayer4LED4},
|
||||
{ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
|
||||
ui->checkboxPlayer5LED4},
|
||||
{ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
|
||||
ui->checkboxPlayer6LED4},
|
||||
{ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
|
||||
ui->checkboxPlayer7LED4},
|
||||
{ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
|
||||
ui->checkboxPlayer8LED4},
|
||||
}};
|
||||
|
||||
explain_text_labels = {
|
||||
ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
|
||||
ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
|
||||
ui->labelPlayer7Explain, ui->labelPlayer8Explain,
|
||||
};
|
||||
|
||||
emulated_controllers = {
|
||||
ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
|
||||
ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
|
||||
ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
|
||||
};
|
||||
|
||||
player_labels = {
|
||||
ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
|
||||
ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
|
||||
};
|
||||
|
||||
connected_controller_labels = {
|
||||
ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
|
||||
ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
|
||||
ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
|
||||
};
|
||||
|
||||
connected_controller_checkboxes = {
|
||||
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
|
||||
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
|
||||
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
|
||||
};
|
||||
|
||||
ui->labelError->setVisible(false);
|
||||
|
||||
// Setup/load everything prior to setting up connections.
|
||||
// This avoids unintentionally changing the states of elements while loading them in.
|
||||
SetSupportedControllers();
|
||||
DisableUnsupportedPlayers();
|
||||
|
||||
for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) {
|
||||
SetEmulatedControllers(player_index);
|
||||
}
|
||||
|
||||
LoadConfiguration();
|
||||
|
||||
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
|
||||
|
||||
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
|
||||
SetExplainText(i);
|
||||
UpdateControllerIcon(i);
|
||||
UpdateLEDPattern(i);
|
||||
UpdateBorderColor(i);
|
||||
|
||||
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
|
||||
// Reconnect current controller if it was the last one checked
|
||||
// (player number was reduced by more than one)
|
||||
const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
|
||||
player_groupboxes[i + 1]->isChecked();
|
||||
|
||||
// Ensures that connecting a controller changes the number of players
|
||||
if (connected_controller_checkboxes[i]->isChecked() != checked) {
|
||||
// Ensures that the players are always connected in sequential order
|
||||
PropagatePlayerNumberChanged(i, checked, reconnect_first);
|
||||
}
|
||||
});
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
|
||||
// Reconnect current controller if it was the last one checked
|
||||
// (player number was reduced by more than one)
|
||||
const bool reconnect_first = !checked &&
|
||||
i < connected_controller_checkboxes.size() - 1 &&
|
||||
connected_controller_checkboxes[i + 1]->isChecked();
|
||||
|
||||
// Ensures that the players are always connected in sequential order
|
||||
PropagatePlayerNumberChanged(i, checked, reconnect_first);
|
||||
});
|
||||
|
||||
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[this, i](int) {
|
||||
UpdateControllerIcon(i);
|
||||
UpdateControllerState(i);
|
||||
UpdateLEDPattern(i);
|
||||
CheckIfParametersMet();
|
||||
});
|
||||
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
|
||||
player_groupboxes[i]->setChecked(state == Qt::Checked);
|
||||
UpdateControllerIcon(i);
|
||||
UpdateControllerState(i);
|
||||
UpdateLEDPattern(i);
|
||||
UpdateBorderColor(i);
|
||||
CheckIfParametersMet();
|
||||
});
|
||||
|
||||
if (i == 0) {
|
||||
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[this, i](int index) {
|
||||
UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
|
||||
Core::HID::NpadStyleIndex::Handheld);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
connect(ui->vibrationButton, &QPushButton::clicked, this,
|
||||
&QtControllerSelectorDialog::CallConfigureVibrationDialog);
|
||||
|
||||
connect(ui->motionButton, &QPushButton::clicked, this,
|
||||
&QtControllerSelectorDialog::CallConfigureMotionTouchDialog);
|
||||
|
||||
connect(ui->inputConfigButton, &QPushButton::clicked, this,
|
||||
&QtControllerSelectorDialog::CallConfigureInputProfileDialog);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||
&QtControllerSelectorDialog::ApplyConfiguration);
|
||||
|
||||
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||
[this](Qt::Key key) {
|
||||
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
QCoreApplication::postEvent(this, event);
|
||||
});
|
||||
|
||||
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
|
||||
// If all the parameters are met AND only allows a single player,
|
||||
// stop the constructor here as we do not need to continue.
|
||||
if (CheckIfParametersMet() && parameters.enable_single_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If keep_controllers_connected is false, forcefully disconnect all controllers
|
||||
if (!parameters.keep_controllers_connected) {
|
||||
for (auto player : player_groupboxes) {
|
||||
player->setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
resize(0, 0);
|
||||
}
|
||||
|
||||
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
|
||||
controller_navigation->UnloadController();
|
||||
system.HIDCore().DisableAllControllerConfiguration();
|
||||
}
|
||||
|
||||
int QtControllerSelectorDialog::exec() {
|
||||
if (parameters_met && parameters.enable_single_mode) {
|
||||
return QDialog::Accepted;
|
||||
}
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::ApplyConfiguration() {
|
||||
const bool pre_docked_mode = Settings::IsDockedMode();
|
||||
const bool docked_mode_selected = ui->radioDocked->isChecked();
|
||||
Settings::values.use_docked_mode.SetValue(
|
||||
docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
|
||||
OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
|
||||
|
||||
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
|
||||
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::LoadConfiguration() {
|
||||
system.HIDCore().EnableAllControllerConfiguration();
|
||||
|
||||
const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
|
||||
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
const auto connected =
|
||||
controller->IsConnected(true) || (index == 0 && handheld->IsConnected(true));
|
||||
player_groupboxes[index]->setChecked(connected);
|
||||
connected_controller_checkboxes[index]->setChecked(connected);
|
||||
emulated_controllers[index]->setCurrentIndex(
|
||||
GetIndexFromControllerType(controller->GetNpadStyleIndex(true), index));
|
||||
}
|
||||
|
||||
UpdateDockedState(handheld->IsConnected(true));
|
||||
|
||||
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
|
||||
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
|
||||
ConfigureVibration dialog(this, system.HIDCore());
|
||||
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
dialog.ApplyConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() {
|
||||
ConfigureMotionTouch dialog(this, input_subsystem);
|
||||
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
dialog.ApplyConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
|
||||
ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get(), system);
|
||||
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
|
||||
const auto num_connected_players = static_cast<int>(
|
||||
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
|
||||
[](const QGroupBox* player) { return player->isChecked(); }));
|
||||
|
||||
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||
|
||||
if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
|
||||
// Display error message when trying to validate using "Enter" and "OK" button is disabled
|
||||
ui->labelError->setVisible(true);
|
||||
return;
|
||||
} else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
|
||||
// Remove a player if possible
|
||||
connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
|
||||
return;
|
||||
} else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
|
||||
// Add a player, if possible
|
||||
ui->labelError->setVisible(false);
|
||||
connected_controller_checkboxes[num_connected_players]->setChecked(true);
|
||||
return;
|
||||
}
|
||||
QDialog::keyPressEvent(evt);
|
||||
}
|
||||
|
||||
bool QtControllerSelectorDialog::CheckIfParametersMet() {
|
||||
// Here, we check and validate the current configuration against all applicable parameters.
|
||||
const auto num_connected_players = static_cast<int>(
|
||||
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
|
||||
[](const QGroupBox* player) { return player->isChecked(); }));
|
||||
|
||||
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||
|
||||
// First, check against the number of connected players.
|
||||
if (num_connected_players < min_supported_players ||
|
||||
num_connected_players > max_supported_players) {
|
||||
parameters_met = false;
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met);
|
||||
return parameters_met;
|
||||
}
|
||||
|
||||
// Next, check against all connected controllers.
|
||||
const auto all_controllers_compatible = [this] {
|
||||
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
|
||||
// Skip controllers that are not used, we only care about the currently connected ones.
|
||||
if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto compatible = IsControllerCompatible(
|
||||
GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index),
|
||||
parameters);
|
||||
|
||||
// If any controller is found to be incompatible, return false early.
|
||||
if (!compatible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means all currently connected controllers are compatible.
|
||||
return true;
|
||||
}();
|
||||
|
||||
parameters_met = all_controllers_compatible;
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met);
|
||||
return parameters_met;
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::SetSupportedControllers() {
|
||||
const QString theme = [] {
|
||||
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
|
||||
return QStringLiteral("_dark");
|
||||
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
|
||||
return QStringLiteral("_midnight");
|
||||
} else {
|
||||
return QString{};
|
||||
}
|
||||
}();
|
||||
|
||||
if (parameters.enable_single_mode && parameters.allow_handheld) {
|
||||
ui->controllerSupported1->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
|
||||
} else {
|
||||
ui->controllerSupported1->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
|
||||
}
|
||||
|
||||
if (parameters.allow_dual_joycons) {
|
||||
ui->controllerSupported2->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
|
||||
} else {
|
||||
ui->controllerSupported2->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
|
||||
}
|
||||
|
||||
if (parameters.allow_left_joycon) {
|
||||
ui->controllerSupported3->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
|
||||
} else {
|
||||
ui->controllerSupported3->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
|
||||
}
|
||||
|
||||
if (parameters.allow_right_joycon) {
|
||||
ui->controllerSupported4->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
|
||||
} else {
|
||||
ui->controllerSupported4->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
|
||||
}
|
||||
|
||||
if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
|
||||
ui->controllerSupported5->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
|
||||
} else {
|
||||
ui->controllerSupported5->setStyleSheet(
|
||||
QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
|
||||
.arg(theme));
|
||||
}
|
||||
|
||||
// enable_single_mode overrides min_players and max_players.
|
||||
if (parameters.enable_single_mode) {
|
||||
ui->numberSupportedLabel->setText(QStringLiteral("1"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.min_players == parameters.max_players) {
|
||||
ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
|
||||
} else {
|
||||
ui->numberSupportedLabel->setText(
|
||||
QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
|
||||
}
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) {
|
||||
const auto npad_style_set = system.HIDCore().GetSupportedStyleTag();
|
||||
auto& pairs = index_controller_type_pairs[player_index];
|
||||
|
||||
pairs.clear();
|
||||
emulated_controllers[player_index]->clear();
|
||||
|
||||
const auto add_item = [&](Core::HID::NpadStyleIndex controller_type,
|
||||
const QString& controller_name) {
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(), controller_type);
|
||||
emulated_controllers[player_index]->addItem(controller_name);
|
||||
};
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_dual == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::JoyconDual, tr("Dual Joycons"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_left == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::JoyconLeft, tr("Left Joycon"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_right == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::JoyconRight, tr("Right Joycon"));
|
||||
}
|
||||
|
||||
if (player_index == 0 && npad_style_set.handheld == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::Handheld, tr("Handheld"));
|
||||
}
|
||||
|
||||
if (npad_style_set.gamecube == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::GameCube, tr("GameCube Controller"));
|
||||
}
|
||||
|
||||
// Disable all unsupported controllers
|
||||
if (!Settings::values.enable_all_controllers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (npad_style_set.palma == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::Pokeball, tr("Poke Ball Plus"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lark == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::NES, tr("NES Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lucia == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::SNES, tr("SNES Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lagoon == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::N64, tr("N64 Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lager == 1) {
|
||||
add_item(Core::HID::NpadStyleIndex::SegaGenesis, tr("Sega Genesis"));
|
||||
}
|
||||
}
|
||||
|
||||
Core::HID::NpadStyleIndex QtControllerSelectorDialog::GetControllerTypeFromIndex(
|
||||
int index, std::size_t player_index) const {
|
||||
const auto& pairs = index_controller_type_pairs[player_index];
|
||||
|
||||
const auto it = std::find_if(pairs.begin(), pairs.end(),
|
||||
[index](const auto& pair) { return pair.first == index; });
|
||||
|
||||
if (it == pairs.end()) {
|
||||
return Core::HID::NpadStyleIndex::Fullkey;
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int QtControllerSelectorDialog::GetIndexFromControllerType(Core::HID::NpadStyleIndex type,
|
||||
std::size_t player_index) const {
|
||||
const auto& pairs = index_controller_type_pairs[player_index];
|
||||
|
||||
const auto it = std::find_if(pairs.begin(), pairs.end(),
|
||||
[type](const auto& pair) { return pair.second == type; });
|
||||
|
||||
if (it == pairs.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return it->first;
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
|
||||
if (!player_groupboxes[player_index]->isChecked()) {
|
||||
connected_controller_icons[player_index]->setStyleSheet(QString{});
|
||||
player_labels[player_index]->show();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString stylesheet = [this, player_index] {
|
||||
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
|
||||
player_index)) {
|
||||
case Core::HID::NpadStyleIndex::Fullkey:
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
|
||||
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||
return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
|
||||
default:
|
||||
return QString{};
|
||||
}
|
||||
}();
|
||||
|
||||
if (stylesheet.isEmpty()) {
|
||||
connected_controller_icons[player_index]->setStyleSheet(QString{});
|
||||
player_labels[player_index]->show();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString theme = [] {
|
||||
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
|
||||
return QStringLiteral("_dark");
|
||||
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
|
||||
return QStringLiteral("_midnight");
|
||||
} else {
|
||||
return QString{};
|
||||
}
|
||||
}();
|
||||
|
||||
connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
|
||||
player_labels[player_index]->hide();
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
|
||||
auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
|
||||
|
||||
const auto controller_type = GetControllerTypeFromIndex(
|
||||
emulated_controllers[player_index]->currentIndex(), player_index);
|
||||
const auto player_connected = player_groupboxes[player_index]->isChecked() &&
|
||||
controller_type != Core::HID::NpadStyleIndex::Handheld;
|
||||
|
||||
if (controller->GetNpadStyleIndex(true) == controller_type &&
|
||||
controller->IsConnected(true) == player_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disconnect the controller first.
|
||||
UpdateController(controller, controller_type, false);
|
||||
|
||||
// Handheld
|
||||
if (player_index == 0) {
|
||||
if (controller_type == Core::HID::NpadStyleIndex::Handheld) {
|
||||
auto* handheld =
|
||||
system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
UpdateController(handheld, Core::HID::NpadStyleIndex::Handheld,
|
||||
player_groupboxes[player_index]->isChecked());
|
||||
}
|
||||
}
|
||||
|
||||
UpdateController(controller, controller_type, player_connected);
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
|
||||
if (!player_groupboxes[player_index]->isChecked() ||
|
||||
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
|
||||
player_index) == Core::HID::NpadStyleIndex::Handheld) {
|
||||
led_patterns_boxes[player_index][0]->setChecked(false);
|
||||
led_patterns_boxes[player_index][1]->setChecked(false);
|
||||
led_patterns_boxes[player_index][2]->setChecked(false);
|
||||
led_patterns_boxes[player_index][3]->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
|
||||
const auto led_pattern = controller->GetLedPattern();
|
||||
led_patterns_boxes[player_index][0]->setChecked(led_pattern.position1);
|
||||
led_patterns_boxes[player_index][1]->setChecked(led_pattern.position2);
|
||||
led_patterns_boxes[player_index][2]->setChecked(led_pattern.position3);
|
||||
led_patterns_boxes[player_index][3]->setChecked(led_pattern.position4);
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
|
||||
if (!parameters.enable_border_color ||
|
||||
player_index >= static_cast<std::size_t>(parameters.max_players) ||
|
||||
player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
player_groupboxes[player_index]->setStyleSheet(
|
||||
player_groupboxes[player_index]->styleSheet().append(
|
||||
QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
|
||||
"{ border: 1px solid rgba(%2, %3, %4, %5); }")
|
||||
.arg(player_index + 1)
|
||||
.arg(parameters.border_colors[player_index][0])
|
||||
.arg(parameters.border_colors[player_index][1])
|
||||
.arg(parameters.border_colors[player_index][2])
|
||||
.arg(parameters.border_colors[player_index][3])));
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
|
||||
if (!parameters.enable_explain_text ||
|
||||
player_index >= static_cast<std::size_t>(parameters.max_players)) {
|
||||
return;
|
||||
}
|
||||
|
||||
explain_text_labels[player_index]->setText(QString::fromStdString(
|
||||
Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
|
||||
parameters.explain_text[player_index].size())));
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
|
||||
// Disallow changing the console mode if the controller type is handheld.
|
||||
ui->radioDocked->setEnabled(!is_handheld);
|
||||
ui->radioUndocked->setEnabled(!is_handheld);
|
||||
|
||||
ui->radioDocked->setChecked(Settings::IsDockedMode());
|
||||
ui->radioUndocked->setChecked(!Settings::IsDockedMode());
|
||||
|
||||
// Also force into undocked mode if the controller type is handheld.
|
||||
if (is_handheld) {
|
||||
ui->radioUndocked->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
|
||||
bool reconnect_current) {
|
||||
connected_controller_checkboxes[player_index]->setChecked(checked);
|
||||
// Hide eventual error message about number of controllers
|
||||
ui->labelError->setVisible(false);
|
||||
|
||||
if (checked) {
|
||||
// Check all previous buttons when checked
|
||||
if (player_index > 0) {
|
||||
PropagatePlayerNumberChanged(player_index - 1, checked);
|
||||
}
|
||||
} else {
|
||||
// Unchecked all following buttons when unchecked
|
||||
if (player_index < connected_controller_checkboxes.size() - 1) {
|
||||
PropagatePlayerNumberChanged(player_index + 1, checked);
|
||||
}
|
||||
}
|
||||
|
||||
if (reconnect_current) {
|
||||
connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
|
||||
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||
|
||||
switch (max_supported_players) {
|
||||
case 0:
|
||||
default:
|
||||
ASSERT(false);
|
||||
return;
|
||||
case 1:
|
||||
ui->widgetSpacer->hide();
|
||||
ui->widgetSpacer2->hide();
|
||||
ui->widgetSpacer3->hide();
|
||||
ui->widgetSpacer4->hide();
|
||||
break;
|
||||
case 2:
|
||||
ui->widgetSpacer->hide();
|
||||
ui->widgetSpacer2->hide();
|
||||
ui->widgetSpacer3->hide();
|
||||
break;
|
||||
case 3:
|
||||
ui->widgetSpacer->hide();
|
||||
ui->widgetSpacer2->hide();
|
||||
break;
|
||||
case 4:
|
||||
ui->widgetSpacer->hide();
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
break;
|
||||
}
|
||||
|
||||
for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
|
||||
auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
// Disconnect any unsupported players here and disable or hide them if applicable.
|
||||
UpdateController(controller, controller->GetNpadStyleIndex(true), false);
|
||||
// Hide the player widgets when max_supported_controllers is less than or equal to 4.
|
||||
if (max_supported_players <= 4) {
|
||||
player_widgets[index]->hide();
|
||||
}
|
||||
|
||||
// Disable and hide the following to prevent these from interaction.
|
||||
player_widgets[index]->setDisabled(true);
|
||||
connected_controller_checkboxes[index]->setDisabled(true);
|
||||
connected_controller_labels[index]->hide();
|
||||
connected_controller_checkboxes[index]->hide();
|
||||
}
|
||||
}
|
||||
|
||||
QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
|
||||
connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
|
||||
&GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
|
||||
connect(this, &QtControllerSelector::MainWindowRequestExit, &parent,
|
||||
&GMainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection);
|
||||
connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
|
||||
&QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtControllerSelector::~QtControllerSelector() = default;
|
||||
|
||||
void QtControllerSelector::Close() const {
|
||||
callback = {};
|
||||
emit MainWindowRequestExit();
|
||||
}
|
||||
|
||||
void QtControllerSelector::ReconfigureControllers(
|
||||
ReconfigureCallback callback_, const Core::Frontend::ControllerParameters& parameters) const {
|
||||
callback = std::move(callback_);
|
||||
emit MainWindowReconfigureControllers(parameters);
|
||||
}
|
||||
|
||||
void QtControllerSelector::MainWindowReconfigureFinished(bool is_success) {
|
||||
if (callback) {
|
||||
callback(is_success);
|
||||
}
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
#include "core/frontend/applets/controller.h"
|
||||
|
||||
class GMainWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
|
||||
class InputProfiles;
|
||||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class QtControllerSelectorDialog;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
enum class NpadStyleIndex : u8;
|
||||
} // namespace Core::HID
|
||||
|
||||
class ControllerNavigation;
|
||||
|
||||
class QtControllerSelectorDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtControllerSelectorDialog(QWidget* parent,
|
||||
Core::Frontend::ControllerParameters parameters_,
|
||||
InputCommon::InputSubsystem* input_subsystem_,
|
||||
Core::System& system_);
|
||||
~QtControllerSelectorDialog() override;
|
||||
|
||||
int exec() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent* evt) override;
|
||||
|
||||
private:
|
||||
// Applies the current configuration.
|
||||
void ApplyConfiguration();
|
||||
|
||||
// Loads the current input configuration into the frontend applet.
|
||||
void LoadConfiguration();
|
||||
|
||||
// Initializes the "Configure Vibration" Dialog.
|
||||
void CallConfigureVibrationDialog();
|
||||
|
||||
// Initializes the "Configure Motion / Touch" Dialog.
|
||||
void CallConfigureMotionTouchDialog();
|
||||
|
||||
// Initializes the "Create Input Profile" Dialog.
|
||||
void CallConfigureInputProfileDialog();
|
||||
|
||||
// Checks the current configuration against the given parameters.
|
||||
// This sets and returns the value of parameters_met.
|
||||
bool CheckIfParametersMet();
|
||||
|
||||
// Sets the controller icons for "Supported Controller Types".
|
||||
void SetSupportedControllers();
|
||||
|
||||
// Sets the emulated controllers per player.
|
||||
void SetEmulatedControllers(std::size_t player_index);
|
||||
|
||||
// Gets the Controller Type for a given controller combobox index per player.
|
||||
Core::HID::NpadStyleIndex GetControllerTypeFromIndex(int index, std::size_t player_index) const;
|
||||
|
||||
// Gets the controller combobox index for a given Controller Type per player.
|
||||
int GetIndexFromControllerType(Core::HID::NpadStyleIndex type, std::size_t player_index) const;
|
||||
|
||||
// Updates the controller icons per player.
|
||||
void UpdateControllerIcon(std::size_t player_index);
|
||||
|
||||
// Updates the controller state (type and connection status) per player.
|
||||
void UpdateControllerState(std::size_t player_index);
|
||||
|
||||
// Updates the LED pattern per player.
|
||||
void UpdateLEDPattern(std::size_t player_index);
|
||||
|
||||
// Updates the border color per player.
|
||||
void UpdateBorderColor(std::size_t player_index);
|
||||
|
||||
// Sets the "Explain Text" per player.
|
||||
void SetExplainText(std::size_t player_index);
|
||||
|
||||
// Updates the console mode.
|
||||
void UpdateDockedState(bool is_handheld);
|
||||
|
||||
// Enable preceding controllers or disable following ones
|
||||
void PropagatePlayerNumberChanged(size_t player_index, bool checked,
|
||||
bool reconnect_current = false);
|
||||
|
||||
// Disables and disconnects unsupported players based on the given parameters.
|
||||
void DisableUnsupportedPlayers();
|
||||
|
||||
std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
|
||||
|
||||
// Parameters sent in from the backend HLE applet.
|
||||
Core::Frontend::ControllerParameters parameters;
|
||||
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
|
||||
std::unique_ptr<InputProfiles> input_profiles;
|
||||
|
||||
Core::System& system;
|
||||
|
||||
ControllerNavigation* controller_navigation = nullptr;
|
||||
|
||||
// This is true if and only if all parameters are met. Otherwise, this is false.
|
||||
// This determines whether the "OK" button can be clicked to exit the applet.
|
||||
bool parameters_met{false};
|
||||
|
||||
static constexpr std::size_t NUM_PLAYERS = 8;
|
||||
|
||||
// Widgets encapsulating the groupboxes and comboboxes per player.
|
||||
std::array<QWidget*, NUM_PLAYERS> player_widgets;
|
||||
|
||||
// Groupboxes encapsulating the controller icons and LED patterns per player.
|
||||
std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
|
||||
|
||||
// Icons for currently connected controllers/players.
|
||||
std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
|
||||
|
||||
// Labels that represent the player numbers in place of the controller icons.
|
||||
std::array<QLabel*, NUM_PLAYERS> player_labels;
|
||||
|
||||
// LED patterns for currently connected controllers/players.
|
||||
std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
|
||||
|
||||
// Labels representing additional information known as "Explain Text" per player.
|
||||
std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
|
||||
|
||||
// Comboboxes with a list of emulated controllers per player.
|
||||
std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
|
||||
|
||||
/// Pairs of emulated controller index and Controller Type enum per player.
|
||||
std::array<std::vector<std::pair<int, Core::HID::NpadStyleIndex>>, NUM_PLAYERS>
|
||||
index_controller_type_pairs;
|
||||
|
||||
// Labels representing the number of connected controllers
|
||||
// above the "Connected Controllers" checkboxes.
|
||||
std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
|
||||
|
||||
// Checkboxes representing the "Connected Controllers".
|
||||
std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
|
||||
};
|
||||
|
||||
class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtControllerSelector(GMainWindow& parent);
|
||||
~QtControllerSelector() override;
|
||||
|
||||
void Close() const override;
|
||||
void ReconfigureControllers(
|
||||
ReconfigureCallback callback_,
|
||||
const Core::Frontend::ControllerParameters& parameters) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowReconfigureControllers(
|
||||
const Core::Frontend::ControllerParameters& parameters) const;
|
||||
void MainWindowRequestExit() const;
|
||||
|
||||
private:
|
||||
void MainWindowReconfigureFinished(bool is_success);
|
||||
|
||||
mutable ReconfigureCallback callback;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDateTime>
|
||||
#include "yuzu/applets/qt_error.h"
|
||||
#include "yuzu/main.h"
|
||||
|
||||
QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
|
||||
connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent,
|
||||
&GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
|
||||
connect(this, &QtErrorDisplay::MainWindowRequestExit, &parent,
|
||||
&GMainWindow::ErrorDisplayRequestExit, Qt::QueuedConnection);
|
||||
connect(&parent, &GMainWindow::ErrorDisplayFinished, this,
|
||||
&QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QtErrorDisplay::~QtErrorDisplay() = default;
|
||||
|
||||
void QtErrorDisplay::Close() const {
|
||||
callback = {};
|
||||
emit MainWindowRequestExit();
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowError(Result error, FinishedCallback finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\nPlease try again or contact the developer of the software."));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
|
||||
FinishedCallback finished) const {
|
||||
callback = std::move(finished);
|
||||
|
||||
const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count());
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the "
|
||||
"software.")
|
||||
.arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
|
||||
.arg(date_time.toString(QStringLiteral("h:mm:ss A"))));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text,
|
||||
std::string fullscreen_text,
|
||||
FinishedCallback finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\n\n%1\n\n%2")
|
||||
.arg(QString::fromStdString(dialog_text))
|
||||
.arg(QString::fromStdString(fullscreen_text)));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::MainWindowFinishedError() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
// 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;
|
||||
};
|
@@ -1,260 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
@@ -1,87 +0,0 @@
|
||||
// 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;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -1,287 +0,0 @@
|
||||
// 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;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -1,449 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
#include <bit>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include <QWebEngineProfile>
|
||||
#include <QWebEngineScript>
|
||||
#include <QWebEngineScriptCollection>
|
||||
#include <QWebEngineSettings>
|
||||
#include <QWebEngineUrlScheme>
|
||||
|
||||
#include "hid_core/frontend/input_interpreter.h"
|
||||
#include "yuzu/applets/qt_web_browser_scripts.h"
|
||||
#endif
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "core/core.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "yuzu/applets/qt_web_browser.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/util/url_request_interceptor.h"
|
||||
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int HIDButtonToKey(Core::HID::NpadButton button) {
|
||||
switch (button) {
|
||||
case Core::HID::NpadButton::Left:
|
||||
case Core::HID::NpadButton::StickLLeft:
|
||||
return Qt::Key_Left;
|
||||
case Core::HID::NpadButton::Up:
|
||||
case Core::HID::NpadButton::StickLUp:
|
||||
return Qt::Key_Up;
|
||||
case Core::HID::NpadButton::Right:
|
||||
case Core::HID::NpadButton::StickLRight:
|
||||
return Qt::Key_Right;
|
||||
case Core::HID::NpadButton::Down:
|
||||
case Core::HID::NpadButton::StickLDown:
|
||||
return Qt::Key_Down;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
|
||||
InputCommon::InputSubsystem* input_subsystem_)
|
||||
: QWebEngineView(parent), input_subsystem{input_subsystem_},
|
||||
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
|
||||
input_interpreter(std::make_unique<InputInterpreter>(system)),
|
||||
default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
|
||||
default_profile->settings()} {
|
||||
default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine")));
|
||||
|
||||
QWebEngineScript gamepad;
|
||||
QWebEngineScript window_nx;
|
||||
|
||||
gamepad.setName(QStringLiteral("gamepad_script.js"));
|
||||
window_nx.setName(QStringLiteral("window_nx_script.js"));
|
||||
|
||||
gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
|
||||
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
|
||||
|
||||
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
|
||||
gamepad.setWorldId(QWebEngineScript::MainWorld);
|
||||
window_nx.setWorldId(QWebEngineScript::MainWorld);
|
||||
|
||||
gamepad.setRunsOnSubFrames(true);
|
||||
window_nx.setRunsOnSubFrames(true);
|
||||
|
||||
default_profile->scripts()->insert(gamepad);
|
||||
default_profile->scripts()->insert(window_nx);
|
||||
|
||||
default_profile->setUrlRequestInterceptor(url_interceptor.get());
|
||||
|
||||
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
||||
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
|
||||
global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
|
||||
global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
|
||||
global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
|
||||
global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
|
||||
|
||||
global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
|
||||
|
||||
connect(
|
||||
page(), &QWebEnginePage::windowCloseRequested, page(),
|
||||
[this] {
|
||||
if (page()->url() == url_interceptor->GetRequestedURL()) {
|
||||
SetFinished(true);
|
||||
SetExitReason(Service::AM::Frontend::WebExitReason::WindowClosed);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtNXWebEngineView::~QtNXWebEngineView() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = true;
|
||||
|
||||
LoadExtractedFonts();
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Frontend::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
|
||||
QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = false;
|
||||
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Frontend::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
|
||||
const QString user_agent_str = [user_agent] {
|
||||
switch (user_agent) {
|
||||
case UserAgent::WebApplet:
|
||||
default:
|
||||
return QStringLiteral("WebApplet");
|
||||
case UserAgent::ShopN:
|
||||
return QStringLiteral("ShopN");
|
||||
case UserAgent::LoginApplet:
|
||||
return QStringLiteral("LoginApplet");
|
||||
case UserAgent::ShareApplet:
|
||||
return QStringLiteral("ShareApplet");
|
||||
case UserAgent::LobbyApplet:
|
||||
return QStringLiteral("LobbyApplet");
|
||||
case UserAgent::WifiWebAuthApplet:
|
||||
return QStringLiteral("WifiWebAuthApplet");
|
||||
}
|
||||
}();
|
||||
|
||||
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
|
||||
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
|
||||
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
|
||||
.arg(user_agent_str));
|
||||
}
|
||||
|
||||
bool QtNXWebEngineView::IsFinished() const {
|
||||
return finished;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetFinished(bool finished_) {
|
||||
finished = finished_;
|
||||
}
|
||||
|
||||
Service::AM::Frontend::WebExitReason QtNXWebEngineView::GetExitReason() const {
|
||||
return exit_reason;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetExitReason(Service::AM::Frontend::WebExitReason exit_reason_) {
|
||||
exit_reason = exit_reason_;
|
||||
}
|
||||
|
||||
const std::string& QtNXWebEngineView::GetLastURL() const {
|
||||
return last_url;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
|
||||
last_url = std::move(last_url_);
|
||||
}
|
||||
|
||||
QString QtNXWebEngineView::GetCurrentURL() const {
|
||||
return url_interceptor->GetRequestedURL().toString();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::hide() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
|
||||
QWidget::hide();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||
const auto button_index = std::countr_zero(static_cast<u64>(button));
|
||||
|
||||
page()->runJavaScript(
|
||||
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(button_index),
|
||||
[this, button](const QVariant& variant) {
|
||||
if (variant.toBool()) {
|
||||
switch (button) {
|
||||
case Core::HID::NpadButton::A:
|
||||
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
|
||||
break;
|
||||
case Core::HID::NpadButton::B:
|
||||
SendKeyPressEvent(Qt::Key_B);
|
||||
break;
|
||||
case Core::HID::NpadButton::X:
|
||||
SendKeyPressEvent(Qt::Key_X);
|
||||
break;
|
||||
case Core::HID::NpadButton::Y:
|
||||
SendKeyPressEvent(Qt::Key_Y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
page()->runJavaScript(
|
||||
QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
|
||||
.arg(button_index));
|
||||
}
|
||||
};
|
||||
|
||||
(f(T), ...);
|
||||
}
|
||||
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||
SendKeyPressEvent(HIDButtonToKey(button));
|
||||
}
|
||||
};
|
||||
|
||||
(f(T), ...);
|
||||
}
|
||||
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonHeld(button)) {
|
||||
SendKeyPressEvent(HIDButtonToKey(button));
|
||||
}
|
||||
};
|
||||
|
||||
(f(T), ...);
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SendKeyPressEvent(int key) {
|
||||
if (key == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QCoreApplication::postEvent(focusProxy(),
|
||||
new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
|
||||
QCoreApplication::postEvent(focusProxy(),
|
||||
new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::StartInputThread() {
|
||||
if (input_thread_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
input_thread_running = true;
|
||||
input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::StopInputThread() {
|
||||
if (is_local) {
|
||||
QWidget::releaseKeyboard();
|
||||
}
|
||||
|
||||
input_thread_running = false;
|
||||
if (input_thread.joinable()) {
|
||||
input_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::InputThread() {
|
||||
// Wait for 1 second before allowing any inputs to be processed.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
if (is_local) {
|
||||
QWidget::grabKeyboard();
|
||||
}
|
||||
|
||||
while (input_thread_running) {
|
||||
input_interpreter->PollInput();
|
||||
|
||||
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
|
||||
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
|
||||
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
|
||||
|
||||
HandleWindowKeyButtonPressedOnce<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
HandleWindowKeyButtonHold<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadExtractedFonts() {
|
||||
QWebEngineScript nx_font_css;
|
||||
QWebEngineScript load_nx_font;
|
||||
|
||||
auto fonts_dir_str = Common::FS::PathToUTF8String(
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/");
|
||||
|
||||
std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/');
|
||||
|
||||
const auto fonts_dir = QString::fromStdString(fonts_dir_str);
|
||||
|
||||
nx_font_css.setName(QStringLiteral("nx_font_css.js"));
|
||||
load_nx_font.setName(QStringLiteral("load_nx_font.js"));
|
||||
|
||||
nx_font_css.setSourceCode(
|
||||
QString::fromStdString(NX_FONT_CSS)
|
||||
.arg(fonts_dir + QStringLiteral("FontStandard.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontKorean.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf"))
|
||||
.arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf")));
|
||||
load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
|
||||
|
||||
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
|
||||
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
|
||||
|
||||
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
|
||||
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
|
||||
|
||||
nx_font_css.setRunsOnSubFrames(true);
|
||||
load_nx_font.setRunsOnSubFrames(true);
|
||||
|
||||
default_profile->scripts()->insert(nx_font_css);
|
||||
default_profile->scripts()->insert(load_nx_font);
|
||||
|
||||
connect(
|
||||
url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
|
||||
[this] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::FocusFirstLinkElement() {
|
||||
QWebEngineScript focus_link_element;
|
||||
|
||||
focus_link_element.setName(QStringLiteral("focus_link_element.js"));
|
||||
focus_link_element.setSourceCode(QString::fromStdString(FOCUS_LINK_ELEMENT_SCRIPT));
|
||||
focus_link_element.setWorldId(QWebEngineScript::MainWorld);
|
||||
focus_link_element.setInjectionPoint(QWebEngineScript::Deferred);
|
||||
focus_link_element.setRunsOnSubFrames(true);
|
||||
default_profile->scripts()->insert(focus_link_element);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
|
||||
connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
|
||||
&GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
|
||||
connect(this, &QtWebBrowser::MainWindowRequestExit, &main_window,
|
||||
&GMainWindow::WebBrowserRequestExit, Qt::QueuedConnection);
|
||||
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
|
||||
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
|
||||
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
|
||||
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtWebBrowser::~QtWebBrowser() = default;
|
||||
|
||||
void QtWebBrowser::Close() const {
|
||||
callback = {};
|
||||
emit MainWindowRequestExit();
|
||||
}
|
||||
|
||||
void QtWebBrowser::OpenLocalWebPage(const std::string& local_url,
|
||||
ExtractROMFSCallback extract_romfs_callback_,
|
||||
OpenWebPageCallback callback_) const {
|
||||
extract_romfs_callback = std::move(extract_romfs_callback_);
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = local_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(local_url, "", true);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::OpenExternalWebPage(const std::string& external_url,
|
||||
OpenWebPageCallback callback_) const {
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = external_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(external_url, "", false);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
|
||||
extract_romfs_callback();
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason,
|
||||
std::string last_url) {
|
||||
if (callback) {
|
||||
callback(exit_reason, last_url);
|
||||
}
|
||||
}
|
@@ -1,220 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
#include <QWebEngineView>
|
||||
#endif
|
||||
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
|
||||
class GMainWindow;
|
||||
class InputInterpreter;
|
||||
class UrlRequestInterceptor;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
enum class NpadButton : u64;
|
||||
}
|
||||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
|
||||
enum class UserAgent {
|
||||
WebApplet,
|
||||
ShopN,
|
||||
LoginApplet,
|
||||
ShareApplet,
|
||||
LobbyApplet,
|
||||
WifiWebAuthApplet,
|
||||
};
|
||||
|
||||
class QWebEngineProfile;
|
||||
class QWebEngineSettings;
|
||||
|
||||
class QtNXWebEngineView : public QWebEngineView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtNXWebEngineView(QWidget* parent, Core::System& system,
|
||||
InputCommon::InputSubsystem* input_subsystem_);
|
||||
~QtNXWebEngineView() override;
|
||||
|
||||
/**
|
||||
* Loads a HTML document that exists locally. Cannot be used to load external websites.
|
||||
*
|
||||
* @param main_url The url to the file.
|
||||
* @param additional_args Additional arguments appended to the main url.
|
||||
*/
|
||||
void LoadLocalWebPage(const std::string& main_url, const std::string& additional_args);
|
||||
|
||||
/**
|
||||
* Loads an external website. Cannot be used to load local urls.
|
||||
*
|
||||
* @param main_url The url to the website.
|
||||
* @param additional_args Additional arguments appended to the main url.
|
||||
*/
|
||||
void LoadExternalWebPage(const std::string& main_url, const std::string& additional_args);
|
||||
|
||||
/**
|
||||
* Sets the background color of the web page.
|
||||
*
|
||||
* @param color The color to set.
|
||||
*/
|
||||
void SetBackgroundColor(QColor color);
|
||||
|
||||
/**
|
||||
* Sets the user agent of the web browser.
|
||||
*
|
||||
* @param user_agent The user agent enum.
|
||||
*/
|
||||
void SetUserAgent(UserAgent user_agent);
|
||||
|
||||
[[nodiscard]] bool IsFinished() const;
|
||||
void SetFinished(bool finished_);
|
||||
|
||||
[[nodiscard]] Service::AM::Frontend::WebExitReason GetExitReason() const;
|
||||
void SetExitReason(Service::AM::Frontend::WebExitReason exit_reason_);
|
||||
|
||||
[[nodiscard]] const std::string& GetLastURL() const;
|
||||
void SetLastURL(std::string last_url_);
|
||||
|
||||
/**
|
||||
* This gets the current URL that has been requested by the webpage.
|
||||
* This only applies to the main frame. Sub frames and other resources are ignored.
|
||||
*
|
||||
* @return Currently requested URL
|
||||
*/
|
||||
[[nodiscard]] QString GetCurrentURL() const;
|
||||
|
||||
public slots:
|
||||
void hide();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handles button presses to execute functions assigned in yuzu_key_callbacks.
|
||||
* yuzu_key_callbacks contains specialized functions for the buttons in the window footer
|
||||
* that can be overridden by games to achieve desired functionality.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
|
||||
*/
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowFooterButtonPressedOnce();
|
||||
|
||||
/**
|
||||
* Handles button presses and converts them into keyboard input.
|
||||
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowKeyButtonPressedOnce();
|
||||
|
||||
/**
|
||||
* Handles button holds and converts them into keyboard input.
|
||||
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowKeyButtonHold();
|
||||
|
||||
/**
|
||||
* Sends a key press event to QWebEngineView.
|
||||
*
|
||||
* @param key Qt key code.
|
||||
*/
|
||||
void SendKeyPressEvent(int key);
|
||||
|
||||
/**
|
||||
* Sends multiple key press events to QWebEngineView.
|
||||
*
|
||||
* @tparam int Qt key code.
|
||||
*/
|
||||
template <int... T>
|
||||
void SendMultipleKeyPressEvents() {
|
||||
(SendKeyPressEvent(T), ...);
|
||||
}
|
||||
|
||||
void StartInputThread();
|
||||
void StopInputThread();
|
||||
|
||||
/// The thread where input is being polled and processed.
|
||||
void InputThread();
|
||||
|
||||
/// Loads the extracted fonts using JavaScript.
|
||||
void LoadExtractedFonts();
|
||||
|
||||
/// Brings focus to the first available link element.
|
||||
void FocusFirstLinkElement();
|
||||
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
|
||||
std::unique_ptr<UrlRequestInterceptor> url_interceptor;
|
||||
|
||||
std::unique_ptr<InputInterpreter> input_interpreter;
|
||||
|
||||
std::thread input_thread;
|
||||
|
||||
std::atomic<bool> input_thread_running{};
|
||||
|
||||
std::atomic<bool> finished{};
|
||||
|
||||
Service::AM::Frontend::WebExitReason exit_reason{
|
||||
Service::AM::Frontend::WebExitReason::EndButtonPressed};
|
||||
|
||||
std::string last_url{"http://localhost/"};
|
||||
|
||||
bool is_local{};
|
||||
|
||||
QWebEngineProfile* default_profile;
|
||||
QWebEngineSettings* global_settings;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtWebBrowser(GMainWindow& parent);
|
||||
~QtWebBrowser() override;
|
||||
|
||||
void Close() const override;
|
||||
void OpenLocalWebPage(const std::string& local_url,
|
||||
ExtractROMFSCallback extract_romfs_callback_,
|
||||
OpenWebPageCallback callback_) const override;
|
||||
|
||||
void OpenExternalWebPage(const std::string& external_url,
|
||||
OpenWebPageCallback callback_) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args,
|
||||
bool is_local) const;
|
||||
void MainWindowRequestExit() const;
|
||||
|
||||
private:
|
||||
void MainWindowExtractOfflineRomFS();
|
||||
|
||||
void MainWindowWebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason,
|
||||
std::string last_url);
|
||||
|
||||
mutable ExtractROMFSCallback extract_romfs_callback;
|
||||
mutable OpenWebPageCallback callback;
|
||||
};
|
@@ -1,198 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr char NX_FONT_CSS[] = R"(
|
||||
(function() {
|
||||
css = document.createElement('style');
|
||||
css.type = 'text/css';
|
||||
css.id = 'nx_font';
|
||||
css.innerText = `
|
||||
/* FontStandard */
|
||||
@font-face {
|
||||
font-family: 'FontStandard';
|
||||
src: url('%1') format('truetype');
|
||||
}
|
||||
|
||||
/* FontChineseSimplified */
|
||||
@font-face {
|
||||
font-family: 'FontChineseSimplified';
|
||||
src: url('%2') format('truetype');
|
||||
}
|
||||
|
||||
/* FontExtendedChineseSimplified */
|
||||
@font-face {
|
||||
font-family: 'FontExtendedChineseSimplified';
|
||||
src: url('%3') format('truetype');
|
||||
}
|
||||
|
||||
/* FontChineseTraditional */
|
||||
@font-face {
|
||||
font-family: 'FontChineseTraditional';
|
||||
src: url('%4') format('truetype');
|
||||
}
|
||||
|
||||
/* FontKorean */
|
||||
@font-face {
|
||||
font-family: 'FontKorean';
|
||||
src: url('%5') format('truetype');
|
||||
}
|
||||
|
||||
/* FontNintendoExtended */
|
||||
@font-face {
|
||||
font-family: 'NintendoExt003';
|
||||
src: url('%6') format('truetype');
|
||||
}
|
||||
|
||||
/* FontNintendoExtended2 */
|
||||
@font-face {
|
||||
font-family: 'NintendoExt003';
|
||||
src: url('%7') format('truetype');
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(css);
|
||||
})();
|
||||
)";
|
||||
|
||||
constexpr char LOAD_NX_FONT[] = R"(
|
||||
(function() {
|
||||
var elements = document.querySelectorAll("*");
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var style = window.getComputedStyle(elements[i], null);
|
||||
if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") ||
|
||||
style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) {
|
||||
elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
|
||||
} else {
|
||||
elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
|
||||
}
|
||||
}
|
||||
})();
|
||||
)";
|
||||
|
||||
constexpr char FOCUS_LINK_ELEMENT_SCRIPT[] = R"(
|
||||
if (document.getElementsByTagName("a").length > 0) {
|
||||
document.getElementsByTagName("a")[0].focus();
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr char GAMEPAD_SCRIPT[] = R"(
|
||||
window.addEventListener("gamepadconnected", function(e) {
|
||||
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
|
||||
e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", function(e) {
|
||||
console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id);
|
||||
});
|
||||
)";
|
||||
|
||||
constexpr char WINDOW_NX_SCRIPT[] = R"(
|
||||
var end_applet = false;
|
||||
var yuzu_key_callbacks = [];
|
||||
|
||||
(function() {
|
||||
class WindowNX {
|
||||
constructor() {
|
||||
yuzu_key_callbacks[1] = function() { window.history.back(); };
|
||||
yuzu_key_callbacks[2] = function() { window.nx.endApplet(); };
|
||||
}
|
||||
|
||||
addEventListener(type, listener, options) {
|
||||
console.log("nx.addEventListener called, type=%s", type);
|
||||
|
||||
window.addEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
endApplet() {
|
||||
console.log("nx.endApplet called");
|
||||
|
||||
end_applet = true;
|
||||
}
|
||||
|
||||
playSystemSe(system_se) {
|
||||
console.log("nx.playSystemSe is not implemented, system_se=%s", system_se);
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
console.log("nx.sendMessage is not implemented, message=%s", message);
|
||||
}
|
||||
|
||||
setCursorScrollSpeed(scroll_speed) {
|
||||
console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed);
|
||||
}
|
||||
}
|
||||
|
||||
class WindowNXFooter {
|
||||
setAssign(key, label, func, option) {
|
||||
console.log("nx.footer.setAssign called, key=%s", key);
|
||||
|
||||
switch (key) {
|
||||
case "A":
|
||||
yuzu_key_callbacks[0] = func;
|
||||
break;
|
||||
case "B":
|
||||
yuzu_key_callbacks[1] = func;
|
||||
break;
|
||||
case "X":
|
||||
yuzu_key_callbacks[2] = func;
|
||||
break;
|
||||
case "Y":
|
||||
yuzu_key_callbacks[3] = func;
|
||||
break;
|
||||
case "L":
|
||||
yuzu_key_callbacks[6] = func;
|
||||
break;
|
||||
case "R":
|
||||
yuzu_key_callbacks[7] = func;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setFixed(kind) {
|
||||
console.log("nx.footer.setFixed is not implemented, kind=%s", kind);
|
||||
}
|
||||
|
||||
unsetAssign(key) {
|
||||
console.log("nx.footer.unsetAssign called, key=%s", key);
|
||||
|
||||
switch (key) {
|
||||
case "A":
|
||||
yuzu_key_callbacks[0] = function() {};
|
||||
break;
|
||||
case "B":
|
||||
yuzu_key_callbacks[1] = function() {};
|
||||
break;
|
||||
case "X":
|
||||
yuzu_key_callbacks[2] = function() {};
|
||||
break;
|
||||
case "Y":
|
||||
yuzu_key_callbacks[3] = function() {};
|
||||
break;
|
||||
case "L":
|
||||
yuzu_key_callbacks[6] = function() {};
|
||||
break;
|
||||
case "R":
|
||||
yuzu_key_callbacks[7] = function() {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WindowNXPlayReport {
|
||||
incrementCounter(counter_id) {
|
||||
console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id);
|
||||
}
|
||||
|
||||
setCounterSetIdentifier(counter_id) {
|
||||
console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id);
|
||||
}
|
||||
}
|
||||
|
||||
window.nx = new WindowNX();
|
||||
window.nx.footer = new WindowNXFooter();
|
||||
window.nx.playReport = new WindowNXPlayReport();
|
||||
})();
|
||||
)";
|
Reference in New Issue
Block a user