mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-05 08:10:10 +01:00
Also renames "AudioThread" to "AudioBackend". The new name is more descriptive of what the class really is responsible for, since the backends are not responsible for managing the audio thread. NOTE: Right now only XAudio2 is supported
495 lines
16 KiB
C++
495 lines
16 KiB
C++
#include "rpcs3_app.h"
|
|
|
|
#include "rpcs3qt/qt_utils.h"
|
|
|
|
#include "rpcs3qt/welcome_dialog.h"
|
|
|
|
#ifdef WITH_DISCORD_RPC
|
|
#include "rpcs3qt/_discord_utils.h"
|
|
#endif
|
|
|
|
#include "Emu/System.h"
|
|
#include "rpcs3qt/gs_frame.h"
|
|
#include "rpcs3qt/gl_gs_frame.h"
|
|
|
|
#include "rpcs3qt/trophy_notification_helper.h"
|
|
#include "rpcs3qt/save_data_dialog.h"
|
|
|
|
#include "Emu/Io/Null/NullKeyboardHandler.h"
|
|
#include "basic_keyboard_handler.h"
|
|
|
|
#include "Emu/Io/Null/NullMouseHandler.h"
|
|
#include "basic_mouse_handler.h"
|
|
|
|
#include "Emu/Io/Null/NullPadHandler.h"
|
|
#include "keyboard_pad_handler.h"
|
|
#include "ds4_pad_handler.h"
|
|
#ifdef _WIN32
|
|
#include "xinput_pad_handler.h"
|
|
#include "mm_joystick_handler.h"
|
|
#endif
|
|
#ifdef HAVE_LIBEVDEV
|
|
#include "evdev_joystick_handler.h"
|
|
#endif
|
|
|
|
#include "pad_thread.h"
|
|
|
|
#include "Emu/RSX/Null/NullGSRender.h"
|
|
#include "Emu/RSX/GL/GLGSRender.h"
|
|
#include "Emu/Audio/Null/NullAudioBackend.h"
|
|
//#include "Emu/Audio/AL/OpenALThread.h"
|
|
#ifdef _MSC_VER
|
|
#include "Emu/RSX/D3D12/D3D12GSRender.h"
|
|
#endif
|
|
#if defined(_WIN32) || defined(HAVE_VULKAN)
|
|
#include "Emu/RSX/VK/VKGSRender.h"
|
|
#endif
|
|
#ifdef _WIN32
|
|
#include "Emu/Audio/XAudio2/XAudio2Backend.h"
|
|
#endif
|
|
#ifdef HAVE_ALSA
|
|
#include "Emu/Audio/ALSA/ALSAThread.h"
|
|
#endif
|
|
#ifdef HAVE_PULSE
|
|
#include "Emu/Audio/Pulse/PulseThread.h"
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include "Utilities/dynamic_library.h"
|
|
DYNAMIC_IMPORT("ntdll.dll", NtQueryTimerResolution, NTSTATUS(PULONG MinimumResolution, PULONG MaximumResolution, PULONG CurrentResolution));
|
|
DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution));
|
|
#endif
|
|
|
|
// For now, a trivial constructor/destructor. May add command line usage later.
|
|
rpcs3_app::rpcs3_app(int& argc, char** argv) : QApplication(argc, argv)
|
|
{
|
|
}
|
|
|
|
void rpcs3_app::Init()
|
|
{
|
|
setApplicationName("RPCS3");
|
|
setWindowIcon(QIcon(":/rpcs3.ico"));
|
|
|
|
guiSettings.reset(new gui_settings());
|
|
emuSettings.reset(new emu_settings());
|
|
|
|
// Force init the emulator
|
|
InitializeEmulator(guiSettings->GetCurrentUser().toStdString(), true);
|
|
|
|
// Create the main window
|
|
RPCS3MainWin = new main_window(guiSettings, emuSettings, nullptr);
|
|
|
|
// Create callbacks from the emulator, which reference the handlers.
|
|
InitializeCallbacks();
|
|
|
|
// Create connects to propagate events throughout Gui.
|
|
InitializeConnects();
|
|
|
|
RPCS3MainWin->Init();
|
|
|
|
if (guiSettings->GetValue(gui::ib_show_welcome).toBool())
|
|
{
|
|
welcome_dialog* welcome = new welcome_dialog();
|
|
welcome->exec();
|
|
}
|
|
#ifdef WITH_DISCORD_RPC
|
|
// Discord Rich Presence Integration
|
|
if (guiSettings->GetValue(gui::m_richPresence).toBool())
|
|
{
|
|
discord::initialize();
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
// Set 0.5 msec timer resolution for best performance
|
|
// - As QT5 timers (QTimer) sets the timer resolution to 1 msec, override it here.
|
|
// - Don't bother "unsetting" the timer resolution after the emulator stops as QT5 will still require the timer resolution to be set to 1 msec.
|
|
ULONG min_res, max_res, orig_res, new_res;
|
|
if (NtQueryTimerResolution(&min_res, &max_res, &orig_res) == 0)
|
|
{
|
|
NtSetTimerResolution(max_res, TRUE, &new_res);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Emu.Init() wrapper for user manager */
|
|
bool rpcs3_app::InitializeEmulator(const std::string& user, bool force_init)
|
|
{
|
|
// try to set a new user
|
|
const bool user_was_set = Emu.SetUsr(user);
|
|
|
|
// only init the emulation if forced or a user was set
|
|
if (user_was_set || force_init)
|
|
{
|
|
Emu.Init();
|
|
}
|
|
|
|
return user_was_set;
|
|
}
|
|
|
|
/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here.
|
|
*/
|
|
void rpcs3_app::InitializeCallbacks()
|
|
{
|
|
EmuCallbacks callbacks;
|
|
|
|
callbacks.exit = [this]()
|
|
{
|
|
quit();
|
|
};
|
|
callbacks.call_after = [=](std::function<void()> func)
|
|
{
|
|
RequestCallAfter(std::move(func));
|
|
};
|
|
|
|
callbacks.reset_pads = [this]()
|
|
{
|
|
pad::get_current_handler()->Reset();
|
|
};
|
|
callbacks.enable_pads = [this](bool enable)
|
|
{
|
|
pad::get_current_handler()->SetEnabled(enable);
|
|
};
|
|
|
|
callbacks.get_kb_handler = [=]() -> std::shared_ptr<KeyboardHandlerBase>
|
|
{
|
|
switch (keyboard_handler type = g_cfg.io.keyboard)
|
|
{
|
|
case keyboard_handler::null: return std::make_shared<NullKeyboardHandler>();
|
|
case keyboard_handler::basic:
|
|
{
|
|
basic_keyboard_handler* ret = new basic_keyboard_handler();
|
|
ret->moveToThread(thread());
|
|
ret->SetTargetWindow(gameWindow);
|
|
return std::shared_ptr<KeyboardHandlerBase>(ret);
|
|
}
|
|
default: fmt::throw_exception("Invalid keyboard handler: %s", type);
|
|
}
|
|
};
|
|
|
|
callbacks.get_mouse_handler = [=]() -> std::shared_ptr<MouseHandlerBase>
|
|
{
|
|
switch (mouse_handler type = g_cfg.io.mouse)
|
|
{
|
|
case mouse_handler::null: return std::make_shared<NullMouseHandler>();
|
|
case mouse_handler::basic:
|
|
{
|
|
basic_mouse_handler* ret = new basic_mouse_handler();
|
|
ret->moveToThread(thread());
|
|
ret->SetTargetWindow(gameWindow);
|
|
return std::shared_ptr<MouseHandlerBase>(ret);
|
|
}
|
|
default: fmt::throw_exception("Invalid mouse handler: %s", type);
|
|
}
|
|
};
|
|
|
|
callbacks.get_pad_handler = [this]() -> std::shared_ptr<pad_thread>
|
|
{
|
|
return std::make_shared<pad_thread>(thread(), gameWindow);
|
|
};
|
|
|
|
callbacks.get_gs_frame = [this]() -> std::unique_ptr<GSFrameBase>
|
|
{
|
|
extern const std::unordered_map<video_resolution, std::pair<int, int>, value_hash<video_resolution>> g_video_out_resolution_map;
|
|
|
|
const auto size = g_video_out_resolution_map.at(g_cfg.video.resolution);
|
|
int w = size.first;
|
|
int h = size.second;
|
|
|
|
if (guiSettings->GetValue(gui::gs_resize).toBool())
|
|
{
|
|
w = guiSettings->GetValue(gui::gs_width).toInt();
|
|
h = guiSettings->GetValue(gui::gs_height).toInt();
|
|
}
|
|
|
|
auto frame_geometry = gui::utils::create_centered_window_geometry(RPCS3MainWin->geometry(), w, h);
|
|
|
|
gs_frame* frame;
|
|
|
|
switch (video_renderer type = g_cfg.video.renderer)
|
|
{
|
|
case video_renderer::null:
|
|
{
|
|
frame = new gs_frame("Null", frame_geometry, RPCS3MainWin->GetAppIcon(), guiSettings);
|
|
break;
|
|
}
|
|
case video_renderer::opengl:
|
|
{
|
|
frame = new gl_gs_frame(frame_geometry, RPCS3MainWin->GetAppIcon(), guiSettings);
|
|
break;
|
|
}
|
|
case video_renderer::vulkan:
|
|
{
|
|
frame = new gs_frame("Vulkan", frame_geometry, RPCS3MainWin->GetAppIcon(), guiSettings);
|
|
break;
|
|
}
|
|
#ifdef _MSC_VER
|
|
case video_renderer::dx12:
|
|
{
|
|
frame = new gs_frame("DirectX 12", frame_geometry, RPCS3MainWin->GetAppIcon(), guiSettings);
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
fmt::throw_exception("Invalid video renderer: %s" HERE, type);
|
|
}
|
|
|
|
gameWindow = frame;
|
|
return std::unique_ptr<gs_frame>(frame);
|
|
};
|
|
|
|
callbacks.get_gs_render = []() -> std::shared_ptr<GSRender>
|
|
{
|
|
switch (video_renderer type = g_cfg.video.renderer)
|
|
{
|
|
case video_renderer::null: return std::make_shared<named_thread<NullGSRender>>("rsx::thread");
|
|
case video_renderer::opengl: return std::make_shared<named_thread<GLGSRender>>("rsx::thread");
|
|
#if defined(_WIN32) || defined(HAVE_VULKAN)
|
|
case video_renderer::vulkan: return std::make_shared<named_thread<VKGSRender>>("rsx::thread");
|
|
#endif
|
|
#ifdef _MSC_VER
|
|
case video_renderer::dx12: return std::make_shared<named_thread<D3D12GSRender>>("rsx::thread");
|
|
#endif
|
|
default: fmt::throw_exception("Invalid video renderer: %s" HERE, type);
|
|
}
|
|
};
|
|
|
|
callbacks.get_audio = []() -> std::shared_ptr<AudioBackend>
|
|
{
|
|
switch (audio_renderer type = g_cfg.audio.renderer)
|
|
{
|
|
case audio_renderer::null: return std::make_shared<NullAudioBackend>();
|
|
#ifdef _WIN32
|
|
case audio_renderer::xaudio: return std::make_shared<XAudio2Backend>();
|
|
#endif
|
|
#ifdef HAVE_ALSA
|
|
case audio_renderer::alsa: return std::make_shared<ALSAThread>();
|
|
#endif
|
|
#ifdef HAVE_PULSE
|
|
case audio_renderer::pulse: return std::make_shared<PulseThread>();
|
|
#endif
|
|
|
|
//case audio_renderer::openal: return std::make_shared<OpenALThread>();
|
|
default: fmt::throw_exception("Invalid audio renderer: %s" HERE, type);
|
|
}
|
|
};
|
|
|
|
callbacks.get_msg_dialog = [=]() -> std::shared_ptr<MsgDialogBase>
|
|
{
|
|
return std::make_shared<msg_dialog_frame>(RPCS3MainWin->windowHandle());
|
|
};
|
|
|
|
callbacks.get_osk_dialog = [=]() -> std::shared_ptr<OskDialogBase>
|
|
{
|
|
return std::make_shared<osk_dialog_frame>();
|
|
};
|
|
|
|
callbacks.get_save_dialog = [=]() -> std::unique_ptr<SaveDialogBase>
|
|
{
|
|
return std::make_unique<save_data_dialog>();
|
|
};
|
|
|
|
callbacks.get_trophy_notification_dialog = [=]() -> std::unique_ptr<TrophyNotificationBase>
|
|
{
|
|
return std::make_unique<trophy_notification_helper>(gameWindow);
|
|
};
|
|
|
|
callbacks.on_run = [=]() { OnEmulatorRun(); };
|
|
callbacks.on_pause = [=]() { OnEmulatorPause(); };
|
|
callbacks.on_resume = [=]() { OnEmulatorResume(); };
|
|
callbacks.on_stop = [=]() { OnEmulatorStop(); };
|
|
callbacks.on_ready = [=]() { OnEmulatorReady(); };
|
|
|
|
callbacks.handle_taskbar_progress = [=](s32 type, s32 value)
|
|
{
|
|
if (gameWindow)
|
|
{
|
|
switch (type)
|
|
{
|
|
case 0:
|
|
((gs_frame*)gameWindow)->progress_reset(value);
|
|
break;
|
|
case 1:
|
|
((gs_frame*)gameWindow)->progress_increment(value);
|
|
break;
|
|
case 2:
|
|
((gs_frame*)gameWindow)->progress_set_limit(value);
|
|
break;
|
|
default:
|
|
LOG_FATAL(GENERAL, "Unknown type in handle_taskbar_progress(type=%d, value=%d)", type, value);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
Emu.SetCallbacks(std::move(callbacks));
|
|
}
|
|
|
|
/*
|
|
* Initialize connects here. These are used to connect things between UI elements that require the intervention of rpcs3_app.
|
|
*/
|
|
void rpcs3_app::InitializeConnects()
|
|
{
|
|
connect(RPCS3MainWin, &main_window::RequestGlobalStylesheetChange, this, &rpcs3_app::OnChangeStyleSheetRequest);
|
|
|
|
qRegisterMetaType <std::function<void()>>("std::function<void()>");
|
|
connect(this, &rpcs3_app::RequestCallAfter, this, &rpcs3_app::HandleCallAfter);
|
|
|
|
connect(this, &rpcs3_app::OnEmulatorRun, RPCS3MainWin, &main_window::OnEmuRun);
|
|
connect(this, &rpcs3_app::OnEmulatorStop, RPCS3MainWin, &main_window::OnEmuStop);
|
|
connect(this, &rpcs3_app::OnEmulatorPause, RPCS3MainWin, &main_window::OnEmuPause);
|
|
connect(this, &rpcs3_app::OnEmulatorResume, RPCS3MainWin, &main_window::OnEmuResume);
|
|
connect(this, &rpcs3_app::OnEmulatorReady, RPCS3MainWin, &main_window::OnEmuReady);
|
|
}
|
|
|
|
/*
|
|
* Handle a request to change the stylesheet. May consider adding reporting of errors in future.
|
|
* Empty string means default.
|
|
*/
|
|
void rpcs3_app::OnChangeStyleSheetRequest(const QString& path)
|
|
{
|
|
QString style_sheet
|
|
(
|
|
// main window toolbar search
|
|
"QLineEdit#mw_searchbar { padding: 0 1em; background: #fdfdfd; selection-background-color: #148aff; margin: .8em; color:#000000; }"
|
|
|
|
// main window toolbar slider
|
|
"QSlider#sizeSlider { color: #505050; background: #F0F0F0; }"
|
|
"QSlider#sizeSlider::handle:horizontal { border: 0em smooth rgba(227, 227, 227, 255); border-radius: .58em; background: #404040; width: 1.2em; margin: -.5em 0; }"
|
|
"QSlider#sizeSlider::groove:horizontal { border-radius: .15em; background: #5b5b5b; height: .3em; }"
|
|
|
|
// main window toolbar
|
|
"QToolBar#mw_toolbar { background-color: #F0F0F0; border: none; }"
|
|
"QToolBar#mw_toolbar::separator { background-color: rgba(207, 207, 207, 235); width: 0.125em; margin-top: 0.250em; margin-bottom: 0.250em; }"
|
|
|
|
// main window toolbar icon color
|
|
"QLabel#toolbar_icon_color { color: #5b5b5b; }"
|
|
|
|
// thumbnail icon color
|
|
"QLabel#thumbnail_icon_color { color: rgba(0, 100, 231, 255); }"
|
|
|
|
// game list icon color
|
|
"QLabel#gamelist_icon_background_color { color: rgba(36, 36, 36, 255); }"
|
|
|
|
// tables
|
|
"QTableWidget { alternate-background-color: #f2f2f2; background-color: #fff; border: none; }"
|
|
"QTableWidget#game_grid { alternate-background-color: #f2f2f2; background-color: #fff; font-weight: 600; font-size: 8pt; font-family: Lucida Grande; color: rgba(51, 51, 51, 255); border: 0em solid white; }"
|
|
"QTableView::item { border-left: 0.063em solid white; border-right: 0.063em solid white; padding-left:0.313em; }"
|
|
"QTableView::item:selected { background-color: #148aff; color: #fff; }"
|
|
"QTableView#game_grid::item:hover:!selected { background-color: #94c9ff; color: #fff; }"
|
|
"QTableView#game_grid::item:hover:selected { background-color: #007fff; color: #fff; }"
|
|
|
|
// table headers
|
|
#if (QT_VERSION < QT_VERSION_CHECK(5,11,0))
|
|
"QHeaderView::section { padding: .5em; border: 0.063em solid #ffffff; }"
|
|
"QHeaderView::section:hover { background: #e3e3e3; padding: .5em; border: 0.063em solid #ffffff; }"
|
|
#else
|
|
"QHeaderView::section { padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #ffffff; }"
|
|
"QHeaderView::section:hover { background: #e3e3e3; padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #ffffff; }"
|
|
#endif
|
|
|
|
// dock widget
|
|
"QDockWidget{ background: transparent; color: black; }"
|
|
"[floating=\"true\"]{ background: white; }"
|
|
"QDockWidget::title{ background: #e3e3e3; border: none; padding-top: 0.2em; padding-left: 0.2em; }"
|
|
"QDockWidget::close-button, QDockWidget::float-button{ background-color: #e3e3e3; }"
|
|
|
|
// log frame tty
|
|
"QTextEdit#tty_frame { background-color: #ffffff; }"
|
|
"QLabel#tty_text { color: #000000; }"
|
|
|
|
// log frame log
|
|
"QTextEdit#log_frame { background-color: #ffffff; }"
|
|
"QLabel#log_level_always { color: #107896; }"
|
|
"QLabel#log_level_fatal { color: #ff00ff; }"
|
|
"QLabel#log_level_error { color: #C02F1D; }"
|
|
"QLabel#log_level_todo { color: #ff6000; }"
|
|
"QLabel#log_level_success { color: #008000; }"
|
|
"QLabel#log_level_warning { color: #BA8745; }"
|
|
"QLabel#log_level_notice { color: #000000; }"
|
|
"QLabel#log_level_trace { color: #808080; }"
|
|
"QLabel#log_stack { color: #000000; }"
|
|
|
|
// about dialog
|
|
"QWidget#header_section { background-color: #ffffff; }"
|
|
|
|
// kernel explorer
|
|
"QDialog#kernel_explorer { background-color: rgba(240, 240, 240, 255); }"
|
|
|
|
// memory viewer
|
|
"QDialog#memory_viewer { background-color: rgba(240, 240, 240, 255); }"
|
|
"QLabel#memory_viewer_address_panel { color: rgba(75, 135, 150, 255); background-color: rgba(240, 240, 240, 255); }"
|
|
"QLabel#memory_viewer_hex_panel { color: #000000; background-color: rgba(240, 240, 240, 255); }"
|
|
"QLabel#memory_viewer_ascii_panel { color: #000000; background-color: rgba(240, 240, 240, 255); }"
|
|
|
|
// debugger frame
|
|
"QLabel#debugger_frame_breakpoint { color: #000000; background-color: #ffff00; }"
|
|
"QLabel#debugger_frame_pc { color: #000000; background-color: #00ff00; }"
|
|
|
|
// rsx debugger
|
|
"QLabel#rsx_debugger_display_buffer { background-color: rgba(240, 240, 240, 255); }"
|
|
|
|
// pad settings
|
|
"QLabel#l_controller { color: #434343; }"
|
|
);
|
|
|
|
QFile file(path);
|
|
|
|
// If we can't open the file, try the /share or /Resources folder
|
|
#if !defined(_WIN32)
|
|
#ifdef __APPLE__
|
|
QString share_dir = QCoreApplication::applicationDirPath() + "/../Resources/";
|
|
#else
|
|
QString share_dir = QCoreApplication::applicationDirPath() + "/../share/rpcs3/";
|
|
#endif
|
|
QFile share_file(share_dir + "GuiConfigs/" + QFileInfo(file.fileName()).fileName());
|
|
#endif
|
|
|
|
if (path == "")
|
|
{
|
|
setStyleSheet(style_sheet);
|
|
}
|
|
else if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
{
|
|
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());
|
|
|
|
// Set root for stylesheets
|
|
QDir::setCurrent(config_dir);
|
|
setStyleSheet(file.readAll());
|
|
file.close();
|
|
}
|
|
#if !defined(_WIN32)
|
|
else if (share_file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
{
|
|
QDir::setCurrent(share_dir);
|
|
setStyleSheet(share_file.readAll());
|
|
share_file.close();
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
setStyleSheet(style_sheet);
|
|
}
|
|
|
|
gui::stylesheet = styleSheet();
|
|
RPCS3MainWin->RepaintGui();
|
|
}
|
|
|
|
/**
|
|
* 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 rpcs3_app::HandleCallAfter(const std::function<void()>& func)
|
|
{
|
|
func();
|
|
}
|