mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
249 lines
6 KiB
C++
249 lines
6 KiB
C++
#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->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_);
|
|
|
|
QObject::connect(&this->thread_, &QThread::started, [this] {
|
|
this->settingChanged(getSettings()->enableStreamerMode.getEnum());
|
|
});
|
|
this->thread_.start();
|
|
}
|
|
|
|
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
|