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. 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. // Dolphin currently doesn't work on Wayland (Only the UI does, games do not launch.) This makes
  128. // XCB the default and forces it on if the platform is specified to be wayland, to prevent this
  129. // from happening.
  130. // For more information: https://bugs.dolphin-emu.org/issues/11807
  131. const char* current_qt_platform = getenv("QT_QPA_PLATFORM");
  132. const bool replace_qt_platform =
  133. (current_qt_platform && strcasecmp(current_qt_platform, "wayland") == 0);
  134. setenv("QT_QPA_PLATFORM", "xcb", replace_qt_platform);
  135. #endif
  136. QCoreApplication::setOrganizationName(QStringLiteral("Dolphin Emulator"));
  137. QCoreApplication::setOrganizationDomain(QStringLiteral("dolphin-emu.org"));
  138. QCoreApplication::setApplicationName(QStringLiteral("dolphin-emu"));
  139. // QApplication will parse arguments and remove any it recognizes as targeting Qt
  140. QApplication app(argc, argv);
  141. auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::IncludeGUIOptions);
  142. const optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv);
  143. const std::vector<std::string> args = parser->args();
  144. #ifdef _WIN32
  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. SetQWidgetWindowDecorations(&analytics_prompt);
  244. const int answer = analytics_prompt.exec();
  245. Config::SetBase(Config::MAIN_ANALYTICS_PERMISSION_ASKED, true);
  246. Settings::Instance().SetAnalyticsEnabled(answer == QMessageBox::Yes);
  247. DolphinAnalytics::Instance().ReloadConfig();
  248. }
  249. #endif
  250. if (!Settings::Instance().IsBatchModeEnabled())
  251. {
  252. auto* updater = new Updater(&win, Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK),
  253. Config::Get(Config::MAIN_AUTOUPDATE_HASH_OVERRIDE));
  254. updater->start();
  255. }
  256. retval = app.exec();
  257. }
  258. Core::Shutdown(Core::System::GetInstance());
  259. UICommon::Shutdown();
  260. Host::GetInstance()->deleteLater();
  261. return retval;
  262. }
  263. #ifdef _WIN32
  264. int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int)
  265. {
  266. std::vector<std::string> args = Common::CommandLineToUtf8Argv(GetCommandLineW());
  267. const int argc = static_cast<int>(args.size());
  268. std::vector<char*> argv(args.size());
  269. for (size_t i = 0; i < args.size(); ++i)
  270. argv[i] = args[i].data();
  271. return main(argc, argv.data());
  272. }
  273. #undef main
  274. #endif