Main.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. const int button = message_box.exec();
  79. if (button == QMessageBox::Yes)
  80. return true;
  81. if (button == QMessageBox::Ignore)
  82. {
  83. Config::SetCurrent(Config::MAIN_USE_PANIC_HANDLERS, false);
  84. return true;
  85. }
  86. return false;
  87. });
  88. if (r.has_value())
  89. return *r;
  90. return false;
  91. }
  92. #ifdef _WIN32
  93. #define main app_main
  94. #endif
  95. int main(int argc, char* argv[])
  96. {
  97. #ifdef _WIN32
  98. const bool console_attached = AttachConsole(ATTACH_PARENT_PROCESS) != FALSE;
  99. HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
  100. if (console_attached && stdout_handle)
  101. {
  102. freopen("CONOUT$", "w", stdout);
  103. freopen("CONOUT$", "w", stderr);
  104. }
  105. #endif
  106. Core::DeclareAsHostThread();
  107. #ifdef __APPLE__
  108. // On macOS, a command line option matching the format "-psn_X_XXXXXX" is passed when
  109. // the application is launched for the first time. This is to set the "ProcessSerialNumber",
  110. // something used by the legacy Process Manager from Carbon. optparse will fail if it finds
  111. // this as it isn't a valid Dolphin command line option, so pretend like it doesn't exist
  112. // if found.
  113. if (strncmp(argv[argc - 1], "-psn", 4) == 0)
  114. {
  115. argc--;
  116. }
  117. #endif
  118. #ifdef __linux__
  119. // Qt 6.3+ has a bug which causes mouse inputs to not be registered in our XInput2 code.
  120. // If we define QT_XCB_NO_XI2, Qt's xcb platform plugin no longer initializes its XInput
  121. // code, which makes mouse inputs work again.
  122. // For more information: https://bugs.dolphin-emu.org/issues/12913
  123. #if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
  124. setenv("QT_XCB_NO_XI2", "1", true);
  125. #endif
  126. // Dolphin currently doesn't work on Wayland (Only the UI does, games do not launch.) This makes
  127. // XCB the default and forces it on if the platform is specified to be wayland, to prevent this
  128. // from happening.
  129. // For more information: https://bugs.dolphin-emu.org/issues/11807
  130. const char* current_qt_platform = getenv("QT_QPA_PLATFORM");
  131. const bool replace_qt_platform =
  132. (current_qt_platform && strcasecmp(current_qt_platform, "wayland") == 0);
  133. setenv("QT_QPA_PLATFORM", "xcb", replace_qt_platform);
  134. #endif
  135. QCoreApplication::setOrganizationName(QStringLiteral("Dolphin Emulator"));
  136. QCoreApplication::setOrganizationDomain(QStringLiteral("dolphin-emu.org"));
  137. QCoreApplication::setApplicationName(QStringLiteral("dolphin-emu"));
  138. // QApplication will parse arguments and remove any it recognizes as targeting Qt
  139. QApplication app(argc, argv);
  140. auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::IncludeGUIOptions);
  141. const optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
  142. const std::vector<std::string> args = parser->args();
  143. #ifdef _WIN32
  144. QtUtils::InstallWindowDecorationFilter(&app);
  145. FreeConsole();
  146. #endif
  147. UICommon::SetUserDirectory(static_cast<const char*>(options.get("user")));
  148. UICommon::CreateDirectories();
  149. UICommon::Init();
  150. Resources::Init();
  151. Settings::Instance().SetBatchModeEnabled(options.is_set("batch"));
  152. // Hook up alerts from core
  153. Common::RegisterMsgAlertHandler(QtMsgAlertHandler);
  154. // Hook up translations
  155. Translation::Initialize();
  156. // Whenever the event loop is about to go to sleep, dispatch the jobs
  157. // queued in the Core first.
  158. QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
  159. &app, [] { Core::HostDispatchJobs(Core::System::GetInstance()); });
  160. std::optional<std::string> save_state_path;
  161. if (options.is_set("save_state"))
  162. {
  163. save_state_path = static_cast<const char*>(options.get("save_state"));
  164. }
  165. std::unique_ptr<BootParameters> boot;
  166. bool game_specified = false;
  167. if (options.is_set("exec"))
  168. {
  169. const std::list<std::string> paths_list = options.all("exec");
  170. const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
  171. std::make_move_iterator(std::end(paths_list))};
  172. boot = BootParameters::GenerateFromFile(
  173. paths, BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
  174. game_specified = true;
  175. }
  176. else if (options.is_set("nand_title"))
  177. {
  178. const std::string hex_string = static_cast<const char*>(options.get("nand_title"));
  179. if (hex_string.length() == 16)
  180. {
  181. const u64 title_id = std::stoull(hex_string, nullptr, 16);
  182. boot = std::make_unique<BootParameters>(BootParameters::NANDTitle{title_id});
  183. }
  184. else
  185. {
  186. ModalMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("Invalid title ID."));
  187. }
  188. game_specified = true;
  189. }
  190. else if (!args.empty())
  191. {
  192. boot = BootParameters::GenerateFromFile(
  193. args.front(), BootSessionData(save_state_path, DeleteSavestateAfterBoot::No));
  194. game_specified = true;
  195. }
  196. int retval;
  197. if (save_state_path && !game_specified)
  198. {
  199. ModalMessageBox::critical(
  200. nullptr, QObject::tr("Error"),
  201. QObject::tr("A save state cannot be loaded without specifying a game to launch."));
  202. retval = 1;
  203. }
  204. else if (Settings::Instance().IsBatchModeEnabled() && !game_specified)
  205. {
  206. ModalMessageBox::critical(
  207. nullptr, QObject::tr("Error"),
  208. QObject::tr("Batch mode cannot be used without specifying a game to launch."));
  209. retval = 1;
  210. }
  211. else if (!boot && (Settings::Instance().IsBatchModeEnabled() || save_state_path))
  212. {
  213. // A game to launch was specified, but it was invalid.
  214. // An error has already been shown by code above, so exit without showing another error.
  215. retval = 1;
  216. }
  217. else
  218. {
  219. DolphinAnalytics::Instance().ReportDolphinStart("qt");
  220. Settings::Instance().InitDefaultPalette();
  221. Settings::Instance().ApplyStyle();
  222. MainWindow win{Core::System::GetInstance(), std::move(boot),
  223. static_cast<const char*>(options.get("movie"))};
  224. #if defined(USE_ANALYTICS) && USE_ANALYTICS
  225. if (!Config::Get(Config::MAIN_ANALYTICS_PERMISSION_ASKED))
  226. {
  227. ModalMessageBox analytics_prompt(&win);
  228. analytics_prompt.setIcon(QMessageBox::Question);
  229. analytics_prompt.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  230. analytics_prompt.setWindowTitle(QObject::tr("Allow Usage Statistics Reporting"));
  231. analytics_prompt.setText(
  232. QObject::tr("Do you authorize Dolphin to report information to Dolphin's developers?"));
  233. analytics_prompt.setInformativeText(
  234. QObject::tr("If authorized, Dolphin can collect data on its performance, "
  235. "feature usage, and configuration, as well as data on your system's "
  236. "hardware and operating system.\n\n"
  237. "No private data is ever collected. This data helps us understand "
  238. "how people and emulated games use Dolphin and prioritize our "
  239. "efforts. It also helps us identify rare configurations that are "
  240. "causing bugs, performance and stability issues.\n"
  241. "This authorization can be revoked at any time through Dolphin's "
  242. "settings."));
  243. const int answer = analytics_prompt.exec();
  244. Config::SetBase(Config::MAIN_ANALYTICS_PERMISSION_ASKED, true);
  245. Settings::Instance().SetAnalyticsEnabled(answer == QMessageBox::Yes);
  246. DolphinAnalytics::Instance().ReloadConfig();
  247. }
  248. #endif
  249. if (!Settings::Instance().IsBatchModeEnabled())
  250. {
  251. auto* updater = new Updater(&win, Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK),
  252. Config::Get(Config::MAIN_AUTOUPDATE_HASH_OVERRIDE));
  253. updater->start();
  254. }
  255. retval = app.exec();
  256. }
  257. Core::Shutdown(Core::System::GetInstance());
  258. UICommon::Shutdown();
  259. Host::GetInstance()->deleteLater();
  260. return retval;
  261. }
  262. #ifdef _WIN32
  263. int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int)
  264. {
  265. std::vector<std::string> args = Common::CommandLineToUtf8Argv(GetCommandLineW());
  266. const int argc = static_cast<int>(args.size());
  267. std::vector<char*> argv(args.size());
  268. for (size_t i = 0; i < args.size(); ++i)
  269. argv[i] = args[i].data();
  270. return main(argc, argv.data());
  271. }
  272. #undef main
  273. #endif