main.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #include <locale.h>
  2. #include <QGuiApplication>
  3. #include <QApplication>
  4. #include <QFileInfo>
  5. #include <QIcon>
  6. #include <QtQml>
  7. #include <QtWebEngine/qtwebengineglobal.h>
  8. #include <QErrorMessage>
  9. #include <QCommandLineOption>
  10. #include <QDebug>
  11. #include "shared/Names.h"
  12. #include "system/SystemComponent.h"
  13. #include "Paths.h"
  14. #include "player/CodecsComponent.h"
  15. #include "player/PlayerComponent.h"
  16. #include "player/OpenGLDetect.h"
  17. #include "Version.h"
  18. #include "settings/SettingsComponent.h"
  19. #include "settings/SettingsSection.h"
  20. #include "ui/KonvergoWindow.h"
  21. #include "ui/KonvergoWindow.h"
  22. #include "Globals.h"
  23. #include "ui/ErrorMessage.h"
  24. #include "UniqueApplication.h"
  25. #include "utils/Log.h"
  26. #ifdef Q_OS_MAC
  27. #include "PFMoveApplication.h"
  28. #endif
  29. #if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
  30. #include "SignalManager.h"
  31. #endif
  32. /////////////////////////////////////////////////////////////////////////////////////////
  33. static void preinitQt()
  34. {
  35. QCoreApplication::setApplicationName(Names::MainName());
  36. QCoreApplication::setApplicationVersion(Version::GetVersionString());
  37. QCoreApplication::setOrganizationDomain("jellyfin.org");
  38. #ifdef Q_OS_WIN32
  39. QVariant useOpengl = SettingsComponent::readPreinitValue(SETTINGS_SECTION_MAIN, "useOpenGL");
  40. // Warning: this must be the same as the default value as declared in
  41. // the settings_description.json file, or confusion will result.
  42. if (useOpengl.type() != QMetaType::Bool)
  43. useOpengl = false;
  44. if (useOpengl.toBool())
  45. QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
  46. else
  47. QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
  48. #endif
  49. }
  50. /////////////////////////////////////////////////////////////////////////////////////////
  51. char** appendCommandLineArguments(int argc, char **argv, const QStringList& args)
  52. {
  53. size_t newSize = (argc + args.length() + 1) * sizeof(char*);
  54. char** newArgv = (char**)calloc(1, newSize);
  55. memcpy(newArgv, argv, (size_t)(argc * sizeof(char*)));
  56. int pos = argc;
  57. for(const QString& str : args)
  58. newArgv[pos++] = qstrdup(str.toUtf8().data());
  59. return newArgv;
  60. }
  61. /////////////////////////////////////////////////////////////////////////////////////////
  62. void ShowLicenseInfo()
  63. {
  64. QFile licenses(":/misc/licenses.txt");
  65. licenses.open(QIODevice::ReadOnly | QIODevice::Text);
  66. QByteArray contents = licenses.readAll();
  67. printf("%.*s\n", contents.size(), contents.data());
  68. }
  69. /////////////////////////////////////////////////////////////////////////////////////////
  70. QStringList g_qtFlags = {
  71. "--disable-web-security",
  72. "--enable-gpu-rasterization"
  73. };
  74. /////////////////////////////////////////////////////////////////////////////////////////
  75. int main(int argc, char *argv[])
  76. {
  77. try
  78. {
  79. QCommandLineParser parser;
  80. parser.setApplicationDescription("Jellyfin Media Player");
  81. parser.addHelpOption();
  82. parser.addVersionOption();
  83. parser.addOptions({{{"l", "licenses"}, "Show license information"},
  84. {"desktop", "Start in desktop mode"},
  85. {"tv", "Start in TV mode"},
  86. {"windowed", "Start in windowed mode"},
  87. {"fullscreen", "Start in fullscreen"},
  88. {"terminal", "Log to terminal"},
  89. {"disable-gpu", "Disable QtWebEngine gpu accel"},
  90. {"force-external-webclient","Use webclient provided by server"}});
  91. auto scaleOption = QCommandLineOption("scale-factor", "Set to a integer or default auto which controls" \
  92. "the scale (DPI) of the desktop interface.");
  93. scaleOption.setValueName("scale");
  94. scaleOption.setDefaultValue("auto");
  95. auto platformOption = QCommandLineOption("platform", "Equivalant to QT_QPA_PLATFORM.");
  96. platformOption.setValueName("platform");
  97. platformOption.setDefaultValue("default");
  98. auto devOption = QCommandLineOption("remote-debugging-port", "Port number for devtools.");
  99. devOption.setValueName("port");
  100. parser.addOption(scaleOption);
  101. parser.addOption(devOption);
  102. parser.addOption(platformOption);
  103. char **newArgv = appendCommandLineArguments(argc, argv, g_qtFlags);
  104. int newArgc = argc + g_qtFlags.size();
  105. // Qt calls setlocale(LC_ALL, "") in a bunch of places, which breaks
  106. // float/string processing in mpv and ffmpeg.
  107. #ifdef Q_OS_UNIX
  108. qputenv("LC_ALL", "C");
  109. qputenv("LC_NUMERIC", "C");
  110. #endif
  111. preinitQt();
  112. detectOpenGLEarly();
  113. QStringList arguments;
  114. for (int i = 0; i < argc; i++)
  115. arguments << QString::fromLatin1(argv[i]);
  116. {
  117. // This is kinda dumb. But in order for the QCommandLineParser
  118. // to work properly we need to init if before we call process
  119. // but we don't want to do that for the main application since
  120. // we need to set the scale factor before we do that. So it becomes
  121. // a small chicken-or-egg problem, which we "solve" by making
  122. // this temporary console app.
  123. //
  124. QCoreApplication core(newArgc, newArgv);
  125. // Now parse the command line.
  126. parser.process(arguments);
  127. }
  128. if (parser.isSet("licenses"))
  129. {
  130. ShowLicenseInfo();
  131. return EXIT_SUCCESS;
  132. }
  133. auto scale = parser.value("scale-factor");
  134. if (scale.isEmpty() || scale == "auto")
  135. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
  136. else if (scale != "none")
  137. qputenv("QT_SCALE_FACTOR", scale.toUtf8());
  138. auto platform = parser.value("platform");
  139. if (!(platform.isEmpty() || platform == "default"))
  140. {
  141. qputenv("QT_QPA_PLATFORM", platform.toUtf8());
  142. }
  143. QApplication app(newArgc, newArgv);
  144. app.setApplicationName("Jellyfin Media Player");
  145. #if defined(Q_OS_WIN)
  146. // Setting window icon on OSX will break user ability to change it
  147. app.setWindowIcon(QIcon(":/images/icon.png"));
  148. #endif
  149. #if defined(Q_OS_LINUX)
  150. // Set window icon on Linux using system icon theme
  151. app.setWindowIcon(QIcon::fromTheme("com.github.iwalton3.jellyfin-media-player", QIcon(":/images/icon.png")));
  152. #endif
  153. #if defined(Q_OS_MAC) && defined(NDEBUG)
  154. PFMoveToApplicationsFolderIfNecessary();
  155. #endif
  156. UniqueApplication* uniqueApp = new UniqueApplication();
  157. if (!uniqueApp->ensureUnique())
  158. return EXIT_SUCCESS;
  159. #ifdef Q_OS_UNIX
  160. // install signals handlers for proper app closing.
  161. SignalManager signalManager(&app);
  162. Q_UNUSED(signalManager);
  163. #endif
  164. Log::Init();
  165. if (parser.isSet("terminal"))
  166. Log::EnableTerminalOutput();
  167. detectOpenGLLate();
  168. Codecs::preinitCodecs();
  169. // Initialize all the components. This needs to be done
  170. // early since most everything else relies on it
  171. //
  172. ComponentManager::Get().initialize();
  173. SettingsComponent::Get().setCommandLineValues(parser.optionNames());
  174. QtWebEngine::initialize();
  175. // load QtWebChannel so that we can register our components with it.
  176. QQmlApplicationEngine *engine = Globals::Engine();
  177. KonvergoWindow::RegisterClass();
  178. Globals::SetContextProperty("components", &ComponentManager::Get().getQmlPropertyMap());
  179. // the only way to detect if QML parsing fails is to hook to this signal and then see
  180. // if we get a valid object passed to it. Any error messages will be reported on stderr
  181. // but since no normal user should ever see this it should be fine
  182. //
  183. QObject::connect(engine, &QQmlApplicationEngine::objectCreated, [=](QObject* object, const QUrl& url)
  184. {
  185. Q_UNUSED(url);
  186. if (object == nullptr)
  187. throw FatalException(QObject::tr("Failed to parse application engine script."));
  188. KonvergoWindow* window = Globals::MainWindow();
  189. QObject* webChannel = qvariant_cast<QObject*>(window->property("webChannel"));
  190. Q_ASSERT(webChannel);
  191. ComponentManager::Get().setWebChannel(qobject_cast<QWebChannel*>(webChannel));
  192. QObject::connect(uniqueApp, &UniqueApplication::otherApplicationStarted, window, &KonvergoWindow::otherAppFocus);
  193. });
  194. engine->load(QUrl(QStringLiteral("qrc:/ui/webview.qml")));
  195. Log::UpdateLogLevel();
  196. // run our application
  197. int ret = app.exec();
  198. delete uniqueApp;
  199. Globals::EngineDestroy();
  200. Codecs::Uninit();
  201. Log::Uninit();
  202. return ret;
  203. }
  204. catch (FatalException& e)
  205. {
  206. qFatal("Unhandled FatalException: %s", qPrintable(e.message()));
  207. QApplication errApp(argc, argv);
  208. auto msg = new ErrorMessage(e.message(), true);
  209. msg->show();
  210. errApp.exec();
  211. Codecs::Uninit();
  212. Log::Uninit();
  213. return 1;
  214. }
  215. }