refactor: turn StreamerMode into a singleton(-like thing) (#5216)

This commit is contained in:
nerix
2024-03-01 21:12:02 +01:00
committed by GitHub
parent ea19c5c989
commit c1fa51242f
28 changed files with 386 additions and 210 deletions
-5
View File
@@ -186,11 +186,6 @@ Settings::Settings(const QString &settingsDirectory)
},
false);
#endif
this->enableStreamerMode.connect(
[]() {
getApp()->streamerModeChanged.invoke();
},
false);
}
Settings::~Settings()
+6 -1
View File
@@ -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
View 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
View 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
+2 -1
View File
@@ -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;