mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
refactor: turn StreamerMode
into a singleton(-like thing) (#5216)
This commit is contained in:
parent
ea19c5c989
commit
c1fa51242f
28 changed files with 386 additions and 210 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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<QJsonDocument> tryReadJsonFile(const QString &path)
|
||||
|
|
12
mocks/include/mocks/DisabledStreamerMode.hpp
Normal file
12
mocks/include/mocks/DisabledStreamerMode.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
|
||||
class DisabledStreamerMode : public chatterino::IStreamerMode
|
||||
{
|
||||
public:
|
||||
bool isEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include <pajlada/signals.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <QApplication>
|
||||
|
||||
#include <cassert>
|
||||
|
@ -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> seventvEmotes;
|
||||
const std::unique_ptr<Logging> logging;
|
||||
std::unique_ptr<ILinkResolver> linkResolver;
|
||||
std::unique_ptr<IStreamerMode> 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);
|
||||
|
|
|
@ -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
|
||||
$<$<BOOL:${WIN32}>:Wtsapi32>
|
||||
)
|
||||
if (CHATTERINO_PLUGINS)
|
||||
target_link_libraries(${LIBRARY_PROJECT} PUBLIC lua)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 <QFileInfo>
|
||||
|
||||
|
@ -204,7 +204,8 @@ void SharedMessageBuilder::triggerHighlights(
|
|||
const QString &channelName, bool playSound,
|
||||
const std::optional<QUrl> &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;
|
||||
|
|
|
@ -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 <QApplication>
|
||||
#include <QDebug>
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 <IrcMessage>
|
||||
#include <QLocale>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
#include "util/StreamerMode.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -186,11 +186,6 @@ Settings::Settings(const QString &settingsDirectory)
|
|||
},
|
||||
false);
|
||||
#endif
|
||||
this->enableStreamerMode.connect(
|
||||
[]() {
|
||||
getApp()->streamerModeChanged.invoke();
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
Settings::~Settings()
|
||||
|
|
|
@ -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 <pajlada/settings/setting.hpp>
|
||||
|
@ -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
|
||||
|
|
245
src/singletons/StreamerMode.cpp
Normal file
245
src/singletons/StreamerMode.cpp
Normal file
|
@ -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 <QAbstractEventDispatcher>
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// clang-format off
|
||||
# include <Windows.h>
|
||||
# include <VersionHelpers.h>
|
||||
# include <WtsApi32.h>
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
|
||||
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<bool> enabled_ = false;
|
||||
mutable std::atomic<uint8_t> 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<StreamerModeSetting>(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
|
48
src/singletons/StreamerMode.hpp
Normal file
48
src/singletons/StreamerMode.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<StreamerModePrivate> private_;
|
||||
|
||||
friend class StreamerModePrivate;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -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;
|
||||
|
|
|
@ -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 <QProcess>
|
||||
|
||||
#ifdef USEWINSDK
|
||||
// clang-format off
|
||||
// These imports cannot be ordered alphabetically.
|
||||
# include <Windows.h>
|
||||
# include <VersionHelpers.h>
|
||||
# include <WtsApi32.h>
|
||||
// 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<char16_t *>(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
|
|
@ -1,16 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum StreamerModeSetting {
|
||||
Disabled = 0,
|
||||
Enabled = 1,
|
||||
DetectStreamingSoftware = 2,
|
||||
};
|
||||
|
||||
const QStringList &broadcastingBinaries();
|
||||
bool isInStreamerMode();
|
||||
|
||||
} // namespace chatterino
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue