refactor: turn StreamerMode into a singleton(-like thing) (#5216)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user