diff --git a/src/singletons/settingsmanager.hpp b/src/singletons/settingsmanager.hpp index 3ba4653fe..196f13d06 100644 --- a/src/singletons/settingsmanager.hpp +++ b/src/singletons/settingsmanager.hpp @@ -61,10 +61,6 @@ public: "/behaviour/autocompletion/onlyFetchChattersForSmallerStreamers", true}; IntSetting smallStreamerLimit = {"/behaviour/autocompletion/smallStreamerLimit", 1000}; - // Streamlink - QStringSetting streamlinkPath = {"/behaviour/streamlink/path", ""}; - QStringSetting preferredQuality = {"/behaviour/streamlink/quality", "Choose"}; - QStringSetting streamlinkOpts = {"/behaviour/streamlink/options", ""}; BoolSetting pauseChatHover = {"/behaviour/pauseChatHover", false}; /// Commands @@ -114,6 +110,13 @@ public: BoolSetting inlineWhispers = {"/whispers/enableInlineWhispers", true}; + /// External tools + // Streamlink + BoolSetting streamlinkUseCustomPath = {"/external/streamlink/useCustomPath", false}; + QStringSetting streamlinkPath = {"/external/streamlink/customPath", ""}; + QStringSetting preferredQuality = {"/external/streamlink/quality", "Choose"}; + QStringSetting streamlinkOpts = {"/external/streamlink/options", ""}; + void updateWordTypeMask(); void saveSnapshot(); diff --git a/src/util/streamlink.cpp b/src/util/streamlink.cpp index fd8b37890..2f7c95edd 100644 --- a/src/util/streamlink.cpp +++ b/src/util/streamlink.cpp @@ -5,6 +5,7 @@ #include "singletons/settingsmanager.hpp" #include "widgets/qualitypopup.hpp" +#include #include #include @@ -33,6 +34,17 @@ const char *GetDefaultBinaryPath() #endif } +QString getStreamlinkProgram() +{ + auto app = getApp(); + + if (app->settings->streamlinkUseCustomPath) { + return app->settings->streamlinkPath + "/" + GetBinaryName(); + } else { + return GetBinaryName(); + } +} + bool CheckStreamlinkPath(const QString &path) { QFileInfo fileinfo(path); @@ -42,49 +54,57 @@ bool CheckStreamlinkPath(const QString &path) // throw Exception(fS("Streamlink path ({}) is invalid, file does not exist", path)); } - if (fileinfo.isDir() || !fileinfo.isExecutable()) { - return false; - } - - return true; + return fileinfo.isExecutable(); } -// TODO: Make streamlink binary finder smarter -QString GetStreamlinkBinaryPath() +void showStreamlinkNotFoundError() { + static QErrorMessage *msg = new QErrorMessage; + auto app = getApp(); - - QString settingPath = app->settings->streamlinkPath; - - QStringList paths; - paths << settingPath; - paths << GetDefaultBinaryPath(); -#ifdef _WIN32 - paths << settingPath + "\\" + GetBinaryName(); - paths << settingPath + "\\bin\\" + GetBinaryName(); -#else - paths << "/usr/local/bin/streamlink"; - paths << "/bin/streamlink"; -#endif - - for (const auto &path : paths) { - if (CheckStreamlinkPath(path)) { - return path; - } + if (app->settings->streamlinkUseCustomPath) { + msg->showMessage( + "Unable to find Streamlink executable\nMake sure your custom path is pointing " + "to the DIRECTORY where the streamlink executable is located"); + } else { + msg->showMessage("Unable to find Streamlink executable.\nIf you have Streamlink " + "installed, you might need to enable the custom path option"); } - - throw Exception("Unable to find streamlink binary. Install streamlink or set the binary path " - "in the settings dialog."); } +QProcess *createStreamlinkProcess() +{ + auto p = new QProcess; + p->setProgram(getStreamlinkProgram()); + + QObject::connect(p, &QProcess::errorOccurred, [=](auto err) { + if (err == QProcess::FailedToStart) { + showStreamlinkNotFoundError(); + } else { + qDebug() << "Error occured: " << err; // + } + + p->deleteLater(); + }); + + QObject::connect(p, static_cast(&QProcess::finished), [=](int res) { + p->deleteLater(); // + }); + + return p; +} + +} // namespace + void GetStreamQualities(const QString &channelURL, std::function cb) { - QString path = GetStreamlinkBinaryPath(); + auto p = createStreamlinkProcess(); - // XXX: Memory leak - QProcess *p = new QProcess(); - - QObject::connect(p, static_cast(&QProcess::finished), [=](int) { + QObject::connect(p, static_cast(&QProcess::finished), [=](int res) { + if (res != 0) { + qDebug() << "Got error code" << res; + // return; + } QString lastLine = QString(p->readAllStandardOutput()); lastLine = lastLine.trimmed().split('\n').last().trimmed(); if (lastLine.startsWith("Available streams: ")) { @@ -106,17 +126,15 @@ void GetStreamQualities(const QString &channelURL, std::functionstart(path, {channelURL, "--default-stream=KKona"}); -} + p->setArguments({channelURL, "--default-stream=KKona"}); -} // namespace + p->start(); +} void OpenStreamlink(const QString &channelURL, const QString &quality, QStringList extraArguments) { auto app = getApp(); - QString path = GetStreamlinkBinaryPath(); - QStringList arguments; QString additionalOptions = app->settings->streamlinkOpts.getValue(); @@ -132,7 +150,15 @@ void OpenStreamlink(const QString &channelURL, const QString &quality, QStringLi arguments << quality; } - QProcess::startDetached(path, arguments); + auto p = createStreamlinkProcess(); + + p->setArguments(arguments); + + bool res = p->startDetached(); + + if (!res) { + showStreamlinkNotFoundError(); + } } void Start(const QString &channel) diff --git a/src/widgets/settingspages/externaltoolspage.cpp b/src/widgets/settingspages/externaltoolspage.cpp index 1f0f3eb7b..3ed7ff859 100644 --- a/src/widgets/settingspages/externaltoolspage.cpp +++ b/src/widgets/settingspages/externaltoolspage.cpp @@ -11,6 +11,15 @@ namespace chatterino { namespace widgets { namespace settingspages { +namespace { + +QString CreateLink(const QString &url, const QString &name) +{ + return QString("" + name + ""); +} + +} // namespace + ExternalToolsPage::ExternalToolsPage() : SettingsPage("External tools", "") { @@ -22,13 +31,44 @@ ExternalToolsPage::ExternalToolsPage() { auto group = layout.emplace("Streamlink"); auto groupLayout = group.setLayoutType(); - groupLayout->addRow("Streamlink path:", - this->createLineEdit(app->settings->streamlinkPath)); + + auto description = + new QLabel("Streamlink is a command-line utility that pipes video streams from various " + "services into a video player, such as VLC. Make sure to edit the " + "configuration file before you use it!"); + description->setWordWrap(true); + description->setStyleSheet("color: #bbb"); + + auto links = new QLabel( + CreateLink("https://streamlink.github.io/", "Website") + " " + + CreateLink("https://github.com/streamlink/streamlink/releases/latest", "Download")); + links->setTextFormat(Qt::RichText); + links->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::LinksAccessibleByKeyboard | + Qt::LinksAccessibleByKeyboard); + links->setOpenExternalLinks(true); + + groupLayout->setWidget(0, QFormLayout::SpanningRole, description); + groupLayout->setWidget(1, QFormLayout::SpanningRole, links); + + auto customPathCb = this->createCheckBox( + "Use custom path (Enable if using non-standard streamlink installation path)", + app->settings->streamlinkUseCustomPath); + groupLayout->setWidget(2, QFormLayout::SpanningRole, customPathCb); + + auto customPath = this->createLineEdit(app->settings->streamlinkPath); + customPath->setPlaceholderText("Path to folder where Streamlink executable can be found"); + groupLayout->addRow("Custom streamlink path:", customPath); groupLayout->addRow( - "Prefered quality:", + "Preferred quality:", this->createComboBox({STREAMLINK_QUALITY}, app->settings->preferredQuality)); groupLayout->addRow("Additional options:", this->createLineEdit(app->settings->streamlinkOpts)); + + app->settings->streamlinkUseCustomPath.connect( + [=](const auto &value, auto) { + customPath->setEnabled(value); // + }, + this->managedConnections); } layout->addStretch(1);