SystemComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. #include <QSysInfo>
  2. #include <QProcess>
  3. #include <QMap>
  4. #include <QtNetwork/qnetworkinterface.h>
  5. #include <QGuiApplication>
  6. #include <QDesktopServices>
  7. #include <QDir>
  8. #include <QJsonObject>
  9. #include <QNetworkRequest>
  10. #include <QNetworkAccessManager>
  11. #include <QNetworkReply>
  12. #include "input/InputComponent.h"
  13. #include "SystemComponent.h"
  14. #include "Version.h"
  15. #include "QsLog.h"
  16. #include "settings/SettingsComponent.h"
  17. #include "ui/KonvergoWindow.h"
  18. #include "settings/SettingsSection.h"
  19. #include "Paths.h"
  20. #include "Names.h"
  21. #include "utils/Utils.h"
  22. #define MOUSE_TIMEOUT 5 * 1000
  23. #define KONVERGO_PRODUCTID_DEFAULT 3
  24. #define KONVERGO_PRODUCTID_OPENELEC 4
  25. // Platform types map
  26. QMap<SystemComponent::PlatformType, QString> g_platformTypeNames = { \
  27. { SystemComponent::platformTypeOsx, "macosx" }, \
  28. { SystemComponent::platformTypeWindows, "windows" },
  29. { SystemComponent::platformTypeLinux, "linux" },
  30. { SystemComponent::platformTypeOpenELEC, "openelec" },
  31. { SystemComponent::platformTypeUnknown, "unknown" },
  32. };
  33. // platform Archictecture map
  34. QMap<SystemComponent::PlatformArch, QString> g_platformArchNames = {
  35. { SystemComponent::platformArchX86_32, "i386" },
  36. { SystemComponent::platformArchX86_64, "x86_64" },
  37. { SystemComponent::platformArchRpi2, "rpi2" },
  38. { SystemComponent::platformArchUnknown, "unknown" }
  39. };
  40. ///////////////////////////////////////////////////////////////////////////////////////////////////
  41. SystemComponent::SystemComponent(QObject* parent) : ComponentBase(parent), m_platformType(platformTypeUnknown), m_platformArch(platformArchUnknown), m_doLogMessages(false), m_cursorVisible(true), m_scale(1)
  42. {
  43. m_mouseOutTimer = new QTimer(this);
  44. m_mouseOutTimer->setSingleShot(true);
  45. connect(m_mouseOutTimer, &QTimer::timeout, [&] () { setCursorVisibility(false); });
  46. // define OS Type
  47. #if defined(Q_OS_MAC)
  48. m_platformType = platformTypeOsx;
  49. #elif defined(Q_OS_WIN)
  50. m_platformType = platformTypeWindows;
  51. #elif defined(KONVERGO_OPENELEC)
  52. m_platformType = platformTypeOpenELEC;
  53. #elif defined(Q_OS_LINUX)
  54. m_platformType = platformTypeLinux;
  55. #endif
  56. // define target type
  57. #if TARGET_RPI
  58. m_platformArch = platformArchRpi2;
  59. #elif defined(Q_PROCESSOR_X86_32)
  60. m_platformArch = platformArchX86_32;
  61. #elif defined(Q_PROCESSOR_X86_64)
  62. m_platformArch = platformArchX86_64;
  63. #endif
  64. connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated, [=]()
  65. {
  66. emit capabilitiesChanged(getCapabilitiesString());
  67. });
  68. }
  69. /////////////////////////////////////////////////////////////////////////////////////////
  70. bool SystemComponent::componentInitialize()
  71. {
  72. QDir().mkpath(Paths::dataDir("scripts"));
  73. QDir().mkpath(Paths::dataDir("sounds"));
  74. // Hide mouse pointer on any keyboard input
  75. connect(&InputComponent::Get(), &InputComponent::receivedInput, [=]() { setCursorVisibility(false); });
  76. return true;
  77. }
  78. /////////////////////////////////////////////////////////////////////////////////////////
  79. void SystemComponent::crashApp()
  80. {
  81. *(volatile int*)nullptr=0;
  82. }
  83. /////////////////////////////////////////////////////////////////////////////////////////
  84. void SystemComponent::componentPostInitialize()
  85. {
  86. InputComponent::Get().registerHostCommand("crash!", this, "crashApp");
  87. InputComponent::Get().registerHostCommand("script", this, "runUserScript");
  88. InputComponent::Get().registerHostCommand("message", this, "hostMessage");
  89. }
  90. ///////////////////////////////////////////////////////////////////////////////////////////////////
  91. QString SystemComponent::getPlatformTypeString() const
  92. {
  93. return g_platformTypeNames[m_platformType];
  94. }
  95. ///////////////////////////////////////////////////////////////////////////////////////////////////
  96. QString SystemComponent::getPlatformArchString() const
  97. {
  98. return g_platformArchNames[m_platformArch];
  99. }
  100. ///////////////////////////////////////////////////////////////////////////////////////////////////
  101. QVariantMap SystemComponent::systemInformation() const
  102. {
  103. QVariantMap info;
  104. QString build;
  105. QString dist;
  106. QString arch;
  107. int productid = KONVERGO_PRODUCTID_DEFAULT;
  108. #ifdef Q_OS_WIN
  109. arch = (sizeof(void *) == 8) ? "x86_64" : "i386";
  110. #else
  111. arch = QSysInfo::currentCpuArchitecture();
  112. #endif
  113. build = getPlatformTypeString();
  114. dist = getPlatformTypeString();
  115. #if defined(KONVERGO_OPENELEC)
  116. productid = KONVERGO_PRODUCTID_OPENELEC;
  117. dist = "openelec";
  118. if (m_platformArch == platformArchRpi2)
  119. {
  120. build = "rpi2";
  121. }
  122. else
  123. {
  124. build = "generic";
  125. }
  126. #endif
  127. info["build"] = build + "-" + arch;
  128. info["dist"] = dist;
  129. info["version"] = Version::GetVersionString();
  130. info["productid"] = productid;
  131. QLOG_DEBUG() << QString(
  132. "System Information : build(%1)-arch(%2).dist(%3).version(%4).productid(%5)")
  133. .arg(build)
  134. .arg(arch)
  135. .arg(dist)
  136. .arg(Version::GetVersionString())
  137. .arg(productid);
  138. return info;
  139. }
  140. ///////////////////////////////////////////////////////////////////////////////////////////////////
  141. void SystemComponent::exit()
  142. {
  143. qApp->quit();
  144. }
  145. ///////////////////////////////////////////////////////////////////////////////////////////////////
  146. void SystemComponent::restart()
  147. {
  148. qApp->quit();
  149. QProcess::startDetached(qApp->arguments()[0], qApp->arguments());
  150. }
  151. ///////////////////////////////////////////////////////////////////////////////////////////////////
  152. void SystemComponent::info(QString text)
  153. {
  154. if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::InfoLevel)
  155. QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() << "JS:" << qPrintable(text);
  156. }
  157. ///////////////////////////////////////////////////////////////////////////////////////////////////
  158. void SystemComponent::setCursorVisibility(bool visible)
  159. {
  160. if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "webMode") == "desktop")
  161. visible = true;
  162. if (visible == m_cursorVisible)
  163. return;
  164. m_cursorVisible = visible;
  165. if (visible)
  166. {
  167. qApp->restoreOverrideCursor();
  168. m_mouseOutTimer->start(MOUSE_TIMEOUT);
  169. }
  170. else
  171. {
  172. qApp->setOverrideCursor(QCursor(Qt::BlankCursor));
  173. m_mouseOutTimer->stop();
  174. }
  175. #ifdef Q_OS_MAC
  176. // OSX notifications will reset the cursor image (without Qt's knowledge). The
  177. // only thing we can do override this is using Cocoa's native cursor hiding.
  178. OSXUtils::SetCursorVisible(visible);
  179. #endif
  180. }
  181. ///////////////////////////////////////////////////////////////////////////////////////////////////
  182. QString SystemComponent::getUserAgent()
  183. {
  184. QString osVersion = QSysInfo::productVersion();
  185. QString userAgent = QString("JellyfinMediaPlayer %1 (%2-%3 %4)").arg(Version::GetVersionString()).arg(getPlatformTypeString()).arg(getPlatformArchString()).arg(osVersion);
  186. return userAgent;
  187. }
  188. /////////////////////////////////////////////////////////////////////////////////////////
  189. QString SystemComponent::debugInformation()
  190. {
  191. QString debugInfo;
  192. QTextStream stream(&debugInfo);
  193. stream << "Jellyfin Media Player\n";
  194. stream << " Version: " << Version::GetVersionString() << " built: " << Version::GetBuildDate() << "\n";
  195. stream << " Web Client Version: " << Version::GetWebVersion() << "\n";
  196. stream << " Web Client URL: " << SettingsComponent::Get().value(SETTINGS_SECTION_PATH, "startupurl").toString() << "\n";
  197. stream << " Platform: " << getPlatformTypeString() << "-" << getPlatformArchString() << "\n";
  198. stream << " User-Agent: " << getUserAgent() << "\n";
  199. stream << " Qt version: " << qVersion() << QString("(%1)").arg(Version::GetQtDepsVersion()) << "\n";
  200. stream << " Depends version: " << Version::GetDependenciesVersion() << "\n";
  201. stream << "\n";
  202. stream << "Files\n";
  203. stream << " Log file: " << Paths::logDir(Names::MainName() + ".log") << "\n";
  204. stream << " Config file: " << Paths::dataDir(Names::MainName() + ".conf") << "\n";
  205. stream << "\n";
  206. stream << "Network Addresses\n";
  207. for(const QString& addr : networkAddresses())
  208. {
  209. stream << " " << addr << "\n";
  210. }
  211. stream << "\n";
  212. stream.flush();
  213. return debugInfo;
  214. }
  215. /////////////////////////////////////////////////////////////////////////////////////////
  216. QStringList SystemComponent::networkAddresses() const
  217. {
  218. QStringList list;
  219. for(const QHostAddress& address : QNetworkInterface::allAddresses())
  220. {
  221. if (! address.isLoopback() && (address.protocol() == QAbstractSocket::IPv4Protocol ||
  222. address.protocol() == QAbstractSocket::IPv6Protocol))
  223. {
  224. auto s = address.toString();
  225. if (!s.startsWith("fe80::"))
  226. list << s;
  227. }
  228. }
  229. return list;
  230. }
  231. /////////////////////////////////////////////////////////////////////////////////////////
  232. void SystemComponent::openExternalUrl(const QString& url)
  233. {
  234. QDesktopServices::openUrl(QUrl(url));
  235. }
  236. /////////////////////////////////////////////////////////////////////////////////////////
  237. void SystemComponent::runUserScript(QString script)
  238. {
  239. // We take the path the user supplied and run it through fileInfo and
  240. // look for the fileName() part, this is to avoid people sharing keymaps
  241. // that tries to execute things like ../../ etc. Note that this function
  242. // is still not safe, people can do nasty things with it, so users needs
  243. // to be careful with their keymaps.
  244. //
  245. QFileInfo fi(script);
  246. QString scriptPath = Paths::dataDir("scripts/" + fi.fileName());
  247. QFile scriptFile(scriptPath);
  248. if (scriptFile.exists())
  249. {
  250. if (!QFileInfo(scriptFile).isExecutable())
  251. {
  252. QLOG_WARN() << "Script:" << script << "is not executable";
  253. return;
  254. }
  255. QLOG_INFO() << "Running script:" << scriptPath;
  256. if (QProcess::startDetached(scriptPath, QStringList()))
  257. QLOG_DEBUG() << "Script started successfully";
  258. else
  259. QLOG_WARN() << "Error running script:" << scriptPath;
  260. }
  261. else
  262. {
  263. QLOG_WARN() << "Could not find script:" << scriptPath;
  264. }
  265. }
  266. /////////////////////////////////////////////////////////////////////////////////////////
  267. void SystemComponent::hello(const QString& version)
  268. {
  269. QLOG_DEBUG() << QString("Web-client (%1) fully inited.").arg(version);
  270. m_webClientVersion = version;
  271. }
  272. /////////////////////////////////////////////////////////////////////////////////////////
  273. QString SystemComponent::getNativeShellScript()
  274. {
  275. auto path = SettingsComponent::Get().getExtensionPath();
  276. QLOG_DEBUG() << QString("Using path for extension: %1").arg(path);
  277. QFile file {path + "nativeshell.js"};
  278. file.open(QIODevice::ReadOnly);
  279. auto nativeshellString = QTextStream(&file).readAll();
  280. QJsonObject clientData;
  281. clientData.insert("deviceName", QJsonValue::fromVariant(SettingsComponent::Get().getClientName()));
  282. clientData.insert("scriptPath", QJsonValue::fromVariant("file:///" + path));
  283. clientData.insert("mode", QJsonValue::fromVariant(SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "layout").toString()));
  284. clientData.insert("allow_transcode_to_hevc", QJsonValue::fromVariant(SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "allow_transcode_to_hevc").toBool()));
  285. clientData.insert("force_transcode_hdr", QJsonValue::fromVariant(SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "force_transcode_hdr").toBool()));
  286. nativeshellString.replace("@@data@@", QJsonDocument(clientData).toJson(QJsonDocument::Compact).toBase64());
  287. return nativeshellString;
  288. }
  289. /////////////////////////////////////////////////////////////////////////////////////////
  290. void SystemComponent::checkForUpdates()
  291. {
  292. if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "checkForUpdates").toBool()) {
  293. #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
  294. QNetworkAccessManager *manager = new QNetworkAccessManager(this);
  295. QString checkUrl = "https://github.com/jellyfin/jellyfin-media-player/releases/latest";
  296. QUrl qCheckUrl = QUrl(checkUrl);
  297. QLOG_DEBUG() << QString("Checking URL for updates: %1").arg(checkUrl);
  298. QNetworkRequest req(qCheckUrl);
  299. connect(manager, &QNetworkAccessManager::finished, this, &SystemComponent::updateInfoHandler);
  300. manager->get(req);
  301. #else
  302. emit updateInfoEmitted("SSL_UNAVAILABLE");
  303. #endif
  304. }
  305. }
  306. /////////////////////////////////////////////////////////////////////////////////////////
  307. void SystemComponent::updateInfoHandler(QNetworkReply* reply)
  308. {
  309. if (reply->error() == QNetworkReply::NoError) {
  310. int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  311. if(statusCode == 302) {
  312. QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
  313. emit updateInfoEmitted(redirectUrl.toString());
  314. }
  315. }
  316. }
  317. /////////////////////////////////////////////////////////////////////////////////////////
  318. #define BASESTR "protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:2160&level:52};audioDecoders=mp3,aac,dts{bitrate:800000&channels:%1},ac3{bitrate:800000&channels:%2}"
  319. /////////////////////////////////////////////////////////////////////////////////////////
  320. QString SystemComponent::getCapabilitiesString()
  321. {
  322. auto capstring = QString(BASESTR);
  323. auto channels = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "channels").toString();
  324. auto dtsenabled = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.dts").toBool();
  325. auto ac3enabled = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.ac3").toBool();
  326. // Assume that auto means that we want to select multi-channel tracks by default.
  327. // So really only disable it when 2.0 is selected.
  328. //
  329. int ac3channels = 2;
  330. int dtschannels = 2;
  331. if (channels != "2.0")
  332. dtschannels = ac3channels = 8;
  333. else if (dtsenabled)
  334. dtschannels = 8;
  335. else if (ac3enabled)
  336. ac3channels = 8;
  337. return capstring.arg(dtschannels).arg(ac3channels);
  338. }