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