rpcsx/rpcs3/rpcs3qt/gui_application.cpp
Eladash a5d74c5e96 GUI: Improve missing firmware handling
* Install PS3UPDAT.PUP at the spot when booting games whenever firmware is missing. The option to boot games without firmware is still supported when all firmware SPRX are HLEd in firmware settings.
* Pop-up a confirmation dialog in firmware installation if firmware is already installed.
2021-03-06 10:34:49 +01:00

569 lines
16 KiB
C++

#include "gui_application.h"
#include "qt_utils.h"
#include "welcome_dialog.h"
#include "main_window.h"
#include "emu_settings.h"
#include "gui_settings.h"
#include "persistent_settings.h"
#include "gs_frame.h"
#include "gl_gs_frame.h"
#include "display_sleep_control.h"
#include "localized_emu.h"
#ifdef WITH_DISCORD_RPC
#include "_discord_utils.h"
#endif
#include "Emu/Cell/Modules/cellAudio.h"
#include "Emu/RSX/Overlays/overlay_perf_metrics.h"
#include "trophy_notification_helper.h"
#include "save_data_dialog.h"
#include "msg_dialog_frame.h"
#include "osk_dialog_frame.h"
#include "stylesheets.h"
#include <QScreen>
#include <QFontDatabase>
#include <QLibraryInfo>
#include <QDirIterator>
#include <clocale>
#include "Emu/RSX/Null/NullGSRender.h"
#include "Emu/RSX/GL/GLGSRender.h"
#if defined(_WIN32) || defined(HAVE_VULKAN)
#include "Emu/RSX/VK/VKGSRender.h"
#endif
LOG_CHANNEL(gui_log, "GUI");
gui_application::gui_application(int& argc, char** argv) : QApplication(argc, argv)
{
}
gui_application::~gui_application()
{
#ifdef WITH_DISCORD_RPC
discord::shutdown();
#endif
}
bool gui_application::Init()
{
setWindowIcon(QIcon(":/rpcs3.ico"));
m_emu_settings.reset(new emu_settings());
m_gui_settings.reset(new gui_settings());
m_persistent_settings.reset(new persistent_settings());
if (!m_emu_settings->Init())
{
return false;
}
// Get deprecated active user (before August 2nd 2020)
QString active_user = m_gui_settings->GetValue(gui::um_active_user).toString();
// Get active user with deprecated active user as fallback
active_user = m_persistent_settings->GetCurrentUser(active_user.isEmpty() ? "00000001" : active_user);
// Force init the emulator
InitializeEmulator(active_user.toStdString(), true, m_show_gui);
// Create the main window
if (m_show_gui)
{
m_main_window = new main_window(m_gui_settings, m_emu_settings, m_persistent_settings, nullptr);
const auto codes = GetAvailableLanguageCodes();
const auto language = m_gui_settings->GetValue(gui::loc_language).toString();
const auto index = codes.indexOf(language);
LoadLanguage(index < 0 ? QLocale(QLocale::English).bcp47Name() : codes.at(index));
}
// Create callbacks from the emulator, which reference the handlers.
InitializeCallbacks();
// Create connects to propagate events throughout Gui.
InitializeConnects();
if (m_gui_settings->GetValue(gui::ib_show_welcome).toBool())
{
welcome_dialog* welcome = new welcome_dialog();
welcome->exec();
}
if (m_main_window && !m_main_window->Init())
{
return false;
}
#ifdef WITH_DISCORD_RPC
// Discord Rich Presence Integration
if (m_gui_settings->GetValue(gui::m_richPresence).toBool())
{
discord::initialize();
}
#endif
return true;
}
void gui_application::SwitchTranslator(QTranslator& translator, const QString& filename, const QString& language_code)
{
// remove the old translator
removeTranslator(&translator);
const QString lang_path = QLibraryInfo::location(QLibraryInfo::TranslationsPath) + QStringLiteral("/");
const QString file_path = lang_path + filename;
if (QFileInfo(file_path).isFile())
{
// load the new translator
if (translator.load(file_path))
{
installTranslator(&translator);
}
}
else if (const QString default_code = QLocale(QLocale::English).bcp47Name(); language_code != default_code)
{
// show error, but ignore default case "en", since it is handled in source code
gui_log.error("No translation file found in: %s", file_path.toStdString());
// reset current language to default "en"
m_language_code = default_code;
}
}
void gui_application::LoadLanguage(const QString& language_code)
{
if (m_language_code == language_code)
{
return;
}
m_language_code = language_code;
const QLocale locale = QLocale(language_code);
const QString locale_name = QLocale::languageToString(locale.language());
QLocale::setDefault(locale);
// Idk if this is overruled by the QLocale default, so I'll change it here just to be sure.
// As per QT recommendations to avoid conflicts for POSIX functions
std::setlocale(LC_NUMERIC, "C");
SwitchTranslator(m_translator, QStringLiteral("rpcs3_%1.qm").arg(language_code), language_code);
if (m_main_window)
{
const QString default_code = QLocale(QLocale::English).bcp47Name();
QStringList language_codes = GetAvailableLanguageCodes();
if (!language_codes.contains(default_code))
{
language_codes.prepend(default_code);
}
m_main_window->RetranslateUI(language_codes, m_language_code);
}
m_gui_settings->SetValue(gui::loc_language, m_language_code);
gui_log.notice("Current language changed to %s (%s)", locale_name.toStdString(), language_code.toStdString());
}
QStringList gui_application::GetAvailableLanguageCodes()
{
QStringList language_codes;
const QString language_path = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
if (QFileInfo(language_path).isDir())
{
const QDir dir(language_path);
const QStringList filenames = dir.entryList(QStringList("*.qm"));
for (const auto& filename : filenames)
{
QString language_code = filename; // "rpcs3_en.qm"
language_code.truncate(language_code.lastIndexOf('.')); // "rpcs3_en"
language_code.remove(0, language_code.indexOf('_') + 1); // "en"
language_codes << language_code;
}
}
return language_codes;
}
void gui_application::InitializeConnects()
{
connect(&m_timer, &QTimer::timeout, this, &gui_application::UpdatePlaytime);
connect(this, &gui_application::OnEmulatorRun, this, &gui_application::StartPlaytime);
connect(this, &gui_application::OnEmulatorStop, this, &gui_application::StopPlaytime);
connect(this, &gui_application::OnEmulatorPause, this, &gui_application::StopPlaytime);
connect(this, &gui_application::OnEmulatorResume, this, &gui_application::StartPlaytime);
if (m_main_window)
{
connect(m_main_window, &main_window::RequestLanguageChange, this, &gui_application::LoadLanguage);
connect(m_main_window, &main_window::RequestGlobalStylesheetChange, this, &gui_application::OnChangeStyleSheetRequest);
connect(m_main_window, &main_window::NotifyEmuSettingsChange, this, &gui_application::OnEmuSettingsChange);
connect(this, &gui_application::OnEmulatorRun, m_main_window, &main_window::OnEmuRun);
connect(this, &gui_application::OnEmulatorStop, m_main_window, &main_window::OnEmuStop);
connect(this, &gui_application::OnEmulatorPause, m_main_window, &main_window::OnEmuPause);
connect(this, &gui_application::OnEmulatorResume, m_main_window, &main_window::OnEmuResume);
connect(this, &gui_application::OnEmulatorReady, m_main_window, &main_window::OnEmuReady);
}
#ifdef WITH_DISCORD_RPC
connect(this, &gui_application::OnEmulatorRun, [this](bool /*start_playtime*/)
{
// Discord Rich Presence Integration
if (m_gui_settings->GetValue(gui::m_richPresence).toBool())
{
discord::update_presence(Emu.GetTitleID(), Emu.GetTitle());
}
});
connect(this, &gui_application::OnEmulatorStop, [this]()
{
// Discord Rich Presence Integration
if (m_gui_settings->GetValue(gui::m_richPresence).toBool())
{
discord::update_presence(m_gui_settings->GetValue(gui::m_discordState).toString().toStdString());
}
});
#endif
qRegisterMetaType<std::function<void()>>("std::function<void()>");
connect(this, &gui_application::RequestCallAfter, this, &gui_application::HandleCallAfter);
}
std::unique_ptr<gs_frame> gui_application::get_gs_frame()
{
extern const std::unordered_map<video_resolution, std::pair<int, int>, value_hash<video_resolution>> g_video_out_resolution_map;
auto [w, h] = g_video_out_resolution_map.at(g_cfg.video.resolution);
if (m_gui_settings->GetValue(gui::gs_resize).toBool())
{
w = m_gui_settings->GetValue(gui::gs_width).toInt();
h = m_gui_settings->GetValue(gui::gs_height).toInt();
}
const auto screen_geometry = m_main_window ? m_main_window->geometry() : primaryScreen()->geometry();
const auto frame_geometry = gui::utils::create_centered_window_geometry(screen_geometry, w, h);
const auto app_icon = m_main_window ? m_main_window->GetAppIcon() : gui::utils::get_app_icon_from_path(Emu.GetBoot(), Emu.GetTitleID());
gs_frame* frame;
switch (video_renderer type = g_cfg.video.renderer)
{
case video_renderer::opengl:
{
frame = new gl_gs_frame(frame_geometry, app_icon, m_gui_settings);
break;
}
case video_renderer::null:
case video_renderer::vulkan:
{
frame = new gs_frame(frame_geometry, app_icon, m_gui_settings);
break;
}
default: fmt::throw_exception("Invalid video renderer: %s", type);
}
m_game_window = frame;
return std::unique_ptr<gs_frame>(frame);
}
/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */
void gui_application::InitializeCallbacks()
{
EmuCallbacks callbacks = CreateCallbacks();
callbacks.try_to_quit = [this](bool force_quit, std::function<void()> on_exit) -> bool
{
// Close rpcs3 if closed in no-gui mode
if (force_quit || !m_main_window)
{
if (on_exit)
{
on_exit();
}
if (m_main_window)
{
// Close main window in order to save its window state
m_main_window->close();
}
quit();
return true;
}
return false;
};
callbacks.call_after = [this](std::function<void()> func)
{
RequestCallAfter(std::move(func));
};
callbacks.init_gs_render = []()
{
switch (video_renderer type = g_cfg.video.renderer)
{
case video_renderer::null:
{
g_fxo->init<rsx::thread, named_thread<NullGSRender>>();
break;
}
case video_renderer::opengl:
{
g_fxo->init<rsx::thread, named_thread<GLGSRender>>();
break;
}
#if defined(_WIN32) || defined(HAVE_VULKAN)
case video_renderer::vulkan:
{
g_fxo->init<rsx::thread, named_thread<VKGSRender>>();
break;
}
#endif
default:
{
fmt::throw_exception("Invalid video renderer: %s", type);
}
}
};
callbacks.get_gs_frame = [this]() -> std::unique_ptr<GSFrameBase> { return get_gs_frame(); };
callbacks.get_msg_dialog = [this]() -> std::shared_ptr<MsgDialogBase> { return m_show_gui ? std::make_shared<msg_dialog_frame>() : nullptr; };
callbacks.get_osk_dialog = [this]() -> std::shared_ptr<OskDialogBase> { return m_show_gui ? std::make_shared<osk_dialog_frame>() : nullptr; };
callbacks.get_save_dialog = []() -> std::unique_ptr<SaveDialogBase> { return std::make_unique<save_data_dialog>(); };
callbacks.get_trophy_notification_dialog = [this]() -> std::unique_ptr<TrophyNotificationBase> { return std::make_unique<trophy_notification_helper>(m_game_window); };
callbacks.on_run = [this](bool start_playtime) { OnEmulatorRun(start_playtime); };
callbacks.on_pause = [this]() { OnEmulatorPause(); };
callbacks.on_resume = [this]() { OnEmulatorResume(true); };
callbacks.on_stop = [this]() { OnEmulatorStop(); };
callbacks.on_ready = [this]() { OnEmulatorReady(); };
callbacks.on_missing_fw = [this]()
{
if (!m_main_window) return false;
return m_main_window->OnMissingFw();
};
callbacks.handle_taskbar_progress = [this](s32 type, s32 value)
{
if (m_game_window)
{
switch (type)
{
case 0: static_cast<gs_frame*>(m_game_window)->progress_reset(value); break;
case 1: static_cast<gs_frame*>(m_game_window)->progress_increment(value); break;
case 2: static_cast<gs_frame*>(m_game_window)->progress_set_limit(value); break;
case 3: static_cast<gs_frame*>(m_game_window)->progress_set_value(value); break;
default: gui_log.fatal("Unknown type in handle_taskbar_progress(type=%d, value=%d)", type, value); break;
}
}
};
callbacks.get_localized_string = [](localized_string_id id, const char* args) -> std::string
{
return localized_emu::get_string(id, args);
};
callbacks.get_localized_u32string = [](localized_string_id id, const char* args) -> std::u32string
{
return localized_emu::get_u32string(id, args);
};
Emu.SetCallbacks(std::move(callbacks));
}
void gui_application::StartPlaytime(bool start_playtime = true)
{
if (!start_playtime)
{
return;
}
const QString serial = qstr(Emu.GetTitleID());
if (serial.isEmpty())
{
return;
}
m_persistent_settings->SetLastPlayed(serial, QDate::currentDate().toString(gui::persistent::last_played_date_format));
m_timer_playtime.start();
m_timer.start(10000); // Update every 10 seconds in case the emulation crashes
}
void gui_application::UpdatePlaytime()
{
if (!m_timer_playtime.isValid())
{
m_timer.stop();
return;
}
const QString serial = qstr(Emu.GetTitleID());
if (serial.isEmpty())
{
m_timer_playtime.invalidate();
m_timer.stop();
return;
}
m_persistent_settings->AddPlaytime(serial, m_timer_playtime.restart());
m_persistent_settings->SetLastPlayed(serial, QDate::currentDate().toString(gui::persistent::last_played_date_format));
}
void gui_application::StopPlaytime()
{
m_timer.stop();
if (!m_timer_playtime.isValid())
return;
const QString serial = qstr(Emu.GetTitleID());
if (serial.isEmpty())
{
m_timer_playtime.invalidate();
return;
}
m_persistent_settings->AddPlaytime(serial, m_timer_playtime.restart());
m_persistent_settings->SetLastPlayed(serial, QDate::currentDate().toString(gui::persistent::last_played_date_format));
m_timer_playtime.invalidate();
}
/*
* Handle a request to change the stylesheet based on the current entry in the settings.
*/
void gui_application::OnChangeStyleSheetRequest()
{
// skip stylesheets on first repaint if a style was set from command line
if (m_use_cli_style && gui::stylesheet.isEmpty())
{
gui::stylesheet = styleSheet().isEmpty() ? "/* style set by command line arg */" : styleSheet();
if (m_main_window)
{
m_main_window->RepaintGui();
}
return;
}
// Remove old fonts
QFontDatabase::removeAllApplicationFonts();
const QString stylesheet_name = m_gui_settings->GetValue(gui::m_currentStylesheet).toString();
if (stylesheet_name.isEmpty() || stylesheet_name == gui::DefaultStylesheet)
{
setStyleSheet(gui::stylesheets::default_style_sheet);
}
else if (stylesheet_name == gui::NoStylesheet)
{
setStyleSheet("/* none */");
}
else
{
QString stylesheet_path;
QString stylesheet_dir;
QList<QDir> locs;
locs << m_gui_settings->GetSettingsDir();
#if !defined(_WIN32)
#ifdef __APPLE__
locs << QCoreApplication::applicationDirPath() + "/../Resources/GuiConfigs/";
#else
locs << QCoreApplication::applicationDirPath() + "/../share/rpcs3/GuiConfigs/";
#endif
locs << QCoreApplication::applicationDirPath() + "/GuiConfigs/";
#endif
for (auto&& loc : locs)
{
QFileInfo file_info(loc.absoluteFilePath(stylesheet_name + QStringLiteral(".qss")));
if (file_info.exists())
{
loc.cdUp();
stylesheet_dir = loc.absolutePath();
stylesheet_path = file_info.absoluteFilePath();
break;
}
}
if (QFile file(stylesheet_path); !stylesheet_path.isEmpty() && file.open(QIODevice::ReadOnly | QIODevice::Text))
{
const QString config_dir = qstr(fs::get_config_dir());
// Add PS3 fonts
QDirIterator ps3_font_it(qstr(g_cfg.vfs.get_dev_flash() + "data/font/"), QStringList() << "*.ttf", QDir::Files, QDirIterator::Subdirectories);
while (ps3_font_it.hasNext())
QFontDatabase::addApplicationFont(ps3_font_it.next());
// Add custom fonts
QDirIterator custom_font_it(config_dir + "fonts/", QStringList() << "*.ttf", QDir::Files, QDirIterator::Subdirectories);
while (custom_font_it.hasNext())
QFontDatabase::addApplicationFont(custom_font_it.next());
// Replace relative paths with absolute paths. Since relative paths should always be the same, we can just use simple string replacement.
// Another option would be to use QDir::setCurrent, but that changes current working directory for the whole process (We don't want that).
QString stylesheet = file.readAll();
stylesheet.replace(QStringLiteral("url(\"GuiConfigs/"), QStringLiteral("url(\"") + stylesheet_dir + QStringLiteral("/GuiConfigs/"));
setStyleSheet(stylesheet);
file.close();
}
else
{
gui_log.error("Could not find stylesheet '%s'. Using default.", stylesheet_name.toStdString());
setStyleSheet(gui::stylesheets::default_style_sheet);
}
}
gui::stylesheet = styleSheet();
if (m_main_window)
{
m_main_window->RepaintGui();
}
}
void gui_application::OnEmuSettingsChange()
{
if (Emu.IsRunning())
{
if (g_cfg.misc.prevent_display_sleep)
{
enable_display_sleep();
}
else
{
disable_display_sleep();
}
}
Emu.ConfigureLogs();
audio::configure_audio();
rsx::overlays::reset_performance_overlay();
}
/**
* Using connects avoids timers being unable to be used in a non-qt thread. So, even if this looks stupid to just call func, it's succinct.
*/
void gui_application::HandleCallAfter(const std::function<void()>& func)
{
func();
}