diff --git a/CHANGELOG.md b/CHANGELOG.md index 4241e715f..b8184d5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,7 @@ - Dev: Twitch messages can be sent using Twitch's Helix API instead of IRC (disabled by default). (#5200) - Dev: Added estimation for image sizes to avoid layout shifts. (#5192) - Dev: Added the `launachable` entry to Linux AppData. (#5210) +- Dev: Refactor `StreamerMode`. (#5216) ## 2.4.6 diff --git a/benchmarks/src/RecentMessages.cpp b/benchmarks/src/RecentMessages.cpp index 77efd6dc1..fd5fe0f1a 100644 --- a/benchmarks/src/RecentMessages.cpp +++ b/benchmarks/src/RecentMessages.cpp @@ -2,6 +2,7 @@ #include "controllers/accounts/AccountController.hpp" #include "controllers/highlights/HighlightController.hpp" #include "messages/Emote.hpp" +#include "mocks/DisabledStreamerMode.hpp" #include "mocks/EmptyApplication.hpp" #include "mocks/TwitchIrcServer.hpp" #include "mocks/UserData.hpp" @@ -93,6 +94,11 @@ public: return &this->seventvEmotes; } + IStreamerMode *getStreamerMode() override + { + return &this->streamerMode; + } + AccountController accounts; Emotes emotes; mock::UserDataController userData; @@ -105,6 +111,7 @@ public: BttvEmotes bttvEmotes; FfzEmotes ffzEmotes; SeventvEmotes seventvEmotes; + DisabledStreamerMode streamerMode; }; std::optional tryReadJsonFile(const QString &path) diff --git a/mocks/include/mocks/DisabledStreamerMode.hpp b/mocks/include/mocks/DisabledStreamerMode.hpp new file mode 100644 index 000000000..96c03b2b2 --- /dev/null +++ b/mocks/include/mocks/DisabledStreamerMode.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "singletons/StreamerMode.hpp" + +class DisabledStreamerMode : public chatterino::IStreamerMode +{ +public: + bool isEnabled() const override + { + return false; + } +}; diff --git a/mocks/include/mocks/EmptyApplication.hpp b/mocks/include/mocks/EmptyApplication.hpp index 2755b07a6..e70be2688 100644 --- a/mocks/include/mocks/EmptyApplication.hpp +++ b/mocks/include/mocks/EmptyApplication.hpp @@ -228,6 +228,13 @@ public: return nullptr; } + IStreamerMode *getStreamerMode() override + { + assert(false && "EmptyApplication::getStreamerMode was called without " + "being initialized"); + return nullptr; + } + private: Paths paths_; Args args_; diff --git a/src/Application.cpp b/src/Application.cpp index 02982f954..929bedd08 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -51,6 +51,7 @@ #include "singletons/Logging.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/Toasts.hpp" #include "singletons/Updates.hpp" @@ -144,6 +145,7 @@ Application::Application(Settings &_settings, const Paths &paths, , seventvEmotes(new SeventvEmotes) , logging(new Logging(_settings)) , linkResolver(new LinkResolver) + , streamerMode(new StreamerMode) #ifdef CHATTERINO_HAVE_PLUGINS , plugins(&this->emplace(new PluginController(paths))) #endif @@ -503,6 +505,11 @@ ILinkResolver *Application::getLinkResolver() return this->linkResolver.get(); } +IStreamerMode *Application::getStreamerMode() +{ + return this->streamerMode.get(); +} + BttvEmotes *Application::getBttvEmotes() { assertInGuiThread(); @@ -707,7 +714,7 @@ void Application::initPubSub() } if (getSettings()->streamerModeHideModActions && - isInStreamerMode()) + this->getStreamerMode()->isEnabled()) { return; } @@ -756,7 +763,7 @@ void Application::initPubSub() } if (getSettings()->streamerModeHideModActions && - isInStreamerMode()) + this->getStreamerMode()->isEnabled()) { return; } @@ -892,9 +899,8 @@ void Application::initPubSub() std::ignore = this->twitchPubSub->moderation.automodUserMessage.connect( [&](const auto &action) { - // This condition has been set up to execute isInStreamerMode() as the last thing - // as it could end up being expensive. - if (getSettings()->streamerModeHideModActions && isInStreamerMode()) + if (getSettings()->streamerModeHideModActions && + this->getStreamerMode()->isEnabled()) { return; } diff --git a/src/Application.hpp b/src/Application.hpp index 991d82acc..eb79ffc53 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -4,8 +4,6 @@ #include "debug/AssertInGuiThread.hpp" #include "singletons/NativeMessaging.hpp" -#include -#include #include #include @@ -55,6 +53,7 @@ class BttvEmotes; class FfzEmotes; class SeventvEmotes; class ILinkResolver; +class IStreamerMode; class IApplication { @@ -97,6 +96,7 @@ public: virtual FfzEmotes *getFfzEmotes() = 0; virtual SeventvEmotes *getSeventvEmotes() = 0; virtual ILinkResolver *getLinkResolver() = 0; + virtual IStreamerMode *getStreamerMode() = 0; }; class Application : public IApplication @@ -165,6 +165,7 @@ private: std::unique_ptr seventvEmotes; const std::unique_ptr logging; std::unique_ptr linkResolver; + std::unique_ptr streamerMode; #ifdef CHATTERINO_HAVE_PLUGINS PluginController *const plugins{}; #endif @@ -216,8 +217,7 @@ public: SeventvEmotes *getSeventvEmotes() override; ILinkResolver *getLinkResolver() override; - - pajlada::Signals::NoArgSignal streamerModeChanged; + IStreamerMode *getStreamerMode() override; private: void addSingleton(Singleton *singleton); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d1e4d05a..81c65832d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -452,6 +452,8 @@ set(SOURCE_FILES singletons/Resources.hpp singletons/Settings.cpp singletons/Settings.hpp + singletons/StreamerMode.cpp + singletons/StreamerMode.hpp singletons/Theme.cpp singletons/Theme.hpp singletons/Toasts.cpp @@ -505,8 +507,6 @@ set(SOURCE_FILES util/SplitCommand.hpp util/StreamLink.cpp util/StreamLink.hpp - util/StreamerMode.cpp - util/StreamerMode.hpp util/ThreadGuard.hpp util/Twitch.cpp util/Twitch.hpp @@ -770,6 +770,7 @@ target_link_libraries(${LIBRARY_PROJECT} RapidJSON::RapidJSON LRUCache MagicEnum + $<$:Wtsapi32> ) if (CHATTERINO_PLUGINS) target_link_libraries(${LIBRARY_PROJECT} PUBLIC lua) diff --git a/src/controllers/commands/builtin/twitch/SendWhisper.cpp b/src/controllers/commands/builtin/twitch/SendWhisper.cpp index ffb4ac48c..6b4cc25bf 100644 --- a/src/controllers/commands/builtin/twitch/SendWhisper.cpp +++ b/src/controllers/commands/builtin/twitch/SendWhisper.cpp @@ -16,6 +16,7 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "util/Twitch.hpp" @@ -183,7 +184,7 @@ bool appendWhisperMessageWordsLocally(const QStringList &words) if (getSettings()->inlineWhispers && !(getSettings()->streamerModeSuppressInlineWhispers && - isInStreamerMode())) + getIApp()->getStreamerMode()->isEnabled())) { app->twitch->forEachChannel( [&messagexD, overrideFlags](ChannelPtr _channel) { diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp index 5e3aa1fdd..745cf383e 100644 --- a/src/controllers/notifications/NotificationController.cpp +++ b/src/controllers/notifications/NotificationController.cpp @@ -9,6 +9,7 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Toasts.hpp" #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" @@ -186,14 +187,15 @@ void NotificationController::checkStream(bool live, QString channelName) getIApp()->getToasts()->sendChannelNotification(channelName, QString(), Platform::Twitch); } + bool inStreamerMode = getIApp()->getStreamerMode()->isEnabled(); if (getSettings()->notificationPlaySound && - !(isInStreamerMode() && + !(inStreamerMode && getSettings()->streamerModeSuppressLiveNotifications)) { getIApp()->getNotifications()->playSound(); } if (getSettings()->notificationFlashTaskbar && - !(isInStreamerMode() && + !(inStreamerMode && getSettings()->streamerModeSuppressLiveNotifications)) { getIApp()->getWindows()->sendAlert(); diff --git a/src/messages/SharedMessageBuilder.cpp b/src/messages/SharedMessageBuilder.cpp index 78d8c0a69..87a3ae9b4 100644 --- a/src/messages/SharedMessageBuilder.cpp +++ b/src/messages/SharedMessageBuilder.cpp @@ -11,10 +11,10 @@ #include "messages/MessageElement.hpp" #include "providers/twitch/TwitchBadge.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" #include "util/Qt.hpp" -#include "util/StreamerMode.hpp" #include @@ -204,7 +204,8 @@ void SharedMessageBuilder::triggerHighlights( const QString &channelName, bool playSound, const std::optional &customSoundUrl, bool windowAlert) { - if (isInStreamerMode() && getSettings()->streamerModeMuteMentions) + if (getIApp()->getStreamerMode()->isEnabled() && + getSettings()->streamerModeMuteMentions) { // We are in streamer mode with muting mention sounds enabled. Do nothing. return; diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index bd80a5dc0..a7c557639 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -9,9 +9,9 @@ #include "messages/Selection.hpp" #include "providers/colors/ColorProvider.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/WindowManager.hpp" #include "util/DebugCount.hpp" -#include "util/StreamerMode.hpp" #include #include @@ -160,11 +160,9 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) if (this->message_->flags.has(MessageFlag::Timeout) || this->message_->flags.has(MessageFlag::Untimeout)) { - // This condition has been set up to execute isInStreamerMode() as the last thing - // as it could end up being expensive. if (hideModerationActions || (getSettings()->streamerModeHideModActions && - isInStreamerMode())) + getIApp()->getStreamerMode()->isEnabled())) { continue; } diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index ed9c8714c..b0eec1f8c 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -22,12 +22,12 @@ #include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/WindowManager.hpp" #include "util/ChannelHelpers.hpp" #include "util/FormatTime.hpp" #include "util/Helpers.hpp" #include "util/IrcHelpers.hpp" -#include "util/StreamerMode.hpp" #include #include @@ -931,7 +931,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *ircMessage) if (getSettings()->inlineWhispers && !(getSettings()->streamerModeSuppressInlineWhispers && - isInStreamerMode())) + getIApp()->getStreamerMode()->isEnabled())) { getApp()->twitch->forEachChannel( [&message, overrideFlags](ChannelPtr channel) { diff --git a/src/providers/twitch/PubSubManager.cpp b/src/providers/twitch/PubSubManager.cpp index 7f75fd8c2..acb75cb59 100644 --- a/src/providers/twitch/PubSubManager.cpp +++ b/src/providers/twitch/PubSubManager.cpp @@ -11,7 +11,6 @@ #include "util/DebugCount.hpp" #include "util/Helpers.hpp" #include "util/RapidjsonHelpers.hpp" -#include "util/StreamerMode.hpp" #include diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index b1a70166e..b5a368a0b 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -35,6 +35,7 @@ #include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Toasts.hpp" #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" @@ -180,7 +181,7 @@ TwitchChannel::TwitchChannel(const QString &name) // Notify on all channels with a ping sound if (getSettings()->notificationOnAnyChannel && - !(isInStreamerMode() && + !(getIApp()->getStreamerMode()->isEnabled() && getSettings()->streamerModeSuppressLiveNotifications)) { getIApp()->getNotifications()->playSound(); diff --git a/src/singletons/Settings.cpp b/src/singletons/Settings.cpp index baac324cb..6cc603c20 100644 --- a/src/singletons/Settings.cpp +++ b/src/singletons/Settings.cpp @@ -186,11 +186,6 @@ Settings::Settings(const QString &settingsDirectory) }, false); #endif - this->enableStreamerMode.connect( - []() { - getApp()->streamerModeChanged.invoke(); - }, - false); } Settings::~Settings() diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 7da5cf690..d96b13f0e 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -15,7 +15,6 @@ #include "controllers/sound/ISoundController.hpp" #include "singletons/Toasts.hpp" #include "util/RapidJsonSerializeQString.hpp" -#include "util/StreamerMode.hpp" #include "widgets/Notebook.hpp" #include @@ -68,6 +67,12 @@ enum class ChatSendProtocol : int { Helix = 2, }; +enum StreamerModeSetting { + Disabled = 0, + Enabled = 1, + DetectStreamingSoftware = 2, +}; + /// Settings which are availlable for reading and writing on the gui thread. // These settings are still accessed concurrently in the code but it is bad practice. class Settings diff --git a/src/singletons/StreamerMode.cpp b/src/singletons/StreamerMode.cpp new file mode 100644 index 000000000..06bdafebc --- /dev/null +++ b/src/singletons/StreamerMode.cpp @@ -0,0 +1,245 @@ +#include "singletons/StreamerMode.hpp" + +#include "Application.hpp" +#include "common/Literals.hpp" +#include "common/QLogging.hpp" +#include "providers/twitch/TwitchIrcServer.hpp" +#include "singletons/Settings.hpp" +#include "util/PostToThread.hpp" + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +// clang-format off +# include +# include +# include +// clang-format on +#endif + +#include + +namespace { + +using namespace chatterino; +using namespace literals; + +/// Number of timeouts to skip if nothing called `isEnabled` in the meantime. +constexpr uint8_t SKIPPED_TIMEOUTS = 5; + +const QStringList &broadcastingBinaries() +{ +#ifdef USEWINSDK + static QStringList bins = { + u"obs.exe"_s, u"obs64.exe"_s, u"PRISMLiveStudio.exe"_s, + u"XSplit.Core.exe"_s, u"TwitchStudio.exe"_s, u"vMix64.exe"_s, + }; +#else + static QStringList bins = { + u"obs"_s, + u"Twitch Studio"_s, + u"Streamlabs Desktop"_s, + }; +#endif + return bins; +} + +bool isBroadcasterSoftwareActive() +{ +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + QProcess p; + p.start("pgrep", {"-x", broadcastingBinaries().join("|")}, + QIODevice::NotOpen); + + if (p.waitForFinished(1000) && p.exitStatus() == QProcess::NormalExit) + { + return (p.exitCode() == 0); + } + + // Fallback to false and showing a warning + + static bool shouldShowWarning = true; + if (shouldShowWarning) + { + shouldShowWarning = false; + + postToThread([] { + getApp()->twitch->addGlobalSystemMessage( + "Streamer Mode is set to Automatic, but pgrep is missing. " + "Install it to fix the issue or set Streamer Mode to " + "Enabled or Disabled in the Settings."); + }); + } + + qCWarning(chatterinoStreamerMode) << "pgrep execution timed out!"; + return false; +#elif defined(Q_OS_WIN) + if (!IsWindowsVistaOrGreater()) + { + return false; + } + + WTS_PROCESS_INFO *pProcessInfo = nullptr; + DWORD dwProcCount = 0; + + if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pProcessInfo, + &dwProcCount)) + { + //Go through all processes retrieved + for (DWORD i = 0; i < dwProcCount; i++) + { + QStringView processName(pProcessInfo[i].pProcessName); + + if (broadcastingBinaries().contains(processName)) + { + WTSFreeMemory(pProcessInfo); + return true; + } + } + } + + if (pProcessInfo) + { + WTSFreeMemory(pProcessInfo); + } + +#else +# warning Unsupported OS: Broadcasting software can't be detected +#endif + return false; +} + +} // namespace + +namespace chatterino { + +using namespace std::chrono_literals; + +class StreamerModePrivate +{ +public: + StreamerModePrivate(StreamerMode *parent_); + + [[nodiscard]] bool isEnabled() const; + +private: + void settingChanged(StreamerModeSetting value); + void setEnabled(bool enabled); + + void check(); + + StreamerMode *parent_; + pajlada::Signals::SignalHolder settingConnections_; + + QTimer timer_; + QThread thread_; + + std::atomic enabled_ = false; + mutable std::atomic timeouts_ = 0; + StreamerModeSetting currentSetting_ = StreamerModeSetting::Disabled; +}; + +StreamerMode::StreamerMode() + : private_(new StreamerModePrivate(this)) +{ +} + +StreamerMode::~StreamerMode() = default; + +void StreamerMode::updated(bool enabled) +{ + this->changed(enabled); +} + +bool StreamerMode::isEnabled() const +{ + return this->private_->isEnabled(); +} + +StreamerModePrivate::StreamerModePrivate(StreamerMode *parent) + : parent_(parent) +{ + this->thread_.start(); + this->timer_.moveToThread(&this->thread_); + QObject::connect(&this->timer_, &QTimer::timeout, [this] { + auto timeouts = + this->timeouts_.fetch_add(1, std::memory_order::relaxed); + if (timeouts < SKIPPED_TIMEOUTS) + { + return; + } + this->timeouts_.store(0, std::memory_order::relaxed); + this->check(); + }); + + getSettings()->enableStreamerMode.connect( + [this](auto value) { + QMetaObject::invokeMethod(this->thread_.eventDispatcher(), [this, + value] { + this->settingChanged(static_cast(value)); + }); + }, + this->settingConnections_); +} + +bool StreamerModePrivate::isEnabled() const +{ + this->timeouts_.store(SKIPPED_TIMEOUTS, std::memory_order::relaxed); + return this->enabled_.load(std::memory_order::relaxed); +} + +void StreamerModePrivate::setEnabled(bool enabled) +{ + if (enabled == this->enabled_.load(std::memory_order::relaxed)) + { + return; + } + + this->enabled_.store(enabled, std::memory_order::relaxed); + this->parent_->updated(enabled); +} + +void StreamerModePrivate::settingChanged(StreamerModeSetting value) +{ + if (value == this->currentSetting_) + { + return; + } + this->currentSetting_ = value; + + switch (this->currentSetting_) + { + case StreamerModeSetting::Disabled: { + this->setEnabled(false); + this->timer_.stop(); + } + break; + case StreamerModeSetting::Enabled: { + this->setEnabled(true); + this->timer_.stop(); + } + break; + case StreamerModeSetting::DetectStreamingSoftware: { + if (!this->timer_.isActive()) + { + this->timer_.start(20s); + this->check(); + } + } + break; + default: + assert(false && "Unexpected setting"); + break; + } +} + +void StreamerModePrivate::check() +{ + this->setEnabled(isBroadcasterSoftwareActive()); +} + +} // namespace chatterino diff --git a/src/singletons/StreamerMode.hpp b/src/singletons/StreamerMode.hpp new file mode 100644 index 000000000..5b7b6ef80 --- /dev/null +++ b/src/singletons/StreamerMode.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include + +namespace chatterino { + +class IStreamerMode : public QObject +{ + Q_OBJECT + +public: + IStreamerMode() = default; + ~IStreamerMode() override = default; + IStreamerMode(const IStreamerMode &) = delete; + IStreamerMode(IStreamerMode &&) = delete; + IStreamerMode &operator=(const IStreamerMode &) = delete; + IStreamerMode &operator=(IStreamerMode &&) = delete; + + [[nodiscard]] virtual bool isEnabled() const = 0; + +signals: + void changed(bool enabled); +}; + +class StreamerModePrivate; +class StreamerMode : public IStreamerMode +{ +public: + StreamerMode(); + ~StreamerMode() override; + StreamerMode(const StreamerMode &) = delete; + StreamerMode(StreamerMode &&) = delete; + StreamerMode &operator=(const StreamerMode &) = delete; + StreamerMode &operator=(StreamerMode &&) = delete; + + bool isEnabled() const override; + +private: + void updated(bool enabled); + + std::unique_ptr private_; + + friend class StreamerModePrivate; +}; + +} // namespace chatterino diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp index aab01b51d..51dbf4680 100644 --- a/src/singletons/Toasts.cpp +++ b/src/singletons/Toasts.cpp @@ -9,6 +9,7 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "util/StreamLink.hpp" #include "widgets/helper/CommonTexts.hpp" @@ -72,7 +73,7 @@ bool Toasts::isEnabled() { #ifdef Q_OS_WIN return WinToast::isCompatible() && getSettings()->notificationToast && - !(isInStreamerMode() && + !(getIApp()->getStreamerMode()->isEnabled() && getSettings()->streamerModeSuppressLiveNotifications); #else return false; diff --git a/src/util/StreamerMode.cpp b/src/util/StreamerMode.cpp deleted file mode 100644 index c905b88a0..000000000 --- a/src/util/StreamerMode.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "util/StreamerMode.hpp" - -#include "Application.hpp" -#include "common/QLogging.hpp" -#include "messages/MessageBuilder.hpp" -#include "providers/twitch/TwitchIrcServer.hpp" -#include "singletons/Settings.hpp" -#include "singletons/WindowManager.hpp" -#include "widgets/helper/NotebookTab.hpp" -#include "widgets/Notebook.hpp" -#include "widgets/splits/Split.hpp" -#include "widgets/Window.hpp" - -#include - -#ifdef USEWINSDK -// clang-format off -// These imports cannot be ordered alphabetically. -# include -# include -# include -// clang-format on -# pragma comment(lib, "Wtsapi32.lib") -#endif - -namespace chatterino { - -constexpr int cooldownInS = 10; - -bool shouldShowWarning = true; - -const QStringList &broadcastingBinaries() -{ -#ifdef USEWINSDK - static QStringList bins = { - "obs.exe", "obs64.exe", "PRISMLiveStudio.exe", - "XSplit.Core.exe", "TwitchStudio.exe", "vMix64.exe"}; -#else - static QStringList bins = {"obs", "Twitch Studio", "Streamlabs Desktop"}; -#endif - return bins; -} - -bool isInStreamerMode() -{ - switch (getSettings()->enableStreamerMode.getEnum()) - { - case StreamerModeSetting::Enabled: - return true; - case StreamerModeSetting::Disabled: - return false; - case StreamerModeSetting::DetectStreamingSoftware: - -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - - static bool cache = false; - static QDateTime time = QDateTime(); - - if (time.isValid() && - time.addSecs(cooldownInS) > QDateTime::currentDateTime()) - { - return cache; - } - time = QDateTime::currentDateTime(); - - QProcess p; - p.start("pgrep", {"-x", broadcastingBinaries().join("|")}, - QIODevice::NotOpen); - - if (p.waitForFinished(1000) && - p.exitStatus() == QProcess::NormalExit) - { - cache = (p.exitCode() == 0); - getApp()->streamerModeChanged.invoke(); - return (p.exitCode() == 0); - } - - // Fallback to false and showing a warning - - if (shouldShowWarning) - { - shouldShowWarning = false; - - getApp()->twitch->addGlobalSystemMessage( - "Streamer Mode is set to Automatic, but pgrep is missing. " - "Install it to fix the issue or set Streamer Mode to " - "Enabled or Disabled in the Settings."); - } - - qCWarning(chatterinoStreamerMode) << "pgrep execution timed out!"; - - cache = false; - getApp()->streamerModeChanged.invoke(); - return false; -#endif - -#ifdef USEWINSDK - if (!IsWindowsVistaOrGreater()) - { - return false; - } - static bool cache = false; - static QDateTime time = QDateTime(); - - if (time.isValid() && - time.addSecs(cooldownInS) > QDateTime::currentDateTime()) - { - return cache; - } - time = QDateTime::currentDateTime(); - - WTS_PROCESS_INFO *pWPIs = nullptr; - DWORD dwProcCount = 0; - - if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, - &pWPIs, &dwProcCount)) - { - //Go through all processes retrieved - for (DWORD i = 0; i < dwProcCount; i++) - { - QString processName = QString::fromUtf16( - reinterpret_cast(pWPIs[i].pProcessName)); - - if (broadcastingBinaries().contains(processName)) - { - cache = true; - getApp()->streamerModeChanged.invoke(); - return true; - } - } - } - - if (pWPIs) - { - WTSFreeMemory(pWPIs); - } - - cache = false; - getApp()->streamerModeChanged.invoke(); -#endif - return false; - } - return false; -} - -} // namespace chatterino diff --git a/src/util/StreamerMode.hpp b/src/util/StreamerMode.hpp deleted file mode 100644 index d161b9067..000000000 --- a/src/util/StreamerMode.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace chatterino { - -enum StreamerModeSetting { - Disabled = 0, - Enabled = 1, - DetectStreamingSoftware = 2, -}; - -const QStringList &broadcastingBinaries(); -bool isInStreamerMode(); - -} // namespace chatterino diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index 4ae8b22f3..c16181f80 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -7,10 +7,10 @@ #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/InitUpdateButton.hpp" -#include "util/StreamerMode.hpp" #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/NotebookButton.hpp" @@ -1437,9 +1437,8 @@ void SplitNotebook::addCustomButtons() getIApp()->getWindows()->showSettingsDialog( this, SettingsDialogPreference::StreamerMode); }); - this->signalHolder_.managedConnect(getApp()->streamerModeChanged, [this]() { - this->updateStreamerModeIcon(); - }); + QObject::connect(getIApp()->getStreamerMode(), &IStreamerMode::changed, + this, &SplitNotebook::updateStreamerModeIcon); this->updateStreamerModeIcon(); } @@ -1462,7 +1461,8 @@ void SplitNotebook::updateStreamerModeIcon() this->streamerModeIcon_->setPixmap( getResources().buttons.streamerModeEnabledDark); } - this->streamerModeIcon_->setVisible(isInStreamerMode()); + this->streamerModeIcon_->setVisible( + getIApp()->getStreamerMode()->isEnabled()); } void SplitNotebook::themeChangedEvent() diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 0111cc84c..8b3cea430 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -12,6 +12,7 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/Updates.hpp" #include "singletons/WindowManager.hpp" @@ -202,9 +203,8 @@ void Window::addCustomTitlebarButtons() getIApp()->getWindows()->showSettingsDialog( this, SettingsDialogPreference::StreamerMode); }); - this->signalHolder_.managedConnect(getApp()->streamerModeChanged, [this]() { - this->updateStreamerModeIcon(); - }); + QObject::connect(getIApp()->getStreamerMode(), &IStreamerMode::changed, + this, &Window::updateStreamerModeIcon); // Update initial state this->updateStreamerModeIcon(); @@ -231,7 +231,8 @@ void Window::updateStreamerModeIcon() this->streamerModeTitlebarIcon_->setPixmap( getResources().buttons.streamerModeEnabledDark); } - this->streamerModeTitlebarIcon_->setVisible(isInStreamerMode()); + this->streamerModeTitlebarIcon_->setVisible( + getIApp()->getStreamerMode()->isEnabled()); #else // clang-format off assert(false && "Streamer mode TitleBar icon should not exist on non-Windows OSes"); @@ -609,7 +610,7 @@ void Window::addShortcuts() } else if (mode == 2) { - if (isInStreamerMode()) + if (getIApp()->getStreamerMode()->isEnabled()) { getSettings()->enableStreamerMode.setValue( StreamerModeSetting::Disabled); diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index 781ab3a55..20d42459a 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -18,12 +18,12 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/Clipboard.hpp" #include "util/Helpers.hpp" #include "util/LayoutCreator.hpp" -#include "util/StreamerMode.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/InvisibleSizeGrip.hpp" @@ -858,7 +858,7 @@ void UserInfoPopup::updateUserData() this->ui_.userIDLabel->setText(TEXT_USER_ID + user.id); this->ui_.userIDLabel->setProperty("copy-text", user.id); - if (isInStreamerMode() && + if (getIApp()->getStreamerMode()->isEnabled() && getSettings()->streamerModeHideUsercardAvatars) { this->ui_.avatarButton->setPixmap(getResources().streamerMode); diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 47f3e3faa..e7920d878 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -26,13 +26,13 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/Clipboard.hpp" #include "util/DistanceBetweenPoints.hpp" #include "util/Helpers.hpp" #include "util/IncognitoBrowser.hpp" -#include "util/StreamerMode.hpp" #include "util/Twitch.hpp" #include "widgets/dialogs/ReplyThreadPopup.hpp" #include "widgets/dialogs/SettingsDialog.hpp" @@ -3079,7 +3079,8 @@ void ChannelView::setLinkInfoTooltip(LinkInfo *info) ImagePtr thumbnail; if (info->hasThumbnail() && thumbnailSize > 0) { - if (isInStreamerMode() && getSettings()->streamerModeHideLinkThumbnails) + if (getIApp()->getStreamerMode()->isEnabled() && + getSettings()->streamerModeHideLinkThumbnails) { thumbnail = Image::fromResourcePixmap(getResources().streamerMode); } diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 3bcf4dbd4..2923516cc 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -17,7 +17,6 @@ #include "util/FuzzyConvert.hpp" #include "util/Helpers.hpp" #include "util/IncognitoBrowser.hpp" -#include "util/StreamerMode.hpp" #include "widgets/BaseWindow.hpp" #include "widgets/settingspages/GeneralPageView.hpp" #include "widgets/splits/SplitInput.hpp" diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 0c4a6797f..8f105320f 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -15,11 +15,11 @@ #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" #include "util/LayoutHelper.hpp" -#include "util/StreamerMode.hpp" #include "widgets/dialogs/SettingsDialog.hpp" #include "widgets/helper/CommonTexts.hpp" #include "widgets/helper/EffectLabel.hpp" @@ -140,7 +140,7 @@ auto formatTooltip(const TwitchChannel::StreamStatus &s, QString thumbnail) }(); auto extraStreamData = [&s]() -> QString { - if (isInStreamerMode() && + if (getIApp()->getStreamerMode()->isEnabled() && getSettings()->streamerModeHideViewerCountAndDuration) { return QStringLiteral( diff --git a/tests/src/TwitchMessageBuilder.cpp b/tests/src/TwitchMessageBuilder.cpp index 6e9989410..7b6b42c33 100644 --- a/tests/src/TwitchMessageBuilder.cpp +++ b/tests/src/TwitchMessageBuilder.cpp @@ -7,6 +7,7 @@ #include "messages/MessageBuilder.hpp" #include "mocks/Channel.hpp" #include "mocks/ChatterinoBadges.hpp" +#include "mocks/DisabledStreamerMode.hpp" #include "mocks/EmptyApplication.hpp" #include "mocks/TwitchIrcServer.hpp" #include "mocks/UserData.hpp" @@ -86,6 +87,11 @@ public: return &this->seventvEmotes; } + IStreamerMode *getStreamerMode() override + { + return &this->streamerMode; + } + AccountController accounts; Emotes emotes; mock::UserDataController userData; @@ -97,6 +103,7 @@ public: BttvEmotes bttvEmotes; FfzEmotes ffzEmotes; SeventvEmotes seventvEmotes; + DisabledStreamerMode streamerMode; }; } // namespace