123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- #include <QSysInfo>
- #include <QProcess>
- #include <QMap>
- #include <QtNetwork/qnetworkinterface.h>
- #include <QGuiApplication>
- #include <QDesktopServices>
- #include <QDir>
- #include <QJsonObject>
- #include <QNetworkRequest>
- #include <QNetworkAccessManager>
- #include <QNetworkReply>
- #include <QDebug>
- #include "input/InputComponent.h"
- #include "SystemComponent.h"
- #include "Version.h"
- #include "settings/SettingsComponent.h"
- #include "ui/KonvergoWindow.h"
- #include "settings/SettingsSection.h"
- #include "Paths.h"
- #include "Names.h"
- #include "utils/Utils.h"
- #include "utils/Log.h"
- #define MOUSE_TIMEOUT 5 * 1000
- #define KONVERGO_PRODUCTID_DEFAULT 3
- #define KONVERGO_PRODUCTID_OPENELEC 4
- // Platform types map
- QMap<SystemComponent::PlatformType, QString> g_platformTypeNames = { \
- { SystemComponent::platformTypeOsx, "macosx" }, \
- { SystemComponent::platformTypeWindows, "windows" },
- { SystemComponent::platformTypeLinux, "linux" },
- { SystemComponent::platformTypeOpenELEC, "openelec" },
- { SystemComponent::platformTypeUnknown, "unknown" },
- };
- // platform Archictecture map
- QMap<SystemComponent::PlatformArch, QString> g_platformArchNames = {
- { SystemComponent::platformArchX86_32, "i386" },
- { SystemComponent::platformArchX86_64, "x86_64" },
- { SystemComponent::platformArchRpi2, "rpi2" },
- { SystemComponent::platformArchUnknown, "unknown" }
- };
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- SystemComponent::SystemComponent(QObject* parent) : ComponentBase(parent), m_platformType(platformTypeUnknown), m_platformArch(platformArchUnknown), m_doLogMessages(false), m_cursorVisible(true), m_scale(1)
- {
- m_mouseOutTimer = new QTimer(this);
- m_mouseOutTimer->setSingleShot(true);
- connect(m_mouseOutTimer, &QTimer::timeout, [&] () { setCursorVisibility(false); });
- // define OS Type
- #if defined(Q_OS_MAC)
- m_platformType = platformTypeOsx;
- #elif defined(Q_OS_WIN)
- m_platformType = platformTypeWindows;
- #elif defined(KONVERGO_OPENELEC)
- m_platformType = platformTypeOpenELEC;
- #elif defined(Q_OS_LINUX)
- m_platformType = platformTypeLinux;
- #endif
- // define target type
- #if TARGET_RPI
- m_platformArch = platformArchRpi2;
- #elif defined(Q_PROCESSOR_X86_32)
- m_platformArch = platformArchX86_32;
- #elif defined(Q_PROCESSOR_X86_64)
- m_platformArch = platformArchX86_64;
- #endif
- connect(SettingsComponent::Get().getSection(SETTINGS_SECTION_AUDIO), &SettingsSection::valuesUpdated, [=]()
- {
- emit capabilitiesChanged(getCapabilitiesString());
- });
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- bool SystemComponent::componentInitialize()
- {
- QDir().mkpath(Paths::dataDir("scripts"));
- QDir().mkpath(Paths::dataDir("sounds"));
- // Hide mouse pointer on any keyboard input
- connect(&InputComponent::Get(), &InputComponent::receivedInput, [=]() { setCursorVisibility(false); });
- return true;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::crashApp()
- {
- *(volatile int*)nullptr=0;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::componentPostInitialize()
- {
- InputComponent::Get().registerHostCommand("crash!", this, "crashApp");
- InputComponent::Get().registerHostCommand("script", this, "runUserScript");
- InputComponent::Get().registerHostCommand("message", this, "hostMessage");
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::getPlatformTypeString() const
- {
- return g_platformTypeNames[m_platformType];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::getPlatformArchString() const
- {
- return g_platformArchNames[m_platformArch];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QVariantMap SystemComponent::systemInformation() const
- {
- QVariantMap info;
- QString build;
- QString dist;
- QString arch;
- int productid = KONVERGO_PRODUCTID_DEFAULT;
- #ifdef Q_OS_WIN
- arch = (sizeof(void *) == 8) ? "x86_64" : "i386";
- #else
- arch = QSysInfo::currentCpuArchitecture();
- #endif
- build = getPlatformTypeString();
- dist = getPlatformTypeString();
- #if defined(KONVERGO_OPENELEC)
- productid = KONVERGO_PRODUCTID_OPENELEC;
- dist = "openelec";
- if (m_platformArch == platformArchRpi2)
- {
- build = "rpi2";
- }
- else
- {
- build = "generic";
- }
- #endif
-
- info["build"] = build + "-" + arch;
- info["dist"] = dist;
- info["version"] = Version::GetVersionString();
- info["productid"] = productid;
-
- qDebug() << QString(
- "System Information : build(%1)-arch(%2).dist(%3).version(%4).productid(%5)")
- .arg(build)
- .arg(arch)
- .arg(dist)
- .arg(Version::GetVersionString())
- .arg(productid);
- return info;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::exit()
- {
- qApp->quit();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::restart()
- {
- qApp->quit();
- QProcess::startDetached(qApp->arguments()[0], qApp->arguments());
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::info(QString text)
- {
- if (Log::ShouldLogInfo())
- qInfo() << "JS:" << qPrintable(text);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::setCursorVisibility(bool visible)
- {
- if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "webMode") == "desktop")
- visible = true;
- if (visible == m_cursorVisible)
- return;
- m_cursorVisible = visible;
- if (visible)
- {
- qApp->restoreOverrideCursor();
- m_mouseOutTimer->start(MOUSE_TIMEOUT);
- }
- else
- {
- qApp->setOverrideCursor(QCursor(Qt::BlankCursor));
- m_mouseOutTimer->stop();
- }
- #ifdef Q_OS_MAC
- // OSX notifications will reset the cursor image (without Qt's knowledge). The
- // only thing we can do override this is using Cocoa's native cursor hiding.
- OSXUtils::SetCursorVisible(visible);
- #endif
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::getUserAgent()
- {
- QString osVersion = QSysInfo::productVersion();
- QString userAgent = QString("JellyfinMediaPlayer %1 (%2-%3 %4)").arg(Version::GetVersionString()).arg(getPlatformTypeString()).arg(getPlatformArchString()).arg(osVersion);
- return userAgent;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::debugInformation()
- {
- QString debugInfo;
- QTextStream stream(&debugInfo);
- stream << "Jellyfin Media Player\n";
- stream << " Version: " << Version::GetVersionString() << " built: " << Version::GetBuildDate() << "\n";
- stream << " Web Client Version: " << Version::GetWebVersion() << "\n";
- stream << " Web Client URL: " << SettingsComponent::Get().value(SETTINGS_SECTION_PATH, "startupurl").toString() << "\n";
- stream << " Platform: " << getPlatformTypeString() << "-" << getPlatformArchString() << "\n";
- stream << " User-Agent: " << getUserAgent() << "\n";
- stream << " Qt version: " << qVersion() << QString("(%1)").arg(Version::GetQtDepsVersion()) << "\n";
- stream << " Depends version: " << Version::GetDependenciesVersion() << "\n";
- stream << "\n";
- stream << "Files\n";
- stream << " Log file: " << Paths::logDir(Names::MainName() + ".log") << "\n";
- stream << " Config file: " << Paths::dataDir(Names::MainName() + ".conf") << "\n";
- stream << "\n";
- stream << "Network Addresses\n";
- for(const QString& addr : networkAddresses())
- {
- stream << " " << addr << "\n";
- }
- stream << "\n";
- stream.flush();
- return debugInfo;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QStringList SystemComponent::networkAddresses() const
- {
- QStringList list;
- for(const QHostAddress& address : QNetworkInterface::allAddresses())
- {
- if (! address.isLoopback() && (address.protocol() == QAbstractSocket::IPv4Protocol ||
- address.protocol() == QAbstractSocket::IPv6Protocol))
- {
- auto s = address.toString();
- if (!s.startsWith("fe80::"))
- list << s;
- }
- }
- return list;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::openExternalUrl(const QString& url)
- {
- QDesktopServices::openUrl(QUrl(url));
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::runUserScript(QString script)
- {
- // We take the path the user supplied and run it through fileInfo and
- // look for the fileName() part, this is to avoid people sharing keymaps
- // that tries to execute things like ../../ etc. Note that this function
- // is still not safe, people can do nasty things with it, so users needs
- // to be careful with their keymaps.
- //
- QFileInfo fi(script);
- QString scriptPath = Paths::dataDir("scripts/" + fi.fileName());
- QFile scriptFile(scriptPath);
- if (scriptFile.exists())
- {
- if (!QFileInfo(scriptFile).isExecutable())
- {
- qWarning() << "Script:" << script << "is not executable";
- return;
- }
- qInfo() << "Running script:" << scriptPath;
- if (QProcess::startDetached(scriptPath, QStringList()))
- qDebug() << "Script started successfully";
- else
- qWarning() << "Error running script:" << scriptPath;
- }
- else
- {
- qWarning() << "Could not find script:" << scriptPath;
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::hello(const QString& version)
- {
- qDebug() << QString("Web-client (%1) fully inited.").arg(version);
- m_webClientVersion = version;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::getNativeShellScript()
- {
- auto path = SettingsComponent::Get().getExtensionPath();
- qDebug() << QString("Using path for extension: %1").arg(path);
- QFile file {path + "nativeshell.js"};
- file.open(QIODevice::ReadOnly);
- auto nativeshellString = QTextStream(&file).readAll();
- QJsonObject clientData;
- clientData.insert("deviceName", QJsonValue::fromVariant(SettingsComponent::Get().getClientName()));
- clientData.insert("scriptPath", QJsonValue::fromVariant("file:///" + path));
- clientData.insert("mode", QJsonValue::fromVariant(SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "layout").toString()));
- QVariantList settingsDescriptionsList = SettingsComponent::Get().settingDescriptions();
- QVariantMap settingsDescriptions = QVariantMap();
- for (auto setting : settingsDescriptionsList) {
- QVariantMap settingMap = setting.toMap();
- settingsDescriptions.insert(settingMap["key"].toString(), settingMap["settings"]);
- }
- clientData.insert("settingsDescriptions", QJsonValue::fromVariant(settingsDescriptions));
- clientData.insert("settings", QJsonValue::fromVariant(SettingsComponent::Get().allValues()));
- nativeshellString.replace("@@data@@", QJsonDocument(clientData).toJson(QJsonDocument::Compact).toBase64());
- return nativeshellString;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::checkForUpdates()
- {
- if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "checkForUpdates").toBool()) {
- #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
- QNetworkAccessManager *manager = new QNetworkAccessManager(this);
- QString checkUrl = "https://github.com/jellyfin/jellyfin-media-player/releases/latest";
- QUrl qCheckUrl = QUrl(checkUrl);
- qDebug() << QString("Checking URL for updates: %1").arg(checkUrl);
- QNetworkRequest req(qCheckUrl);
- connect(manager, &QNetworkAccessManager::finished, this, &SystemComponent::updateInfoHandler);
- manager->get(req);
- #else
- emit updateInfoEmitted("SSL_UNAVAILABLE");
- #endif
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- void SystemComponent::updateInfoHandler(QNetworkReply* reply)
- {
- if (reply->error() == QNetworkReply::NoError) {
- int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- if(statusCode == 302) {
- QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
- emit updateInfoEmitted(redirectUrl.toString());
- }
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- #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}"
- /////////////////////////////////////////////////////////////////////////////////////////
- QString SystemComponent::getCapabilitiesString()
- {
- auto capstring = QString(BASESTR);
- auto channels = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "channels").toString();
- auto dtsenabled = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.dts").toBool();
- auto ac3enabled = SettingsComponent::Get().value(SETTINGS_SECTION_AUDIO, "passthrough.ac3").toBool();
- // Assume that auto means that we want to select multi-channel tracks by default.
- // So really only disable it when 2.0 is selected.
- //
- int ac3channels = 2;
- int dtschannels = 2;
- if (channels != "2.0")
- dtschannels = ac3channels = 8;
- else if (dtsenabled)
- dtschannels = 8;
- else if (ac3enabled)
- ac3channels = 8;
- return capstring.arg(dtschannels).arg(ac3channels);
- }
|