main.cpp 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <cinttypes>
  5. #include <clocale>
  6. #include <memory>
  7. #include <thread>
  8. #include <fmt/ostream.h>
  9. #include <glad/glad.h>
  10. #define QT_NO_OPENGL
  11. #include <QDesktopWidget>
  12. #include <QFileDialog>
  13. #include <QMessageBox>
  14. #include <QtGui>
  15. #include <QtWidgets>
  16. #include "common/common_paths.h"
  17. #include "common/logging/backend.h"
  18. #include "common/logging/filter.h"
  19. #include "common/logging/log.h"
  20. #include "common/logging/text_formatter.h"
  21. #include "common/microprofile.h"
  22. #include "common/scm_rev.h"
  23. #include "common/scope_exit.h"
  24. #include "common/string_util.h"
  25. #include "core/core.h"
  26. #include "core/crypto/key_manager.h"
  27. #include "core/file_sys/bis_factory.h"
  28. #include "core/file_sys/card_image.h"
  29. #include "core/file_sys/registered_cache.h"
  30. #include "core/file_sys/savedata_factory.h"
  31. #include "core/file_sys/vfs_real.h"
  32. #include "core/gdbstub/gdbstub.h"
  33. #include "core/loader/loader.h"
  34. #include "core/settings.h"
  35. #include "video_core/debug_utils/debug_utils.h"
  36. #include "yuzu/about_dialog.h"
  37. #include "yuzu/bootmanager.h"
  38. #include "yuzu/configuration/config.h"
  39. #include "yuzu/configuration/configure_dialog.h"
  40. #include "yuzu/debugger/console.h"
  41. #include "yuzu/debugger/graphics/graphics_breakpoints.h"
  42. #include "yuzu/debugger/graphics/graphics_surface.h"
  43. #include "yuzu/debugger/profiler.h"
  44. #include "yuzu/debugger/wait_tree.h"
  45. #include "yuzu/game_list.h"
  46. #include "yuzu/hotkeys.h"
  47. #include "yuzu/main.h"
  48. #include "yuzu/ui_settings.h"
  49. #ifdef QT_STATICPLUGIN
  50. Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
  51. #endif
  52. #ifdef _WIN32
  53. extern "C" {
  54. // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
  55. // graphics
  56. __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
  57. __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
  58. }
  59. #endif
  60. /**
  61. * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
  62. * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
  63. * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
  64. */
  65. enum class CalloutFlag : uint32_t {
  66. Telemetry = 0x1,
  67. };
  68. static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
  69. if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
  70. return;
  71. }
  72. UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
  73. QMessageBox msg;
  74. msg.setText(message);
  75. msg.setStandardButtons(QMessageBox::Ok);
  76. msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  77. msg.setStyleSheet("QLabel{min-width: 900px;}");
  78. msg.exec();
  79. }
  80. void GMainWindow::ShowCallouts() {}
  81. const int GMainWindow::max_recent_files_item;
  82. static void InitializeLogging() {
  83. Log::Filter log_filter;
  84. log_filter.ParseFilterString(Settings::values.log_filter);
  85. Log::SetGlobalFilter(log_filter);
  86. const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
  87. FileUtil::CreateFullPath(log_dir);
  88. Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
  89. }
  90. GMainWindow::GMainWindow()
  91. : config(new Config()), emu_thread(nullptr),
  92. vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
  93. InitializeLogging();
  94. debug_context = Tegra::DebugContext::Construct();
  95. setAcceptDrops(true);
  96. ui.setupUi(this);
  97. statusBar()->hide();
  98. default_theme_paths = QIcon::themeSearchPaths();
  99. UpdateUITheme();
  100. InitializeWidgets();
  101. InitializeDebugWidgets();
  102. InitializeRecentFileMenuActions();
  103. InitializeHotkeys();
  104. SetDefaultUIGeometry();
  105. RestoreUIState();
  106. ConnectMenuEvents();
  107. ConnectWidgetEvents();
  108. LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch,
  109. Common::g_scm_desc);
  110. setWindowTitle(QString("yuzu %1| %2-%3")
  111. .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
  112. show();
  113. // Necessary to load titles from nand in gamelist.
  114. Service::FileSystem::CreateFactories(vfs);
  115. game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
  116. // Show one-time "callout" messages to the user
  117. ShowCallouts();
  118. QStringList args = QApplication::arguments();
  119. if (args.length() >= 2) {
  120. BootGame(args[1]);
  121. }
  122. }
  123. GMainWindow::~GMainWindow() {
  124. // will get automatically deleted otherwise
  125. if (render_window->parent() == nullptr)
  126. delete render_window;
  127. }
  128. void GMainWindow::InitializeWidgets() {
  129. render_window = new GRenderWindow(this, emu_thread.get());
  130. render_window->hide();
  131. game_list = new GameList(vfs, this);
  132. ui.horizontalLayout->addWidget(game_list);
  133. // Create status bar
  134. message_label = new QLabel();
  135. // Configured separately for left alignment
  136. message_label->setVisible(false);
  137. message_label->setFrameStyle(QFrame::NoFrame);
  138. message_label->setContentsMargins(4, 0, 4, 0);
  139. message_label->setAlignment(Qt::AlignLeft);
  140. statusBar()->addPermanentWidget(message_label, 1);
  141. emu_speed_label = new QLabel();
  142. emu_speed_label->setToolTip(
  143. tr("Current emulation speed. Values higher or lower than 100% "
  144. "indicate emulation is running faster or slower than a Switch."));
  145. game_fps_label = new QLabel();
  146. game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. "
  147. "This will vary from game to game and scene to scene."));
  148. emu_frametime_label = new QLabel();
  149. emu_frametime_label->setToolTip(
  150. tr("Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For "
  151. "full-speed emulation this should be at most 16.67 ms."));
  152. for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) {
  153. label->setVisible(false);
  154. label->setFrameStyle(QFrame::NoFrame);
  155. label->setContentsMargins(4, 0, 4, 0);
  156. statusBar()->addPermanentWidget(label, 0);
  157. }
  158. statusBar()->setVisible(true);
  159. setStyleSheet("QStatusBar::item{border: none;}");
  160. }
  161. void GMainWindow::InitializeDebugWidgets() {
  162. QMenu* debug_menu = ui.menu_View_Debugging;
  163. #if MICROPROFILE_ENABLED
  164. microProfileDialog = new MicroProfileDialog(this);
  165. microProfileDialog->hide();
  166. debug_menu->addAction(microProfileDialog->toggleViewAction());
  167. #endif
  168. graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this);
  169. addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
  170. graphicsBreakpointsWidget->hide();
  171. debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
  172. graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
  173. addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
  174. graphicsSurfaceWidget->hide();
  175. debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
  176. waitTreeWidget = new WaitTreeWidget(this);
  177. addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
  178. waitTreeWidget->hide();
  179. debug_menu->addAction(waitTreeWidget->toggleViewAction());
  180. connect(this, &GMainWindow::EmulationStarting, waitTreeWidget,
  181. &WaitTreeWidget::OnEmulationStarting);
  182. connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
  183. &WaitTreeWidget::OnEmulationStopping);
  184. }
  185. void GMainWindow::InitializeRecentFileMenuActions() {
  186. for (int i = 0; i < max_recent_files_item; ++i) {
  187. actions_recent_files[i] = new QAction(this);
  188. actions_recent_files[i]->setVisible(false);
  189. connect(actions_recent_files[i], &QAction::triggered, this, &GMainWindow::OnMenuRecentFile);
  190. ui.menu_recent_files->addAction(actions_recent_files[i]);
  191. }
  192. ui.menu_recent_files->addSeparator();
  193. QAction* action_clear_recent_files = new QAction(this);
  194. action_clear_recent_files->setText(tr("Clear Recent Files"));
  195. connect(action_clear_recent_files, &QAction::triggered, this, [this] {
  196. UISettings::values.recent_files.clear();
  197. UpdateRecentFiles();
  198. });
  199. ui.menu_recent_files->addAction(action_clear_recent_files);
  200. UpdateRecentFiles();
  201. }
  202. void GMainWindow::InitializeHotkeys() {
  203. hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
  204. hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
  205. hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
  206. hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
  207. hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
  208. hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
  209. Qt::ApplicationShortcut);
  210. hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
  211. Qt::ApplicationShortcut);
  212. hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
  213. Qt::ApplicationShortcut);
  214. hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
  215. Qt::ApplicationShortcut);
  216. hotkey_registry.LoadHotkeys();
  217. connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
  218. this, &GMainWindow::OnMenuLoadFile);
  219. connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
  220. &QShortcut::activated, this, &GMainWindow::OnStartGame);
  221. connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
  222. this, [&] {
  223. if (emulation_running) {
  224. if (emu_thread->IsRunning()) {
  225. OnPauseGame();
  226. } else {
  227. OnStartGame();
  228. }
  229. }
  230. });
  231. connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
  232. [this] {
  233. if (!Core::System::GetInstance().IsPoweredOn())
  234. return;
  235. BootGame(QString(game_path));
  236. });
  237. connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
  238. &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger);
  239. connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
  240. &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger);
  241. connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this),
  242. &QShortcut::activated, this, [&] {
  243. if (emulation_running) {
  244. ui.action_Fullscreen->setChecked(false);
  245. ToggleFullscreen();
  246. }
  247. });
  248. connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this),
  249. &QShortcut::activated, this, [&] {
  250. Settings::values.use_frame_limit = !Settings::values.use_frame_limit;
  251. UpdateStatusBar();
  252. });
  253. constexpr u16 SPEED_LIMIT_STEP = 5;
  254. connect(hotkey_registry.GetHotkey("Main Window", "Increase Speed Limit", this),
  255. &QShortcut::activated, this, [&] {
  256. if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {
  257. Settings::values.frame_limit += SPEED_LIMIT_STEP;
  258. UpdateStatusBar();
  259. }
  260. });
  261. connect(hotkey_registry.GetHotkey("Main Window", "Decrease Speed Limit", this),
  262. &QShortcut::activated, this, [&] {
  263. if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
  264. Settings::values.frame_limit -= SPEED_LIMIT_STEP;
  265. UpdateStatusBar();
  266. }
  267. });
  268. }
  269. void GMainWindow::SetDefaultUIGeometry() {
  270. // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
  271. const QRect screenRect = QApplication::desktop()->screenGeometry(this);
  272. const int w = screenRect.width() * 2 / 3;
  273. const int h = screenRect.height() / 2;
  274. const int x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
  275. const int y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
  276. setGeometry(x, y, w, h);
  277. }
  278. void GMainWindow::RestoreUIState() {
  279. restoreGeometry(UISettings::values.geometry);
  280. restoreState(UISettings::values.state);
  281. render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
  282. #if MICROPROFILE_ENABLED
  283. microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
  284. microProfileDialog->setVisible(UISettings::values.microprofile_visible);
  285. #endif
  286. game_list->LoadInterfaceLayout();
  287. ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode);
  288. ToggleWindowMode();
  289. ui.action_Fullscreen->setChecked(UISettings::values.fullscreen);
  290. ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar);
  291. OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked());
  292. ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
  293. game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
  294. ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
  295. statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
  296. Debugger::ToggleConsole();
  297. }
  298. void GMainWindow::ConnectWidgetEvents() {
  299. connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
  300. connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
  301. connect(this, &GMainWindow::EmulationStarting, render_window,
  302. &GRenderWindow::OnEmulationStarting);
  303. connect(this, &GMainWindow::EmulationStopping, render_window,
  304. &GRenderWindow::OnEmulationStopping);
  305. connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
  306. }
  307. void GMainWindow::ConnectMenuEvents() {
  308. // File
  309. connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
  310. connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
  311. connect(ui.action_Install_File_NAND, &QAction::triggered, this,
  312. &GMainWindow::OnMenuInstallToNAND);
  313. connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
  314. &GMainWindow::OnMenuSelectGameListRoot);
  315. connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
  316. // Emulation
  317. connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
  318. connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
  319. connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
  320. connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
  321. connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
  322. // View
  323. connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
  324. &GMainWindow::ToggleWindowMode);
  325. connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
  326. &GMainWindow::OnDisplayTitleBars);
  327. ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
  328. connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
  329. connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
  330. // Fullscreen
  331. ui.action_Fullscreen->setShortcut(
  332. hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
  333. connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
  334. // Help
  335. connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
  336. }
  337. void GMainWindow::OnDisplayTitleBars(bool show) {
  338. QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
  339. if (show) {
  340. for (QDockWidget* widget : widgets) {
  341. QWidget* old = widget->titleBarWidget();
  342. widget->setTitleBarWidget(nullptr);
  343. if (old != nullptr)
  344. delete old;
  345. }
  346. } else {
  347. for (QDockWidget* widget : widgets) {
  348. QWidget* old = widget->titleBarWidget();
  349. widget->setTitleBarWidget(new QWidget());
  350. if (old != nullptr)
  351. delete old;
  352. }
  353. }
  354. }
  355. bool GMainWindow::SupportsRequiredGLExtensions() {
  356. QStringList unsupported_ext;
  357. if (!GLAD_GL_ARB_program_interface_query)
  358. unsupported_ext.append("ARB_program_interface_query");
  359. if (!GLAD_GL_ARB_separate_shader_objects)
  360. unsupported_ext.append("ARB_separate_shader_objects");
  361. if (!GLAD_GL_ARB_vertex_attrib_binding)
  362. unsupported_ext.append("ARB_vertex_attrib_binding");
  363. if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
  364. unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");
  365. if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
  366. unsupported_ext.append("ARB_texture_mirror_clamp_to_edge");
  367. // Extensions required to support some texture formats.
  368. if (!GLAD_GL_EXT_texture_compression_s3tc)
  369. unsupported_ext.append("EXT_texture_compression_s3tc");
  370. if (!GLAD_GL_ARB_texture_compression_rgtc)
  371. unsupported_ext.append("ARB_texture_compression_rgtc");
  372. if (!GLAD_GL_ARB_texture_compression_bptc)
  373. unsupported_ext.append("ARB_texture_compression_bptc");
  374. if (!GLAD_GL_ARB_depth_buffer_float)
  375. unsupported_ext.append("ARB_depth_buffer_float");
  376. for (const QString& ext : unsupported_ext)
  377. LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
  378. return unsupported_ext.empty();
  379. }
  380. bool GMainWindow::LoadROM(const QString& filename) {
  381. // Shutdown previous session if the emu thread is still active...
  382. if (emu_thread != nullptr)
  383. ShutdownGame();
  384. render_window->InitRenderTarget();
  385. render_window->MakeCurrent();
  386. if (!gladLoadGL()) {
  387. QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"),
  388. tr("Your GPU may not support OpenGL 3.3, or you do not "
  389. "have the latest graphics driver."));
  390. return false;
  391. }
  392. if (!SupportsRequiredGLExtensions()) {
  393. QMessageBox::critical(
  394. this, tr("Error while initializing OpenGL Core!"),
  395. tr("Your GPU may not support one or more required OpenGL extensions. Please "
  396. "ensure you have the latest graphics driver. See the log for more details."));
  397. return false;
  398. }
  399. Core::System& system{Core::System::GetInstance()};
  400. system.SetFilesystem(vfs);
  401. system.SetGPUDebugContext(debug_context);
  402. const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
  403. render_window->DoneCurrent();
  404. if (result != Core::System::ResultStatus::Success) {
  405. switch (result) {
  406. case Core::System::ResultStatus::ErrorGetLoader:
  407. LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
  408. QMessageBox::critical(this, tr("Error while loading ROM!"),
  409. tr("The ROM format is not supported."));
  410. break;
  411. case Core::System::ResultStatus::ErrorSystemMode:
  412. LOG_CRITICAL(Frontend, "Failed to load ROM!");
  413. QMessageBox::critical(this, tr("Error while loading ROM!"),
  414. tr("Could not determine the system mode."));
  415. break;
  416. case Core::System::ResultStatus::ErrorVideoCore:
  417. QMessageBox::critical(
  418. this, tr("An error occurred initializing the video core."),
  419. tr("yuzu has encountered an error while running the video core, please see the "
  420. "log for more details."
  421. "For more information on accessing the log, please see the following page: "
  422. "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
  423. "to "
  424. "Upload the Log File</a>."
  425. "Ensure that you have the latest graphics drivers for your GPU."));
  426. break;
  427. default:
  428. if (static_cast<u32>(result) >
  429. static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) {
  430. LOG_CRITICAL(Frontend, "Failed to load ROM!");
  431. const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader);
  432. const u16 error_id = static_cast<u16>(result) - loader_id;
  433. QMessageBox::critical(
  434. this, tr("Error while loading ROM!"),
  435. QString::fromStdString(fmt::format(
  436. "While attempting to load the ROM requested, an error occured. Please "
  437. "refer to the yuzu wiki for more information or the yuzu discord for "
  438. "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
  439. loader_id, error_id, static_cast<Loader::ResultStatus>(error_id))));
  440. } else {
  441. QMessageBox::critical(
  442. this, tr("Error while loading ROM!"),
  443. tr("An unknown error occurred. Please see the log for more details."));
  444. }
  445. break;
  446. }
  447. return false;
  448. }
  449. game_path = filename;
  450. Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
  451. return true;
  452. }
  453. void GMainWindow::BootGame(const QString& filename) {
  454. LOG_INFO(Frontend, "yuzu starting...");
  455. StoreRecentFile(filename); // Put the filename on top of the list
  456. if (!LoadROM(filename))
  457. return;
  458. // Create and start the emulation thread
  459. emu_thread = std::make_unique<EmuThread>(render_window);
  460. emit EmulationStarting(emu_thread.get());
  461. render_window->moveContext();
  462. emu_thread->start();
  463. connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
  464. // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
  465. // before the CPU continues
  466. connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget,
  467. &WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
  468. connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
  469. &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
  470. // Update the GUI
  471. if (ui.action_Single_Window_Mode->isChecked()) {
  472. game_list->hide();
  473. }
  474. status_bar_update_timer.start(2000);
  475. std::string title_name;
  476. const auto res = Core::System::GetInstance().GetGameName(title_name);
  477. if (res != Loader::ResultStatus::Success)
  478. title_name = FileUtil::GetFilename(filename.toStdString());
  479. setWindowTitle(QString("yuzu %1| %4 | %2-%3")
  480. .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
  481. QString::fromStdString(title_name)));
  482. render_window->show();
  483. render_window->setFocus();
  484. emulation_running = true;
  485. if (ui.action_Fullscreen->isChecked()) {
  486. ShowFullscreen();
  487. }
  488. OnStartGame();
  489. }
  490. void GMainWindow::ShutdownGame() {
  491. emu_thread->RequestStop();
  492. emit EmulationStopping();
  493. // Wait for emulation thread to complete and delete it
  494. emu_thread->wait();
  495. emu_thread = nullptr;
  496. // The emulation is stopped, so closing the window or not does not matter anymore
  497. disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
  498. // Update the GUI
  499. ui.action_Start->setEnabled(false);
  500. ui.action_Start->setText(tr("Start"));
  501. ui.action_Pause->setEnabled(false);
  502. ui.action_Stop->setEnabled(false);
  503. ui.action_Restart->setEnabled(false);
  504. render_window->hide();
  505. game_list->show();
  506. game_list->setFilterFocus();
  507. setWindowTitle(QString("yuzu %1| %2-%3")
  508. .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
  509. // Disable status bar updates
  510. status_bar_update_timer.stop();
  511. message_label->setVisible(false);
  512. emu_speed_label->setVisible(false);
  513. game_fps_label->setVisible(false);
  514. emu_frametime_label->setVisible(false);
  515. emulation_running = false;
  516. game_path.clear();
  517. }
  518. void GMainWindow::StoreRecentFile(const QString& filename) {
  519. UISettings::values.recent_files.prepend(filename);
  520. UISettings::values.recent_files.removeDuplicates();
  521. while (UISettings::values.recent_files.size() > max_recent_files_item) {
  522. UISettings::values.recent_files.removeLast();
  523. }
  524. UpdateRecentFiles();
  525. }
  526. void GMainWindow::UpdateRecentFiles() {
  527. const int num_recent_files =
  528. std::min(UISettings::values.recent_files.size(), max_recent_files_item);
  529. for (int i = 0; i < num_recent_files; i++) {
  530. const QString text = QString("&%1. %2").arg(i + 1).arg(
  531. QFileInfo(UISettings::values.recent_files[i]).fileName());
  532. actions_recent_files[i]->setText(text);
  533. actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
  534. actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]);
  535. actions_recent_files[i]->setVisible(true);
  536. }
  537. for (int j = num_recent_files; j < max_recent_files_item; ++j) {
  538. actions_recent_files[j]->setVisible(false);
  539. }
  540. // Enable the recent files menu if the list isn't empty
  541. ui.menu_recent_files->setEnabled(num_recent_files != 0);
  542. }
  543. void GMainWindow::OnGameListLoadFile(QString game_path) {
  544. BootGame(game_path);
  545. }
  546. void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {
  547. std::string path;
  548. std::string open_target;
  549. switch (target) {
  550. case GameListOpenTarget::SaveData: {
  551. open_target = "Save Data";
  552. const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
  553. ASSERT(program_id != 0);
  554. // TODO(tech4me): Update this to work with arbitrary user profile
  555. // Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor
  556. constexpr u128 user_id = {1, 0};
  557. path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
  558. FileSys::SaveDataType::SaveData,
  559. program_id, user_id, 0);
  560. break;
  561. }
  562. default:
  563. UNIMPLEMENTED();
  564. }
  565. const QString qpath = QString::fromStdString(path);
  566. const QDir dir(qpath);
  567. if (!dir.exists()) {
  568. QMessageBox::warning(this,
  569. tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)),
  570. tr("Folder does not exist!"));
  571. return;
  572. }
  573. LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id);
  574. QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
  575. }
  576. void GMainWindow::OnMenuLoadFile() {
  577. QString extensions;
  578. for (const auto& piece : game_list->supported_file_extensions)
  579. extensions += "*." + piece + " ";
  580. extensions += "main ";
  581. QString file_filter = tr("Switch Executable") + " (" + extensions + ")";
  582. file_filter += ";;" + tr("All Files (*.*)");
  583. QString filename = QFileDialog::getOpenFileName(this, tr("Load File"),
  584. UISettings::values.roms_path, file_filter);
  585. if (!filename.isEmpty()) {
  586. UISettings::values.roms_path = QFileInfo(filename).path();
  587. BootGame(filename);
  588. }
  589. }
  590. void GMainWindow::OnMenuLoadFolder() {
  591. const QString dir_path =
  592. QFileDialog::getExistingDirectory(this, tr("Open Extracted ROM Directory"));
  593. if (dir_path.isNull()) {
  594. return;
  595. }
  596. const QDir dir{dir_path};
  597. const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files);
  598. if (matching_main.size() == 1) {
  599. BootGame(dir.path() + DIR_SEP + matching_main[0]);
  600. } else {
  601. QMessageBox::warning(this, tr("Invalid Directory Selected"),
  602. tr("The directory you have selected does not contain a 'main' file."));
  603. }
  604. }
  605. void GMainWindow::OnMenuInstallToNAND() {
  606. const QString file_filter =
  607. tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
  608. "Image (*.xci)");
  609. QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
  610. UISettings::values.roms_path, file_filter);
  611. if (filename.isEmpty()) {
  612. return;
  613. }
  614. const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
  615. if (src == nullptr || dest == nullptr)
  616. return false;
  617. if (!dest->Resize(src->GetSize()))
  618. return false;
  619. std::array<u8, 0x1000> buffer{};
  620. const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
  621. QProgressDialog progress(
  622. tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
  623. tr("Cancel"), 0, progress_maximum, this);
  624. progress.setWindowModality(Qt::WindowModal);
  625. for (size_t i = 0; i < src->GetSize(); i += buffer.size()) {
  626. if (progress.wasCanceled()) {
  627. dest->Resize(0);
  628. return false;
  629. }
  630. const int progress_value = static_cast<int>(i / buffer.size());
  631. progress.setValue(progress_value);
  632. const auto read = src->Read(buffer.data(), buffer.size(), i);
  633. dest->Write(buffer.data(), read, i);
  634. }
  635. return true;
  636. };
  637. const auto success = [this]() {
  638. QMessageBox::information(this, tr("Successfully Installed"),
  639. tr("The file was successfully installed."));
  640. game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
  641. };
  642. const auto failed = [this]() {
  643. QMessageBox::warning(
  644. this, tr("Failed to Install"),
  645. tr("There was an error while attempting to install the provided file. It "
  646. "could have an incorrect format or be missing metadata. Please "
  647. "double-check your file and try again."));
  648. };
  649. const auto overwrite = [this]() {
  650. return QMessageBox::question(this, tr("Failed to Install"),
  651. tr("The file you are attempting to install already exists "
  652. "in the cache. Would you like to overwrite it?")) ==
  653. QMessageBox::Yes;
  654. };
  655. if (filename.endsWith("xci", Qt::CaseInsensitive)) {
  656. const auto xci = std::make_shared<FileSys::XCI>(
  657. vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
  658. if (xci->GetStatus() != Loader::ResultStatus::Success) {
  659. failed();
  660. return;
  661. }
  662. const auto res =
  663. Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
  664. if (res == FileSys::InstallResult::Success) {
  665. success();
  666. } else {
  667. if (res == FileSys::InstallResult::ErrorAlreadyExists) {
  668. if (overwrite()) {
  669. const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
  670. xci, true, qt_raw_copy);
  671. if (res2 == FileSys::InstallResult::Success) {
  672. success();
  673. } else {
  674. failed();
  675. }
  676. }
  677. } else {
  678. failed();
  679. }
  680. }
  681. } else {
  682. const auto nca = std::make_shared<FileSys::NCA>(
  683. vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
  684. if (nca->GetStatus() != Loader::ResultStatus::Success) {
  685. failed();
  686. return;
  687. }
  688. const QStringList tt_options{tr("System Application"),
  689. tr("System Archive"),
  690. tr("System Application Update"),
  691. tr("Firmware Package (Type A)"),
  692. tr("Firmware Package (Type B)"),
  693. tr("Game"),
  694. tr("Game Update"),
  695. tr("Game DLC"),
  696. tr("Delta Title")};
  697. bool ok;
  698. const auto item = QInputDialog::getItem(
  699. this, tr("Select NCA Install Type..."),
  700. tr("Please select the type of title you would like to install this NCA as:\n(In "
  701. "most instances, the default 'Game' is fine.)"),
  702. tt_options, 5, false, &ok);
  703. auto index = tt_options.indexOf(item);
  704. if (!ok || index == -1) {
  705. QMessageBox::warning(this, tr("Failed to Install"),
  706. tr("The title type you selected for the NCA is invalid."));
  707. return;
  708. }
  709. if (index >= 5)
  710. index += 0x7B;
  711. const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
  712. nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
  713. if (res == FileSys::InstallResult::Success) {
  714. success();
  715. } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
  716. if (overwrite()) {
  717. const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
  718. nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
  719. if (res2 == FileSys::InstallResult::Success) {
  720. success();
  721. } else {
  722. failed();
  723. }
  724. }
  725. } else {
  726. failed();
  727. }
  728. }
  729. }
  730. void GMainWindow::OnMenuSelectGameListRoot() {
  731. QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
  732. if (!dir_path.isEmpty()) {
  733. UISettings::values.gamedir = dir_path;
  734. game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan);
  735. }
  736. }
  737. void GMainWindow::OnMenuRecentFile() {
  738. QAction* action = qobject_cast<QAction*>(sender());
  739. assert(action);
  740. const QString filename = action->data().toString();
  741. if (QFileInfo::exists(filename)) {
  742. BootGame(filename);
  743. } else {
  744. // Display an error message and remove the file from the list.
  745. QMessageBox::information(this, tr("File not found"),
  746. tr("File \"%1\" not found").arg(filename));
  747. UISettings::values.recent_files.removeOne(filename);
  748. UpdateRecentFiles();
  749. }
  750. }
  751. void GMainWindow::OnStartGame() {
  752. emu_thread->SetRunning(true);
  753. qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
  754. qRegisterMetaType<std::string>("std::string");
  755. connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
  756. ui.action_Start->setEnabled(false);
  757. ui.action_Start->setText(tr("Continue"));
  758. ui.action_Pause->setEnabled(true);
  759. ui.action_Stop->setEnabled(true);
  760. }
  761. void GMainWindow::OnPauseGame() {
  762. emu_thread->SetRunning(false);
  763. ui.action_Start->setEnabled(true);
  764. ui.action_Pause->setEnabled(false);
  765. ui.action_Stop->setEnabled(true);
  766. ui.action_Restart->setEnabled(true);
  767. }
  768. void GMainWindow::OnStopGame() {
  769. ShutdownGame();
  770. }
  771. void GMainWindow::ToggleFullscreen() {
  772. if (!emulation_running) {
  773. return;
  774. }
  775. if (ui.action_Fullscreen->isChecked()) {
  776. ShowFullscreen();
  777. } else {
  778. HideFullscreen();
  779. }
  780. }
  781. void GMainWindow::ShowFullscreen() {
  782. if (ui.action_Single_Window_Mode->isChecked()) {
  783. UISettings::values.geometry = saveGeometry();
  784. ui.menubar->hide();
  785. statusBar()->hide();
  786. showFullScreen();
  787. } else {
  788. UISettings::values.renderwindow_geometry = render_window->saveGeometry();
  789. render_window->showFullScreen();
  790. }
  791. }
  792. void GMainWindow::HideFullscreen() {
  793. if (ui.action_Single_Window_Mode->isChecked()) {
  794. statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
  795. ui.menubar->show();
  796. showNormal();
  797. restoreGeometry(UISettings::values.geometry);
  798. } else {
  799. render_window->showNormal();
  800. render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
  801. }
  802. }
  803. void GMainWindow::ToggleWindowMode() {
  804. if (ui.action_Single_Window_Mode->isChecked()) {
  805. // Render in the main window...
  806. render_window->BackupGeometry();
  807. ui.horizontalLayout->addWidget(render_window);
  808. render_window->setFocusPolicy(Qt::ClickFocus);
  809. if (emulation_running) {
  810. render_window->setVisible(true);
  811. render_window->setFocus();
  812. game_list->hide();
  813. }
  814. } else {
  815. // Render in a separate window...
  816. ui.horizontalLayout->removeWidget(render_window);
  817. render_window->setParent(nullptr);
  818. render_window->setFocusPolicy(Qt::NoFocus);
  819. if (emulation_running) {
  820. render_window->setVisible(true);
  821. render_window->RestoreGeometry();
  822. game_list->show();
  823. }
  824. }
  825. }
  826. void GMainWindow::OnConfigure() {
  827. ConfigureDialog configureDialog(this, hotkey_registry);
  828. auto old_theme = UISettings::values.theme;
  829. auto result = configureDialog.exec();
  830. if (result == QDialog::Accepted) {
  831. configureDialog.applyConfiguration();
  832. if (UISettings::values.theme != old_theme)
  833. UpdateUITheme();
  834. game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
  835. config->Save();
  836. }
  837. }
  838. void GMainWindow::OnAbout() {
  839. AboutDialog aboutDialog(this);
  840. aboutDialog.exec();
  841. }
  842. void GMainWindow::OnToggleFilterBar() {
  843. game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
  844. if (ui.action_Show_Filter_Bar->isChecked()) {
  845. game_list->setFilterFocus();
  846. } else {
  847. game_list->clearFilter();
  848. }
  849. }
  850. void GMainWindow::UpdateStatusBar() {
  851. if (emu_thread == nullptr) {
  852. status_bar_update_timer.stop();
  853. return;
  854. }
  855. auto results = Core::System::GetInstance().GetAndResetPerfStats();
  856. if (Settings::values.use_frame_limit) {
  857. emu_speed_label->setText(tr("Speed: %1% / %2%")
  858. .arg(results.emulation_speed * 100.0, 0, 'f', 0)
  859. .arg(Settings::values.frame_limit));
  860. } else {
  861. emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
  862. }
  863. game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
  864. emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
  865. emu_speed_label->setVisible(true);
  866. game_fps_label->setVisible(true);
  867. emu_frametime_label->setVisible(true);
  868. }
  869. void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
  870. QMessageBox::StandardButton answer;
  871. QString status_message;
  872. const QString common_message = tr(
  873. "The game you are trying to load requires additional files from your Switch to be dumped "
  874. "before playing.<br/><br/>For more information on dumping these files, please see the "
  875. "following wiki page: <a "
  876. "href='https://yuzu-emu.org/wiki/"
  877. "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
  878. "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
  879. "back to the game list? Continuing emulation may result in crashes, corrupted save "
  880. "data, or other bugs.");
  881. switch (result) {
  882. case Core::System::ResultStatus::ErrorSystemFiles: {
  883. QString message = "yuzu was unable to locate a Switch system archive";
  884. if (!details.empty()) {
  885. message.append(tr(": %1. ").arg(details.c_str()));
  886. } else {
  887. message.append(". ");
  888. }
  889. message.append(common_message);
  890. answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
  891. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  892. status_message = "System Archive Missing";
  893. break;
  894. }
  895. case Core::System::ResultStatus::ErrorSharedFont: {
  896. QString message = tr("yuzu was unable to locate the Switch shared fonts. ");
  897. message.append(common_message);
  898. answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
  899. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  900. status_message = "Shared Font Missing";
  901. break;
  902. }
  903. default:
  904. answer = QMessageBox::question(
  905. this, tr("Fatal Error"),
  906. tr("yuzu has encountered a fatal error, please see the log for more details. "
  907. "For more information on accessing the log, please see the following page: "
  908. "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
  909. "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
  910. "Continuing emulation may result in crashes, corrupted save data, or other bugs."),
  911. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  912. status_message = "Fatal Error encountered";
  913. break;
  914. }
  915. if (answer == QMessageBox::Yes) {
  916. if (emu_thread) {
  917. ShutdownGame();
  918. }
  919. } else {
  920. // Only show the message if the game is still running.
  921. if (emu_thread) {
  922. emu_thread->SetRunning(true);
  923. message_label->setText(status_message);
  924. message_label->setVisible(true);
  925. }
  926. }
  927. }
  928. bool GMainWindow::ConfirmClose() {
  929. if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
  930. return true;
  931. QMessageBox::StandardButton answer =
  932. QMessageBox::question(this, tr("yuzu"), tr("Are you sure you want to close yuzu?"),
  933. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  934. return answer != QMessageBox::No;
  935. }
  936. void GMainWindow::closeEvent(QCloseEvent* event) {
  937. if (!ConfirmClose()) {
  938. event->ignore();
  939. return;
  940. }
  941. if (ui.action_Fullscreen->isChecked()) {
  942. UISettings::values.geometry = saveGeometry();
  943. UISettings::values.renderwindow_geometry = render_window->saveGeometry();
  944. }
  945. UISettings::values.state = saveState();
  946. #if MICROPROFILE_ENABLED
  947. UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
  948. UISettings::values.microprofile_visible = microProfileDialog->isVisible();
  949. #endif
  950. UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked();
  951. UISettings::values.fullscreen = ui.action_Fullscreen->isChecked();
  952. UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked();
  953. UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked();
  954. UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked();
  955. UISettings::values.first_start = false;
  956. game_list->SaveInterfaceLayout();
  957. hotkey_registry.SaveHotkeys();
  958. // Shutdown session if the emu thread is active...
  959. if (emu_thread != nullptr)
  960. ShutdownGame();
  961. render_window->close();
  962. QWidget::closeEvent(event);
  963. }
  964. static bool IsSingleFileDropEvent(QDropEvent* event) {
  965. const QMimeData* mimeData = event->mimeData();
  966. return mimeData->hasUrls() && mimeData->urls().length() == 1;
  967. }
  968. void GMainWindow::dropEvent(QDropEvent* event) {
  969. if (IsSingleFileDropEvent(event) && ConfirmChangeGame()) {
  970. const QMimeData* mimeData = event->mimeData();
  971. QString filename = mimeData->urls().at(0).toLocalFile();
  972. BootGame(filename);
  973. }
  974. }
  975. void GMainWindow::dragEnterEvent(QDragEnterEvent* event) {
  976. if (IsSingleFileDropEvent(event)) {
  977. event->acceptProposedAction();
  978. }
  979. }
  980. void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
  981. event->acceptProposedAction();
  982. }
  983. bool GMainWindow::ConfirmChangeGame() {
  984. if (emu_thread == nullptr)
  985. return true;
  986. auto answer = QMessageBox::question(
  987. this, tr("yuzu"),
  988. tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
  989. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  990. return answer != QMessageBox::No;
  991. }
  992. void GMainWindow::filterBarSetChecked(bool state) {
  993. ui.action_Show_Filter_Bar->setChecked(state);
  994. emit(OnToggleFilterBar());
  995. }
  996. void GMainWindow::UpdateUITheme() {
  997. QStringList theme_paths(default_theme_paths);
  998. if (UISettings::values.theme != UISettings::themes[0].second &&
  999. !UISettings::values.theme.isEmpty()) {
  1000. const QString theme_uri(":" + UISettings::values.theme + "/style.qss");
  1001. QFile f(theme_uri);
  1002. if (f.open(QFile::ReadOnly | QFile::Text)) {
  1003. QTextStream ts(&f);
  1004. qApp->setStyleSheet(ts.readAll());
  1005. GMainWindow::setStyleSheet(ts.readAll());
  1006. } else {
  1007. LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
  1008. }
  1009. theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme});
  1010. QIcon::setThemeName(":/icons/" + UISettings::values.theme);
  1011. } else {
  1012. qApp->setStyleSheet("");
  1013. GMainWindow::setStyleSheet("");
  1014. theme_paths.append(QStringList{":/icons/default"});
  1015. QIcon::setThemeName(":/icons/default");
  1016. }
  1017. QIcon::setThemeSearchPaths(theme_paths);
  1018. emit UpdateThemedIcons();
  1019. }
  1020. #ifdef main
  1021. #undef main
  1022. #endif
  1023. int main(int argc, char* argv[]) {
  1024. MicroProfileOnThreadCreate("Frontend");
  1025. SCOPE_EXIT({ MicroProfileShutdown(); });
  1026. // Init settings params
  1027. QCoreApplication::setOrganizationName("yuzu team");
  1028. QCoreApplication::setApplicationName("yuzu");
  1029. QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
  1030. QApplication app(argc, argv);
  1031. // Qt changes the locale and causes issues in float conversion using std::to_string() when
  1032. // generating shaders
  1033. setlocale(LC_ALL, "C");
  1034. GMainWindow main_window;
  1035. // After settings have been loaded by GMainWindow, apply the filter
  1036. main_window.show();
  1037. return app.exec();
  1038. }