Main.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Copyright 2015 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #ifdef _WIN32
  4. #include <cstdio>
  5. #include <string>
  6. #include <vector>
  7. #include <Windows.h>
  8. #endif
  9. #ifdef __linux__
  10. #include <cstdlib>
  11. #endif
  12. #include <OptionParser.h>
  13. #include <QAbstractEventDispatcher>
  14. #include <QApplication>
  15. #include <QObject>
  16. #include <QPushButton>
  17. #include <QWidget>
  18. #include "Common/Config/Config.h"
  19. #include "Common/MsgHandler.h"
  20. #include "Common/ScopeGuard.h"
  21. #include "Core/Boot/Boot.h"
  22. #include "Core/Config/MainSettings.h"
  23. #include "Core/Core.h"
  24. #include "Core/DolphinAnalytics.h"
  25. #include "Core/System.h"
  26. #include "DolphinQt/Host.h"
  27. #include "DolphinQt/MainWindow.h"
  28. #include "DolphinQt/QtUtils/ModalMessageBox.h"
  29. #include "DolphinQt/QtUtils/RunOnObject.h"
  30. #include "DolphinQt/QtUtils/SetWindowDecorations.h"
  31. #include "DolphinQt/Resources.h"
  32. #include "DolphinQt/Settings.h"
  33. #include "DolphinQt/Translation.h"
  34. #include "DolphinQt/Updater.h"
  35. #include "UICommon/CommandLineParse.h"
  36. #include "UICommon/UICommon.h"
  37. static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no,
  38. Common::MsgType style)
  39. {
  40. const bool called_from_cpu_thread = Core::IsCPUThread();
  41. const bool called_from_gpu_thread = Core::IsGPUThread();
  42. std::optional<bool> r = RunOnObject(QApplication::instance(), [&] {
  43. // If we were called from the CPU/GPU thread, set us as the CPU/GPU thread.
  44. // This information is used in order to avoid deadlocks when calling e.g.
  45. // Host::SetRenderFocus or Core::CPUThreadGuard. (Host::SetRenderFocus
  46. // can get called automatically when a dialog steals the focus.)
  47. Common::ScopeGuard cpu_scope_guard(&Core::UndeclareAsCPUThread);
  48. Common::ScopeGuard gpu_scope_guard(&Core::UndeclareAsGPUThread);
  49. if (!called_from_cpu_thread)
  50. cpu_scope_guard.Dismiss();
  51. if (!called_from_gpu_thread)
  52. gpu_scope_guard.Dismiss();
  53. if (called_from_cpu_thread)
  54. Core::DeclareAsCPUThread();
  55. if (called_from_gpu_thread)
  56. Core::DeclareAsGPUThread();
  57. ModalMessageBox message_box(QApplication::activeWindow(), Qt::ApplicationModal);
  58. message_box.setWindowTitle(QString::fromUtf8(caption));
  59. message_box.setText(QString::fromUtf8(text));
  60. message_box.setStandardButtons(yes_no ? QMessageBox::Yes | QMessageBox::No : QMessageBox::Ok);
  61. if (style == Common::MsgType::Warning)
  62. message_box.addButton(QMessageBox::Ignore)->setText(QObject::tr("Ignore for this session"));
  63. message_box.setIcon([&] {
  64. switch (style)
  65. {
  66. case Common::MsgType::Information:
  67. return QMessageBox::Information;
  68. case Common::MsgType::Question:
  69. return QMessageBox::Question;
  70. case Common::MsgType::Warning:
  71. return QMessageBox::Warning;
  72. case Common::MsgType::Critical:
  73. return QMessageBox::Critical;
  74. }
  75. // appease MSVC
  76. return QMessageBox::NoIcon;
  77. }());
  78. SetQWidgetWindowDecorations(&message_box);
  79. const int button = message_box.exec();
  80. if (button == QMessageBox::Yes)
  81. return true;
  82. if (button == QMessageBox::Ignore)
  83. {
  84. Config::SetCurrent(Config::MAIN_USE_PANIC_HANDLERS, false);
  85. return true;
  86. }
  87. return false;
  88. });
  89. if (r.has_value())
  90. return *r;
  91. return false;
  92. }
  93. #ifdef _WIN32
  94. #define main app_main
  95. #endif
  96. int main(int argc, char* argv[])
  97. {
  98. #ifdef _WIN32
  99. const bool console_attached = AttachConsole(ATTACH_PARENT_PROCESS) != FALSE;
  100. HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
  101. if (console_attached && stdout_handle)
  102. {
  103. freopen("CONOUT$", "w", stdout);
  104. freopen("CONOUT$", "w", stderr);
  105. }
  106. #endif
  107. Core::DeclareAsHostThread();
  108. #ifdef __APPLE__
  109. // On macOS, a command line option matching the format "-psn_X_XXXXXX" is passed when
  110. // the application is launched for the first time. This is to set the "ProcessSerialNumber",
  111. // something used by the legacy Process Manager from Carbon. optparse will fail if it finds
  112. // this as it isn't a valid Dolphin command line option, so pretend like it doesn't exist
  113. // if found.
  114. if (strncmp(argv[argc - 1], "-psn", 4) == 0)
  115. {
  116. argc--;
  117. }
  118. #endif
  119. #ifdef __linux__
  120. // Qt 6.3+ has a bug which causes mouse inputs to not be registered in our XInput2 code.
  121. // If we define QT_XCB_NO_XI2, Qt's xcb platform plugin no longer initializes its XInput
  122. // code, which makes mouse inputs work again.
  123. // For more information: https://bugs.dolphin-emu.org/issues/12913
  124. #if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
  125. setenv("QT_XCB_NO_XI2", "1", true);
  126. #endif
  127. #endif
  128. QCoreApplication::setOrganizationName(QStringLiteral("Dolphin Emulator"));
  129. QCoreApplication::setOrganizationDomain(QStringLiteral("dolphin-emu.org"));
  130. QCoreApplication::setApplicationName(QStringLiteral("dolphin-emu"));
  131. // QApplication will parse arguments and remove any it recognizes as targeting Qt
  132. QApplication app(argc, argv);
  133. auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::IncludeGUIOptions);
  134. const optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
  135. const std::vector<std::string> args = parser->args();
  136. #ifdef _WIN32
  137. FreeConsole();
  138. #endif
  139. UICommon::SetUserDirectory(static_cast<const char*>(options.get("user")));
  140. UICommon::CreateDirectories();
  141. UICommon::Init();
  142. Resources::Init();
  143. Settings::Instance().SetBatchModeEnabled(options.is_set("batch"));
  144. // Hook up alerts from core
  145. Common::RegisterMsgAlertHandler(QtMsgAlertHandler);
  146. // Hook up translations
  147. Translation::Initialize();
  148. // Whenever the event loop is about to go to sleep, dispatch the jobs
  149. // queued in the Core first.
  150. QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
  151. &app, [] { Core::HostDispatchJobs(Core::System::GetInstance()); });
  152. std::optional<std::string> save_state_path;
  153. if (options.is_set("save_state"))
  154. {
  155. save_state_path = static_cast<const char*>(options.get("save_state"));
  156. }
  157. std::unique_ptr<BootParameters> boot;
  158. bool game_specified = false;
  159. if (options.is_set("exec"))
  160. {
  161. const std::list<std::string> paths_list = options.all("exec");
  162. const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
  163. std::make_move_iterator(std::end(paths_list))};
  164. boot = BootParameters::GenerateFromFile(
  165. paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
  166. game_specified = true;
  167. }
  168. else if (options.is_set("nand_title"))
  169. {
  170. const std::string hex_string = static_cast<const char*>(options.get("nand_title"));
  171. if (hex_string.length() == 16)
  172. {
  173. const u64 title_id = std::stoull(hex_string, nullptr, 16);
  174. boot = std::make_unique<BootParameters>(BootParameters::NANDTitle{title_id});
  175. }
  176. else
  177. {
  178. ModalMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("Invalid title ID."));
  179. }
  180. game_specified = true;
  181. }
  182. else if (!args.empty())
  183. {
  184. boot = BootParameters::GenerateFromFile(
  185. args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
  186. game_specified = true;
  187. }
  188. int retval;
  189. if (save_state_path && !game_specified)
  190. {
  191. ModalMessageBox::critical(
  192. nullptr, QObject::tr("Error"),
  193. QObject::tr("A save state cannot be loaded without specifying a game to launch."));
  194. retval = 1;
  195. }
  196. else if (Settings::Instance().IsBatchModeEnabled() && !game_specified)
  197. {
  198. ModalMessageBox::critical(
  199. nullptr, QObject::tr("Error"),
  200. QObject::tr("Batch mode cannot be used without specifying a game to launch."));
  201. retval = 1;
  202. }
  203. else if (!boot && (Settings::Instance().IsBatchModeEnabled() || save_state_path))
  204. {
  205. // A game to launch was specified, but it was invalid.
  206. // An error has already been shown by code above, so exit without showing another error.
  207. retval = 1;
  208. }
  209. else
  210. {
  211. DolphinAnalytics::Instance().ReportDolphinStart("qt");
  212. Settings::Instance().InitDefaultPalette();
  213. Settings::Instance().ApplyStyle();
  214. MainWindow win{Core::System::GetInstance(), std::move(boot),
  215. static_cast<const char*>(options.get("movie"))};
  216. #if defined(USE_ANALYTICS) && USE_ANALYTICS
  217. if (!Config::Get(Config::MAIN_ANALYTICS_PERMISSION_ASKED))
  218. {
  219. ModalMessageBox analytics_prompt(&win);
  220. analytics_prompt.setIcon(QMessageBox::Question);
  221. analytics_prompt.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  222. analytics_prompt.setWindowTitle(QObject::tr("Allow Usage Statistics Reporting"));
  223. analytics_prompt.setText(
  224. QObject::tr("Do you authorize Dolphin to report information to Dolphin's developers?"));
  225. analytics_prompt.setInformativeText(
  226. QObject::tr("If authorized, Dolphin can collect data on its performance, "
  227. "feature usage, and configuration, as well as data on your system's "
  228. "hardware and operating system.\n\n"
  229. "No private data is ever collected. This data helps us understand "
  230. "how people and emulated games use Dolphin and prioritize our "
  231. "efforts. It also helps us identify rare configurations that are "
  232. "causing bugs, performance and stability issues.\n"
  233. "This authorization can be revoked at any time through Dolphin's "
  234. "settings."));
  235. SetQWidgetWindowDecorations(&analytics_prompt);
  236. const int answer = analytics_prompt.exec();
  237. Config::SetBase(Config::MAIN_ANALYTICS_PERMISSION_ASKED, true);
  238. Settings::Instance().SetAnalyticsEnabled(answer == QMessageBox::Yes);
  239. DolphinAnalytics::Instance().ReloadConfig();
  240. }
  241. #endif
  242. if (!Settings::Instance().IsBatchModeEnabled())
  243. {
  244. auto* updater = new Updater(&win, Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK),
  245. Config::Get(Config::MAIN_AUTOUPDATE_HASH_OVERRIDE));
  246. updater->start();
  247. }
  248. retval = app.exec();
  249. }
  250. Core::Shutdown(Core::System::GetInstance());
  251. UICommon::Shutdown();
  252. Host::GetInstance()->deleteLater();
  253. return retval;
  254. }
  255. #ifdef _WIN32
  256. int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int)
  257. {
  258. std::vector<std::string> args = Common::CommandLineToUtf8Argv(GetCommandLineW());
  259. const int argc = static_cast<int>(args.size());
  260. std::vector<char*> argv(args.size());
  261. for (size_t i = 0; i < args.size(); ++i)
  262. argv[i] = args[i].data();
  263. return main(argc, argv.data());
  264. }
  265. #undef main
  266. #endif