summaryrefslogtreecommitdiff
path: root/src/yuzu/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/main.cpp')
-rw-r--r--src/yuzu/main.cpp335
1 files changed, 299 insertions, 36 deletions
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 22c207a3a..ab403b3ac 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -8,11 +8,17 @@
#include <thread>
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
+#include "applets/web_browser.h"
+#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/frontend/scope_acquire_window_context.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -87,6 +93,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
#include "yuzu/ui_settings.h"
@@ -94,6 +101,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/discord_impl.h"
#endif
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+#endif
+
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -207,6 +222,28 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
+void GMainWindow::ProfileSelectorSelectProfile() {
+ QtProfileSelectionDialog dialog(this);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ if (!dialog.GetStatus()) {
+ emit ProfileSelectorFinishedSelection(std::nullopt);
+ return;
+ }
+
+ Service::Account::ProfileManager manager;
+ const auto uuid = manager.GetUser(dialog.GetIndex());
+ if (!uuid.has_value()) {
+ emit ProfileSelectorFinishedSelection(std::nullopt);
+ return;
+ }
+
+ emit ProfileSelectorFinishedSelection(uuid);
+}
+
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
@@ -228,6 +265,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog();
}
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ NXInputWebEngineView web_browser_view(this);
+
+ // Scope to contain the QProgressDialog for initalization
+ {
+ QProgressDialog progress(this);
+ progress.setMinimumDuration(200);
+ progress.setLabelText(tr("Loading Web Applet..."));
+ progress.setRange(0, 4);
+ progress.setValue(0);
+ progress.show();
+
+ auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); });
+
+ while (!future.isFinished())
+ QApplication::processEvents();
+
+ progress.setValue(1);
+
+ // Load the special shim script to handle input and exit.
+ QWebEngineScript nx_shim;
+ nx_shim.setSourceCode(GetNXShimInjectionScript());
+ nx_shim.setWorldId(QWebEngineScript::MainWorld);
+ nx_shim.setName("nx_inject.js");
+ nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
+ nx_shim.setRunsOnSubFrames(true);
+ web_browser_view.page()->profile()->scripts()->insert(nx_shim);
+
+ web_browser_view.load(
+ QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() +
+ QString::fromStdString(std::string(additional_args))));
+
+ progress.setValue(2);
+
+ render_window->hide();
+ web_browser_view.setFocus();
+
+ const auto& layout = render_window->GetFramebufferLayout();
+ web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
+ web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
+ web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
+ Layout::ScreenUndocked::Width);
+ web_browser_view.settings()->setAttribute(
+ QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+
+ web_browser_view.show();
+
+ progress.setValue(3);
+
+ QApplication::processEvents();
+
+ progress.setValue(4);
+ }
+
+ bool finished = false;
+ QAction* exit_action = new QAction(tr("Exit Web Applet"), this);
+ connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; });
+ ui.menubar->addAction(exit_action);
+
+ auto& npad =
+ Core::System::GetInstance()
+ .ServiceManager()
+ .GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
+ .arg(QString::fromStdString(std::to_string(key_code))));
+ };
+
+ bool running_exit_check = false;
+ while (!finished) {
+ QApplication::processEvents();
+
+ if (!running_exit_check) {
+ web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"),
+ [&](const QVariant& res) {
+ running_exit_check = false;
+ if (res.toBool())
+ finished = true;
+ });
+ running_exit_check = true;
+ }
+
+ const auto input = npad.GetAndResetPressState();
+ for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ if ((input & (1 << i)) != 0) {
+ LOG_DEBUG(Frontend, "firing input for button id={:02X}", i);
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("yuzu_key_callbacks[%1]();").arg(i));
+ }
+ }
+
+ if (input & 0x00888000) // RStick Down | LStick Down | DPad Down
+ fire_js_keypress(40); // Down Arrow Key
+ else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right
+ fire_js_keypress(39); // Right Arrow Key
+ else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up
+ fire_js_keypress(38); // Up Arrow Key
+ else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left
+ fire_js_keypress(37); // Left Arrow Key
+ else if (input & 0x00000001) // A Button
+ fire_js_keypress(13); // Enter Key
+ }
+
+ web_browser_view.hide();
+ render_window->show();
+ render_window->setFocus();
+ ui.menubar->removeAction(exit_action);
+
+ // Needed to update render window focus/show and remove menubar action
+ QApplication::processEvents();
+ emit WebBrowserFinishedBrowsing();
+}
+
+#else
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ QMessageBox::warning(
+ this, tr("Web Applet"),
+ tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot "
+ "properly display the game manual or web page requested."),
+ QMessageBox::Ok, QMessageBox::Ok);
+
+ LOG_INFO(Frontend,
+ "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at "
+ "'{}' with arguments '{}'!",
+ filename, additional_args);
+
+ emit WebBrowserFinishedBrowsing();
+}
+
+#endif
+
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
@@ -238,6 +413,17 @@ void GMainWindow::InitializeWidgets() {
game_list = new GameList(vfs, this);
ui.horizontalLayout->addWidget(game_list);
+ loading_screen = new LoadingScreen(this);
+ loading_screen->hide();
+ ui.horizontalLayout->addWidget(loading_screen);
+ connect(loading_screen, &LoadingScreen::Hidden, [&] {
+ loading_screen->Clear();
+ if (emulation_running) {
+ render_window->show();
+ render_window->setFocus();
+ }
+ });
+
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
@@ -334,6 +520,9 @@ void GMainWindow::InitializeHotkeys() {
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
Qt::ApplicationShortcut);
+ hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
+ QKeySequence(QKeySequence::Print));
+
hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@@ -393,6 +582,12 @@ void GMainWindow::InitializeHotkeys() {
OnLoadAmiibo();
}
});
+ connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
+ &QShortcut::activated, this, [&] {
+ if (emu_thread->IsRunning()) {
+ OnCaptureScreenshot();
+ }
+ });
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -441,6 +636,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
+ connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
+ &GMainWindow::OnGameListOpenPerGameProperties);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
@@ -488,6 +685,10 @@ void GMainWindow::ConnectMenuEvents() {
hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
+ // Movie
+ connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
+ &GMainWindow::OnCaptureScreenshot);
+
// Help
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
connect(ui.action_Rederive, &QAction::triggered, this,
@@ -547,13 +748,15 @@ bool GMainWindow::LoadROM(const QString& filename) {
ShutdownGame();
render_window->InitRenderTarget();
- render_window->MakeCurrent();
- if (!gladLoadGL()) {
- QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"),
- tr("Your GPU may not support OpenGL 4.3, or you do not "
- "have the latest graphics driver."));
- return false;
+ {
+ Core::Frontend::ScopeAcquireWindowContext acquire_context{*render_window};
+ if (!gladLoadGL()) {
+ QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"),
+ tr("Your GPU may not support OpenGL 4.3, or you do not "
+ "have the latest graphics driver."));
+ return false;
+ }
}
QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
@@ -571,7 +774,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
+ system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
+ system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -592,8 +797,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
"wiki</a>. This message will not be shown again."));
}
- render_window->DoneCurrent();
-
if (result != Core::System::ResultStatus::Success) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:
@@ -647,10 +850,26 @@ bool GMainWindow::LoadROM(const QString& filename) {
return true;
}
+void GMainWindow::SelectAndSetCurrentUser() {
+ QtProfileSelectionDialog dialog(this);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ if (dialog.GetStatus()) {
+ Settings::values.current_user = static_cast<s32>(dialog.GetIndex());
+ }
+}
+
void GMainWindow::BootGame(const QString& filename) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
+ if (UISettings::values.select_user_on_boot) {
+ SelectAndSetCurrentUser();
+ }
+
if (!LoadROM(filename))
return;
@@ -691,8 +910,8 @@ void GMainWindow::BootGame(const QString& filename) {
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
QString::fromStdString(title_name)));
- render_window->show();
- render_window->setFocus();
+ loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
+ loading_screen->show();
emulation_running = true;
if (ui.action_Fullscreen->isChecked()) {
@@ -724,7 +943,10 @@ void GMainWindow::ShutdownGame() {
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
+ ui.action_Capture_Screenshot->setEnabled(false);
render_window->hide();
+ loading_screen->hide();
+ loading_screen->Clear();
game_list->show();
game_list->setFilterFocus();
setWindowTitle(QString("yuzu %1| %2-%3")
@@ -786,31 +1008,25 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
- Service::Account::ProfileManager manager{};
- const auto user_ids = manager.GetAllUsers();
- QStringList list;
- for (const auto& user_id : user_ids) {
- if (user_id == Service::Account::UUID{})
- continue;
- Service::Account::ProfileBase base;
- if (!manager.GetProfileBase(user_id, base))
- continue;
-
- list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(base.username.data()), base.username.size())));
- }
+ const auto select_profile = [this]() -> s32 {
+ QtProfileSelectionDialog dialog(this);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
- bool ok = false;
- const auto index_string =
- QInputDialog::getItem(this, tr("Select User"),
- tr("Please select the user's save data you would like to open."),
- list, Settings::values.current_user, false, &ok);
- if (!ok)
- return;
+ if (!dialog.GetStatus()) {
+ return -1;
+ }
+
+ return dialog.GetIndex();
+ };
- const auto index = list.indexOf(index_string);
- ASSERT(index != -1 && index < 8);
+ const auto index = select_profile();
+ if (index == -1)
+ return;
+ Service::Account::ProfileManager manager;
const auto user_id = manager.GetUser(index);
ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
@@ -988,6 +1204,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
}
+void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
+ u64 title_id{};
+ const auto v_file = Core::GetGameFileFromPath(vfs, file);
+ const auto loader = Loader::GetLoader(v_file);
+ if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
+ QMessageBox::information(this, tr("Properties"),
+ tr("The game properties could not be loaded."));
+ return;
+ }
+
+ ConfigurePerGameGeneral dialog(this, title_id);
+ dialog.loadFromFile(v_file);
+ auto result = dialog.exec();
+ if (result == QDialog::Accepted) {
+ dialog.applyConfiguration();
+
+ const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
+ if (reload) {
+ game_list->PopulateAsync(UISettings::values.gamedir,
+ UISettings::values.gamedir_deepscan);
+ }
+
+ config->Save();
+ }
+}
+
void GMainWindow::OnMenuLoadFile() {
const QString extensions =
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
@@ -1248,6 +1490,7 @@ void GMainWindow::OnStartGame() {
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
+ qRegisterMetaType<std::string_view>("std::string_view");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
@@ -1261,6 +1504,7 @@ void GMainWindow::OnStartGame() {
discord_rpc->Update();
ui.action_Load_Amiibo->setEnabled(true);
+ ui.action_Capture_Screenshot->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1269,12 +1513,17 @@ void GMainWindow::OnPauseGame() {
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
+ ui.action_Capture_Screenshot->setEnabled(false);
}
void GMainWindow::OnStopGame() {
ShutdownGame();
}
+void GMainWindow::OnLoadComplete() {
+ loading_screen->OnLoadComplete();
+}
+
void GMainWindow::OnMenuReportCompatibility() {
if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
CompatDB compatdb{this};
@@ -1431,6 +1680,18 @@ void GMainWindow::OnToggleFilterBar() {
}
}
+void GMainWindow::OnCaptureScreenshot() {
+ OnPauseGame();
+ const QString path =
+ QFileDialog::getSaveFileName(this, tr("Capture Screenshot"),
+ UISettings::values.screenshot_path, tr("PNG Image (*.png)"));
+ if (!path.isEmpty()) {
+ UISettings::values.screenshot_path = QFileInfo(path).path();
+ render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
+ }
+ OnStartGame();
+}
+
void GMainWindow::UpdateStatusBar() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();
@@ -1529,9 +1790,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
this, tr("Confirm Key Rederivation"),
tr("You are about to force rederive all of your keys. \nIf you do not know what this "
"means or what you are doing, \nthis is a potentially destructive action. \nPlease "
- "make "
- "sure this is what you want \nand optionally make backups.\n\nThis will delete your "
- "autogenerated key files and re-run the key derivation module."),
+ "make sure this is what you want \nand optionally make backups.\n\nThis will delete "
+ "your autogenerated key files and re-run the key derivation module."),
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
if (res == QMessageBox::Cancel)
@@ -1576,7 +1836,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
errors +
tr("<br><br>You can get all of these and dump all of your games easily by "
"following <a href='https://yuzu-emu.org/help/quickstart/'>the "
- "quickstart guide</a>. Alternatively, you can use another method of dumping "
+ "quickstart guide</a>. Alternatively, you can use another method of dumping"
"to obtain all of your keys."));
}
@@ -1783,6 +2043,9 @@ int main(int argc, char* argv[]) {
GMainWindow main_window;
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
+
+ Settings::LogSettings();
+
int result = app.exec();
detached_tasks.WaitForAllTasks();
return result;