another try
This commit is contained in:
+695
-695
File diff suppressed because it is too large
Load Diff
+171
-171
@@ -1,171 +1,171 @@
|
||||
// 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 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;
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 ReconfigureControllers(
|
||||
std::function<void()> callback_,
|
||||
const Core::Frontend::ControllerParameters& parameters) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowReconfigureControllers(
|
||||
const Core::Frontend::ControllerParameters& parameters) const;
|
||||
|
||||
private:
|
||||
void MainWindowReconfigureFinished();
|
||||
|
||||
mutable std::function<void()> callback;
|
||||
};
|
||||
// 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 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;
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 ReconfigureControllers(
|
||||
std::function<void()> callback_,
|
||||
const Core::Frontend::ControllerParameters& parameters) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowReconfigureControllers(
|
||||
const Core::Frontend::ControllerParameters& parameters) const;
|
||||
|
||||
private:
|
||||
void MainWindowReconfigureFinished();
|
||||
|
||||
mutable std::function<void()> callback;
|
||||
};
|
||||
|
||||
+2653
-2653
File diff suppressed because it is too large
Load Diff
@@ -1,59 +1,59 @@
|
||||
// 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(&parent, &GMainWindow::ErrorDisplayFinished, this,
|
||||
&QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QtErrorDisplay::~QtErrorDisplay() = default;
|
||||
|
||||
void QtErrorDisplay::ShowError(Result error, std::function<void()> finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\nPlease try again or contact the developer of the software."));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
|
||||
std::function<void()> 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.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the "
|
||||
"software.")
|
||||
.arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
|
||||
.arg(date_time.toString(QStringLiteral("h:mm:ss A"))));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text,
|
||||
std::string fullscreen_text,
|
||||
std::function<void()> finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\n\n%1\n\n%2")
|
||||
.arg(QString::fromStdString(dialog_text))
|
||||
.arg(QString::fromStdString(fullscreen_text)));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::MainWindowFinishedError() {
|
||||
callback();
|
||||
}
|
||||
// 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(&parent, &GMainWindow::ErrorDisplayFinished, this,
|
||||
&QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QtErrorDisplay::~QtErrorDisplay() = default;
|
||||
|
||||
void QtErrorDisplay::ShowError(Result error, std::function<void()> finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\nPlease try again or contact the developer of the software."));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
|
||||
std::function<void()> 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.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the "
|
||||
"software.")
|
||||
.arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
|
||||
.arg(date_time.toString(QStringLiteral("h:mm:ss A"))));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text,
|
||||
std::string fullscreen_text,
|
||||
std::function<void()> finished) const {
|
||||
callback = std::move(finished);
|
||||
emit MainWindowDisplayError(
|
||||
tr("Error Code: %1-%2 (0x%3)")
|
||||
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
|
||||
.arg(error.raw, 8, 16, QChar::fromLatin1('0')),
|
||||
tr("An error has occurred.\n\n%1\n\n%2")
|
||||
.arg(QString::fromStdString(dialog_text))
|
||||
.arg(QString::fromStdString(fullscreen_text)));
|
||||
}
|
||||
|
||||
void QtErrorDisplay::MainWindowFinishedError() {
|
||||
callback();
|
||||
}
|
||||
|
||||
+32
-32
@@ -1,32 +1,32 @@
|
||||
// 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 ShowError(Result error, std::function<void()> finished) const override;
|
||||
void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
|
||||
std::function<void()> finished) const override;
|
||||
void ShowCustomErrorText(Result error, std::string dialog_text, std::string fullscreen_text,
|
||||
std::function<void()> finished) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowDisplayError(QString error_code, QString error_text) const;
|
||||
|
||||
private:
|
||||
void MainWindowFinishedError();
|
||||
|
||||
mutable std::function<void()> callback;
|
||||
};
|
||||
// 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 ShowError(Result error, std::function<void()> finished) const override;
|
||||
void ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
|
||||
std::function<void()> finished) const override;
|
||||
void ShowCustomErrorText(Result error, std::string dialog_text, std::string fullscreen_text,
|
||||
std::function<void()> finished) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowDisplayError(QString error_code, QString error_text) const;
|
||||
|
||||
private:
|
||||
void MainWindowFinishedError();
|
||||
|
||||
mutable std::function<void()> callback;
|
||||
};
|
||||
|
||||
@@ -1,174 +1,174 @@
|
||||
// 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 "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::HID::HIDCore& hid_core, QWidget* parent)
|
||||
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||
outer_layout = new QVBoxLayout;
|
||||
|
||||
instruction_label = new QLabel(tr("Select a user:"));
|
||||
|
||||
scroll_area = new QScrollArea;
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
|
||||
|
||||
outer_layout->addWidget(instruction_label);
|
||||
outer_layout->addWidget(scroll_area);
|
||||
outer_layout->addWidget(buttons);
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
controller_navigation = new ControllerNavigation(hid_core, 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(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(tr("Profile Selector"));
|
||||
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();
|
||||
}
|
||||
|
||||
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
|
||||
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
|
||||
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
|
||||
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
|
||||
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QtProfileSelector::~QtProfileSelector() = default;
|
||||
|
||||
void QtProfileSelector::SelectProfile(
|
||||
std::function<void(std::optional<Common::UUID>)> callback_) const {
|
||||
callback = std::move(callback_);
|
||||
emit MainWindowSelectProfile();
|
||||
}
|
||||
|
||||
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
|
||||
callback(uuid);
|
||||
}
|
||||
// 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 "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::HID::HIDCore& hid_core, QWidget* parent)
|
||||
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||
outer_layout = new QVBoxLayout;
|
||||
|
||||
instruction_label = new QLabel(tr("Select a user:"));
|
||||
|
||||
scroll_area = new QScrollArea;
|
||||
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
|
||||
|
||||
outer_layout->addWidget(instruction_label);
|
||||
outer_layout->addWidget(scroll_area);
|
||||
outer_layout->addWidget(buttons);
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
controller_navigation = new ControllerNavigation(hid_core, 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(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(tr("Profile Selector"));
|
||||
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();
|
||||
}
|
||||
|
||||
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
|
||||
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
|
||||
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
|
||||
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
|
||||
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
QtProfileSelector::~QtProfileSelector() = default;
|
||||
|
||||
void QtProfileSelector::SelectProfile(
|
||||
std::function<void(std::optional<Common::UUID>)> callback_) const {
|
||||
callback = std::move(callback_);
|
||||
emit MainWindowSelectProfile();
|
||||
}
|
||||
|
||||
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
|
||||
callback(uuid);
|
||||
}
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
// 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"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
class ControllerNavigation;
|
||||
class GMainWindow;
|
||||
class QDialogButtonBox;
|
||||
class QGraphicsScene;
|
||||
class QLabel;
|
||||
class QScrollArea;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
} // namespace Core::HID
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);
|
||||
~QtProfileSelectionDialog() override;
|
||||
|
||||
int exec() override;
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
|
||||
int GetIndex() const;
|
||||
|
||||
private:
|
||||
void SelectUser(const QModelIndex& index);
|
||||
|
||||
int user_index = 0;
|
||||
|
||||
QVBoxLayout* layout;
|
||||
QTreeView* tree_view;
|
||||
QStandardItemModel* item_model;
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
QVBoxLayout* outer_layout;
|
||||
QLabel* instruction_label;
|
||||
QScrollArea* scroll_area;
|
||||
QDialogButtonBox* buttons;
|
||||
|
||||
std::unique_ptr<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 SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowSelectProfile() const;
|
||||
|
||||
private:
|
||||
void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
|
||||
|
||||
mutable std::function<void(std::optional<Common::UUID>)> callback;
|
||||
};
|
||||
// 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"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
class ControllerNavigation;
|
||||
class GMainWindow;
|
||||
class QDialogButtonBox;
|
||||
class QGraphicsScene;
|
||||
class QLabel;
|
||||
class QScrollArea;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
} // namespace Core::HID
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);
|
||||
~QtProfileSelectionDialog() override;
|
||||
|
||||
int exec() override;
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
|
||||
int GetIndex() const;
|
||||
|
||||
private:
|
||||
void SelectUser(const QModelIndex& index);
|
||||
|
||||
int user_index = 0;
|
||||
|
||||
QVBoxLayout* layout;
|
||||
QTreeView* tree_view;
|
||||
QStandardItemModel* item_model;
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
QVBoxLayout* outer_layout;
|
||||
QLabel* instruction_label;
|
||||
QScrollArea* scroll_area;
|
||||
QDialogButtonBox* buttons;
|
||||
|
||||
std::unique_ptr<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 SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowSelectProfile() const;
|
||||
|
||||
private:
|
||||
void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
|
||||
|
||||
mutable std::function<void(std::optional<Common::UUID>)> callback;
|
||||
};
|
||||
|
||||
+1677
-1677
File diff suppressed because it is too large
Load Diff
@@ -1,286 +1,286 @@
|
||||
// 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::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message);
|
||||
|
||||
void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos,
|
||||
QSize size);
|
||||
|
||||
void HideInlineKeyboard();
|
||||
|
||||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters);
|
||||
|
||||
void ExitKeyboard();
|
||||
|
||||
signals:
|
||||
void SubmitNormalText(Service::AM::Applets::SwkbdResult result, std::u16string submitted_text,
|
||||
bool confirmed = false) const;
|
||||
|
||||
void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
|
||||
std::u16string submitted_text, s32 cursor_position) const;
|
||||
|
||||
public slots:
|
||||
void open() override;
|
||||
void reject() override;
|
||||
|
||||
protected:
|
||||
/// We override the keyPressEvent for inputting text into the inline software keyboard.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
enum class Direction {
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
};
|
||||
|
||||
enum class BottomOSKIndex {
|
||||
LowerCase,
|
||||
UpperCase,
|
||||
NumberPad,
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves and resizes the window to a specified position and size.
|
||||
*
|
||||
* @param pos Top-left window position
|
||||
* @param size Window size
|
||||
*/
|
||||
void MoveAndResizeWindow(QPoint pos, QSize size);
|
||||
|
||||
/**
|
||||
* Rescales all keyboard elements to account for High DPI displays.
|
||||
*
|
||||
* @param width Window width
|
||||
* @param height Window height
|
||||
* @param dpi_scale Display scaling factor
|
||||
*/
|
||||
void RescaleKeyboardElements(float width, float height, float dpi_scale);
|
||||
|
||||
/// Sets the keyboard type based on initialize_parameters.
|
||||
void SetKeyboardType();
|
||||
|
||||
/// Sets the password mode based on initialize_parameters.
|
||||
void SetPasswordMode();
|
||||
|
||||
/// Sets the text draw type based on initialize_parameters.
|
||||
void SetTextDrawType();
|
||||
|
||||
/// Sets the controller image at the bottom left of the software keyboard.
|
||||
void SetControllerImage();
|
||||
|
||||
/// Disables buttons based on initialize_parameters.
|
||||
void DisableKeyboardButtons();
|
||||
|
||||
/// Changes whether the backspace or/and ok buttons should be enabled or disabled.
|
||||
void SetBackspaceOkEnabled();
|
||||
|
||||
/**
|
||||
* Validates the input text sent in based on the parameters in initialize_parameters.
|
||||
*
|
||||
* @param input_text Input text
|
||||
*
|
||||
* @returns True if the input text is valid, false otherwise.
|
||||
*/
|
||||
bool ValidateInputText(const QString& input_text);
|
||||
|
||||
/// Switches between LowerCase and UpperCase (Shift and Caps Lock)
|
||||
void ChangeBottomOSKIndex();
|
||||
|
||||
/// Processes a keyboard button click from the UI as normal keyboard input.
|
||||
void NormalKeyboardButtonClicked(QPushButton* button);
|
||||
|
||||
/// Processes a keyboard button click from the UI as inline keyboard input.
|
||||
void InlineKeyboardButtonClicked(QPushButton* button);
|
||||
|
||||
/**
|
||||
* Inserts a string of arbitrary length into the current_text at the current cursor position.
|
||||
* This is only used for the inline software keyboard.
|
||||
*/
|
||||
void InlineTextInsertString(std::u16string_view string);
|
||||
|
||||
/// Setup the mouse hover workaround for "focusing" buttons. This should only be called once.
|
||||
void SetupMouseHover();
|
||||
|
||||
/**
|
||||
* Handles button presses and converts them into keyboard input.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <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 InitializeKeyboard(
|
||||
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
|
||||
std::function<void(Service::AM::Applets::SwkbdResult, std::u16string, bool)>
|
||||
submit_normal_callback_,
|
||||
std::function<void(Service::AM::Applets::SwkbdReplyType, std::u16string, s32)>
|
||||
submit_inline_callback_) override;
|
||||
|
||||
void ShowNormalKeyboard() const override;
|
||||
|
||||
void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message) const override;
|
||||
|
||||
void ShowInlineKeyboard(
|
||||
Core::Frontend::InlineAppearParameters appear_parameters) const override;
|
||||
|
||||
void HideInlineKeyboard() const override;
|
||||
|
||||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override;
|
||||
|
||||
void ExitKeyboard() const override;
|
||||
|
||||
signals:
|
||||
void MainWindowInitializeKeyboard(
|
||||
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const;
|
||||
|
||||
void MainWindowShowNormalKeyboard() const;
|
||||
|
||||
void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message) const;
|
||||
|
||||
void MainWindowShowInlineKeyboard(
|
||||
Core::Frontend::InlineAppearParameters appear_parameters) const;
|
||||
|
||||
void MainWindowHideInlineKeyboard() const;
|
||||
|
||||
void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const;
|
||||
|
||||
void MainWindowExitKeyboard() const;
|
||||
|
||||
private:
|
||||
void SubmitNormalText(Service::AM::Applets::SwkbdResult result, std::u16string submitted_text,
|
||||
bool confirmed) const;
|
||||
|
||||
void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
|
||||
std::u16string submitted_text, s32 cursor_position) const;
|
||||
|
||||
mutable std::function<void(Service::AM::Applets::SwkbdResult, std::u16string, bool)>
|
||||
submit_normal_callback;
|
||||
mutable std::function<void(Service::AM::Applets::SwkbdReplyType, std::u16string, s32)>
|
||||
submit_inline_callback;
|
||||
};
|
||||
// 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::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message);
|
||||
|
||||
void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos,
|
||||
QSize size);
|
||||
|
||||
void HideInlineKeyboard();
|
||||
|
||||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters);
|
||||
|
||||
void ExitKeyboard();
|
||||
|
||||
signals:
|
||||
void SubmitNormalText(Service::AM::Applets::SwkbdResult result, std::u16string submitted_text,
|
||||
bool confirmed = false) const;
|
||||
|
||||
void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
|
||||
std::u16string submitted_text, s32 cursor_position) const;
|
||||
|
||||
public slots:
|
||||
void open() override;
|
||||
void reject() override;
|
||||
|
||||
protected:
|
||||
/// We override the keyPressEvent for inputting text into the inline software keyboard.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
enum class Direction {
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
};
|
||||
|
||||
enum class BottomOSKIndex {
|
||||
LowerCase,
|
||||
UpperCase,
|
||||
NumberPad,
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves and resizes the window to a specified position and size.
|
||||
*
|
||||
* @param pos Top-left window position
|
||||
* @param size Window size
|
||||
*/
|
||||
void MoveAndResizeWindow(QPoint pos, QSize size);
|
||||
|
||||
/**
|
||||
* Rescales all keyboard elements to account for High DPI displays.
|
||||
*
|
||||
* @param width Window width
|
||||
* @param height Window height
|
||||
* @param dpi_scale Display scaling factor
|
||||
*/
|
||||
void RescaleKeyboardElements(float width, float height, float dpi_scale);
|
||||
|
||||
/// Sets the keyboard type based on initialize_parameters.
|
||||
void SetKeyboardType();
|
||||
|
||||
/// Sets the password mode based on initialize_parameters.
|
||||
void SetPasswordMode();
|
||||
|
||||
/// Sets the text draw type based on initialize_parameters.
|
||||
void SetTextDrawType();
|
||||
|
||||
/// Sets the controller image at the bottom left of the software keyboard.
|
||||
void SetControllerImage();
|
||||
|
||||
/// Disables buttons based on initialize_parameters.
|
||||
void DisableKeyboardButtons();
|
||||
|
||||
/// Changes whether the backspace or/and ok buttons should be enabled or disabled.
|
||||
void SetBackspaceOkEnabled();
|
||||
|
||||
/**
|
||||
* Validates the input text sent in based on the parameters in initialize_parameters.
|
||||
*
|
||||
* @param input_text Input text
|
||||
*
|
||||
* @returns True if the input text is valid, false otherwise.
|
||||
*/
|
||||
bool ValidateInputText(const QString& input_text);
|
||||
|
||||
/// Switches between LowerCase and UpperCase (Shift and Caps Lock)
|
||||
void ChangeBottomOSKIndex();
|
||||
|
||||
/// Processes a keyboard button click from the UI as normal keyboard input.
|
||||
void NormalKeyboardButtonClicked(QPushButton* button);
|
||||
|
||||
/// Processes a keyboard button click from the UI as inline keyboard input.
|
||||
void InlineKeyboardButtonClicked(QPushButton* button);
|
||||
|
||||
/**
|
||||
* Inserts a string of arbitrary length into the current_text at the current cursor position.
|
||||
* This is only used for the inline software keyboard.
|
||||
*/
|
||||
void InlineTextInsertString(std::u16string_view string);
|
||||
|
||||
/// Setup the mouse hover workaround for "focusing" buttons. This should only be called once.
|
||||
void SetupMouseHover();
|
||||
|
||||
/**
|
||||
* Handles button presses and converts them into keyboard input.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <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 InitializeKeyboard(
|
||||
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters,
|
||||
std::function<void(Service::AM::Applets::SwkbdResult, std::u16string, bool)>
|
||||
submit_normal_callback_,
|
||||
std::function<void(Service::AM::Applets::SwkbdReplyType, std::u16string, s32)>
|
||||
submit_inline_callback_) override;
|
||||
|
||||
void ShowNormalKeyboard() const override;
|
||||
|
||||
void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message) const override;
|
||||
|
||||
void ShowInlineKeyboard(
|
||||
Core::Frontend::InlineAppearParameters appear_parameters) const override;
|
||||
|
||||
void HideInlineKeyboard() const override;
|
||||
|
||||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override;
|
||||
|
||||
void ExitKeyboard() const override;
|
||||
|
||||
signals:
|
||||
void MainWindowInitializeKeyboard(
|
||||
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const;
|
||||
|
||||
void MainWindowShowNormalKeyboard() const;
|
||||
|
||||
void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result,
|
||||
std::u16string text_check_message) const;
|
||||
|
||||
void MainWindowShowInlineKeyboard(
|
||||
Core::Frontend::InlineAppearParameters appear_parameters) const;
|
||||
|
||||
void MainWindowHideInlineKeyboard() const;
|
||||
|
||||
void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const;
|
||||
|
||||
void MainWindowExitKeyboard() const;
|
||||
|
||||
private:
|
||||
void SubmitNormalText(Service::AM::Applets::SwkbdResult result, std::u16string submitted_text,
|
||||
bool confirmed) const;
|
||||
|
||||
void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type,
|
||||
std::u16string submitted_text, s32 cursor_position) const;
|
||||
|
||||
mutable std::function<void(Service::AM::Applets::SwkbdResult, std::u16string, bool)>
|
||||
submit_normal_callback;
|
||||
mutable std::function<void(Service::AM::Applets::SwkbdReplyType, std::u16string, s32)>
|
||||
submit_inline_callback;
|
||||
};
|
||||
|
||||
+3541
-3541
File diff suppressed because it is too large
Load Diff
+441
-441
@@ -1,441 +1,441 @@
|
||||
// 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 "core/hid/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::Applets::WebExitReason::WindowClosed);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtNXWebEngineView::~QtNXWebEngineView() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = true;
|
||||
|
||||
LoadExtractedFonts();
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
|
||||
QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = false;
|
||||
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
|
||||
const QString user_agent_str = [user_agent] {
|
||||
switch (user_agent) {
|
||||
case UserAgent::WebApplet:
|
||||
default:
|
||||
return QStringLiteral("WebApplet");
|
||||
case UserAgent::ShopN:
|
||||
return QStringLiteral("ShopN");
|
||||
case UserAgent::LoginApplet:
|
||||
return QStringLiteral("LoginApplet");
|
||||
case UserAgent::ShareApplet:
|
||||
return QStringLiteral("ShareApplet");
|
||||
case UserAgent::LobbyApplet:
|
||||
return QStringLiteral("LobbyApplet");
|
||||
case UserAgent::WifiWebAuthApplet:
|
||||
return QStringLiteral("WifiWebAuthApplet");
|
||||
}
|
||||
}();
|
||||
|
||||
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
|
||||
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
|
||||
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
|
||||
.arg(user_agent_str));
|
||||
}
|
||||
|
||||
bool QtNXWebEngineView::IsFinished() const {
|
||||
return finished;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetFinished(bool finished_) {
|
||||
finished = finished_;
|
||||
}
|
||||
|
||||
Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
|
||||
return exit_reason;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
|
||||
exit_reason = exit_reason_;
|
||||
}
|
||||
|
||||
const std::string& QtNXWebEngineView::GetLastURL() const {
|
||||
return last_url;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
|
||||
last_url = std::move(last_url_);
|
||||
}
|
||||
|
||||
QString QtNXWebEngineView::GetCurrentURL() const {
|
||||
return url_interceptor->GetRequestedURL().toString();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::hide() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
|
||||
QWidget::hide();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
template <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(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
|
||||
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
|
||||
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
|
||||
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtWebBrowser::~QtWebBrowser() = default;
|
||||
|
||||
void QtWebBrowser::OpenLocalWebPage(
|
||||
const std::string& local_url, std::function<void()> extract_romfs_callback_,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
||||
extract_romfs_callback = std::move(extract_romfs_callback_);
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = local_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(local_url, "", true);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::OpenExternalWebPage(
|
||||
const std::string& external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = external_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(external_url, "", false);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
|
||||
extract_romfs_callback();
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
|
||||
std::string last_url) {
|
||||
callback(exit_reason, last_url);
|
||||
}
|
||||
// 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 "core/hid/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::Applets::WebExitReason::WindowClosed);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtNXWebEngineView::~QtNXWebEngineView() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = true;
|
||||
|
||||
LoadExtractedFonts();
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
|
||||
QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
|
||||
const std::string& additional_args) {
|
||||
is_local = false;
|
||||
|
||||
FocusFirstLinkElement();
|
||||
SetUserAgent(UserAgent::WebApplet);
|
||||
SetFinished(false);
|
||||
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
||||
SetLastURL("http://localhost/");
|
||||
StartInputThread();
|
||||
|
||||
load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
|
||||
const QString user_agent_str = [user_agent] {
|
||||
switch (user_agent) {
|
||||
case UserAgent::WebApplet:
|
||||
default:
|
||||
return QStringLiteral("WebApplet");
|
||||
case UserAgent::ShopN:
|
||||
return QStringLiteral("ShopN");
|
||||
case UserAgent::LoginApplet:
|
||||
return QStringLiteral("LoginApplet");
|
||||
case UserAgent::ShareApplet:
|
||||
return QStringLiteral("ShareApplet");
|
||||
case UserAgent::LobbyApplet:
|
||||
return QStringLiteral("LobbyApplet");
|
||||
case UserAgent::WifiWebAuthApplet:
|
||||
return QStringLiteral("WifiWebAuthApplet");
|
||||
}
|
||||
}();
|
||||
|
||||
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
|
||||
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
|
||||
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
|
||||
.arg(user_agent_str));
|
||||
}
|
||||
|
||||
bool QtNXWebEngineView::IsFinished() const {
|
||||
return finished;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetFinished(bool finished_) {
|
||||
finished = finished_;
|
||||
}
|
||||
|
||||
Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
|
||||
return exit_reason;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
|
||||
exit_reason = exit_reason_;
|
||||
}
|
||||
|
||||
const std::string& QtNXWebEngineView::GetLastURL() const {
|
||||
return last_url;
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
|
||||
last_url = std::move(last_url_);
|
||||
}
|
||||
|
||||
QString QtNXWebEngineView::GetCurrentURL() const {
|
||||
return url_interceptor->GetRequestedURL().toString();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::hide() {
|
||||
SetFinished(true);
|
||||
StopInputThread();
|
||||
|
||||
QWidget::hide();
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (is_local) {
|
||||
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
}
|
||||
|
||||
template <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(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
|
||||
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
|
||||
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
|
||||
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QtWebBrowser::~QtWebBrowser() = default;
|
||||
|
||||
void QtWebBrowser::OpenLocalWebPage(
|
||||
const std::string& local_url, std::function<void()> extract_romfs_callback_,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
||||
extract_romfs_callback = std::move(extract_romfs_callback_);
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = local_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(local_url, "", true);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::OpenExternalWebPage(
|
||||
const std::string& external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
||||
callback = std::move(callback_);
|
||||
|
||||
const auto index = external_url.find('?');
|
||||
|
||||
if (index == std::string::npos) {
|
||||
emit MainWindowOpenWebPage(external_url, "", false);
|
||||
} else {
|
||||
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
|
||||
extract_romfs_callback();
|
||||
}
|
||||
|
||||
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
|
||||
std::string last_url) {
|
||||
callback(exit_reason, last_url);
|
||||
}
|
||||
|
||||
+221
-221
@@ -1,221 +1,221 @@
|
||||
// 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::Applets::WebExitReason GetExitReason() const;
|
||||
void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_);
|
||||
|
||||
[[nodiscard]] const std::string& GetLastURL() const;
|
||||
void SetLastURL(std::string last_url_);
|
||||
|
||||
/**
|
||||
* This gets the current URL that has been requested by the webpage.
|
||||
* This only applies to the main frame. Sub frames and other resources are ignored.
|
||||
*
|
||||
* @return Currently requested URL
|
||||
*/
|
||||
[[nodiscard]] QString GetCurrentURL() const;
|
||||
|
||||
public slots:
|
||||
void hide();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handles button presses to execute functions assigned in yuzu_key_callbacks.
|
||||
* yuzu_key_callbacks contains specialized functions for the buttons in the window footer
|
||||
* that can be overriden by games to achieve desired functionality.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
|
||||
*/
|
||||
template <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::Applets::WebExitReason exit_reason{
|
||||
Service::AM::Applets::WebExitReason::EndButtonPressed};
|
||||
|
||||
std::string last_url{"http://localhost/"};
|
||||
|
||||
bool is_local{};
|
||||
|
||||
QWebEngineProfile* default_profile;
|
||||
QWebEngineSettings* global_settings;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtWebBrowser(GMainWindow& parent);
|
||||
~QtWebBrowser() override;
|
||||
|
||||
void OpenLocalWebPage(const std::string& local_url,
|
||||
std::function<void()> extract_romfs_callback_,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback_) const override;
|
||||
|
||||
void OpenExternalWebPage(const std::string& external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback_) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args,
|
||||
bool is_local) const;
|
||||
|
||||
private:
|
||||
void MainWindowExtractOfflineRomFS();
|
||||
|
||||
void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
|
||||
std::string last_url);
|
||||
|
||||
mutable std::function<void()> extract_romfs_callback;
|
||||
|
||||
mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback;
|
||||
};
|
||||
// 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::Applets::WebExitReason GetExitReason() const;
|
||||
void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_);
|
||||
|
||||
[[nodiscard]] const std::string& GetLastURL() const;
|
||||
void SetLastURL(std::string last_url_);
|
||||
|
||||
/**
|
||||
* This gets the current URL that has been requested by the webpage.
|
||||
* This only applies to the main frame. Sub frames and other resources are ignored.
|
||||
*
|
||||
* @return Currently requested URL
|
||||
*/
|
||||
[[nodiscard]] QString GetCurrentURL() const;
|
||||
|
||||
public slots:
|
||||
void hide();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handles button presses to execute functions assigned in yuzu_key_callbacks.
|
||||
* yuzu_key_callbacks contains specialized functions for the buttons in the window footer
|
||||
* that can be overriden by games to achieve desired functionality.
|
||||
*
|
||||
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
|
||||
*/
|
||||
template <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::Applets::WebExitReason exit_reason{
|
||||
Service::AM::Applets::WebExitReason::EndButtonPressed};
|
||||
|
||||
std::string last_url{"http://localhost/"};
|
||||
|
||||
bool is_local{};
|
||||
|
||||
QWebEngineProfile* default_profile;
|
||||
QWebEngineSettings* global_settings;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtWebBrowser(GMainWindow& parent);
|
||||
~QtWebBrowser() override;
|
||||
|
||||
void OpenLocalWebPage(const std::string& local_url,
|
||||
std::function<void()> extract_romfs_callback_,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback_) const override;
|
||||
|
||||
void OpenExternalWebPage(const std::string& external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback_) const override;
|
||||
|
||||
signals:
|
||||
void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args,
|
||||
bool is_local) const;
|
||||
|
||||
private:
|
||||
void MainWindowExtractOfflineRomFS();
|
||||
|
||||
void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
|
||||
std::string last_url);
|
||||
|
||||
mutable std::function<void()> extract_romfs_callback;
|
||||
|
||||
mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback;
|
||||
};
|
||||
|
||||
@@ -1,198 +1,198 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr char NX_FONT_CSS[] = R"(
|
||||
(function() {
|
||||
css = document.createElement('style');
|
||||
css.type = 'text/css';
|
||||
css.id = 'nx_font';
|
||||
css.innerText = `
|
||||
/* FontStandard */
|
||||
@font-face {
|
||||
font-family: 'FontStandard';
|
||||
src: url('%1') format('truetype');
|
||||
}
|
||||
|
||||
/* FontChineseSimplified */
|
||||
@font-face {
|
||||
font-family: 'FontChineseSimplified';
|
||||
src: url('%2') format('truetype');
|
||||
}
|
||||
|
||||
/* FontExtendedChineseSimplified */
|
||||
@font-face {
|
||||
font-family: 'FontExtendedChineseSimplified';
|
||||
src: url('%3') format('truetype');
|
||||
}
|
||||
|
||||
/* FontChineseTraditional */
|
||||
@font-face {
|
||||
font-family: 'FontChineseTraditional';
|
||||
src: url('%4') format('truetype');
|
||||
}
|
||||
|
||||
/* FontKorean */
|
||||
@font-face {
|
||||
font-family: 'FontKorean';
|
||||
src: url('%5') format('truetype');
|
||||
}
|
||||
|
||||
/* FontNintendoExtended */
|
||||
@font-face {
|
||||
font-family: 'NintendoExt003';
|
||||
src: url('%6') format('truetype');
|
||||
}
|
||||
|
||||
/* FontNintendoExtended2 */
|
||||
@font-face {
|
||||
font-family: 'NintendoExt003';
|
||||
src: url('%7') format('truetype');
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(css);
|
||||
})();
|
||||
)";
|
||||
|
||||
constexpr char LOAD_NX_FONT[] = R"(
|
||||
(function() {
|
||||
var elements = document.querySelectorAll("*");
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var style = window.getComputedStyle(elements[i], null);
|
||||
if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") ||
|
||||
style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) {
|
||||
elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
|
||||
} else {
|
||||
elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
|
||||
}
|
||||
}
|
||||
})();
|
||||
)";
|
||||
|
||||
constexpr char FOCUS_LINK_ELEMENT_SCRIPT[] = R"(
|
||||
if (document.getElementsByTagName("a").length > 0) {
|
||||
document.getElementsByTagName("a")[0].focus();
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr char GAMEPAD_SCRIPT[] = R"(
|
||||
window.addEventListener("gamepadconnected", function(e) {
|
||||
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
|
||||
e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", function(e) {
|
||||
console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id);
|
||||
});
|
||||
)";
|
||||
|
||||
constexpr char WINDOW_NX_SCRIPT[] = R"(
|
||||
var end_applet = false;
|
||||
var yuzu_key_callbacks = [];
|
||||
|
||||
(function() {
|
||||
class WindowNX {
|
||||
constructor() {
|
||||
yuzu_key_callbacks[1] = function() { window.history.back(); };
|
||||
yuzu_key_callbacks[2] = function() { window.nx.endApplet(); };
|
||||
}
|
||||
|
||||
addEventListener(type, listener, options) {
|
||||
console.log("nx.addEventListener called, type=%s", type);
|
||||
|
||||
window.addEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
endApplet() {
|
||||
console.log("nx.endApplet called");
|
||||
|
||||
end_applet = true;
|
||||
}
|
||||
|
||||
playSystemSe(system_se) {
|
||||
console.log("nx.playSystemSe is not implemented, system_se=%s", system_se);
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
console.log("nx.sendMessage is not implemented, message=%s", message);
|
||||
}
|
||||
|
||||
setCursorScrollSpeed(scroll_speed) {
|
||||
console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed);
|
||||
}
|
||||
}
|
||||
|
||||
class WindowNXFooter {
|
||||
setAssign(key, label, func, option) {
|
||||
console.log("nx.footer.setAssign called, key=%s", key);
|
||||
|
||||
switch (key) {
|
||||
case "A":
|
||||
yuzu_key_callbacks[0] = func;
|
||||
break;
|
||||
case "B":
|
||||
yuzu_key_callbacks[1] = func;
|
||||
break;
|
||||
case "X":
|
||||
yuzu_key_callbacks[2] = func;
|
||||
break;
|
||||
case "Y":
|
||||
yuzu_key_callbacks[3] = func;
|
||||
break;
|
||||
case "L":
|
||||
yuzu_key_callbacks[6] = func;
|
||||
break;
|
||||
case "R":
|
||||
yuzu_key_callbacks[7] = func;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setFixed(kind) {
|
||||
console.log("nx.footer.setFixed is not implemented, kind=%s", kind);
|
||||
}
|
||||
|
||||
unsetAssign(key) {
|
||||
console.log("nx.footer.unsetAssign called, key=%s", key);
|
||||
|
||||
switch (key) {
|
||||
case "A":
|
||||
yuzu_key_callbacks[0] = function() {};
|
||||
break;
|
||||
case "B":
|
||||
yuzu_key_callbacks[1] = function() {};
|
||||
break;
|
||||
case "X":
|
||||
yuzu_key_callbacks[2] = function() {};
|
||||
break;
|
||||
case "Y":
|
||||
yuzu_key_callbacks[3] = function() {};
|
||||
break;
|
||||
case "L":
|
||||
yuzu_key_callbacks[6] = function() {};
|
||||
break;
|
||||
case "R":
|
||||
yuzu_key_callbacks[7] = function() {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WindowNXPlayReport {
|
||||
incrementCounter(counter_id) {
|
||||
console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id);
|
||||
}
|
||||
|
||||
setCounterSetIdentifier(counter_id) {
|
||||
console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id);
|
||||
}
|
||||
}
|
||||
|
||||
window.nx = new WindowNX();
|
||||
window.nx.footer = new WindowNXFooter();
|
||||
window.nx.playReport = new WindowNXPlayReport();
|
||||
})();
|
||||
)";
|
||||
// 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