Compare commits

..

7 Commits

Author SHA1 Message Date
WarmUpTill
9c3c953c6b Ignore deprecation warnings for Qt 6.7 and above
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-03-22 18:09:51 +01:00
WarmUpTill
ae74f68db7 Add option to close projector windows 2025-03-22 18:09:51 +01:00
WarmUpTill
213f1bba36 Add "Any" entry of the reward selection only for Twitch condition 2025-03-22 18:09:51 +01:00
WarmUpTill
5a2cb0bd68 Add more scripting signals / procedures 2025-03-22 18:09:51 +01:00
WarmUpTill
23b461828b Add start start / stop callbacks 2025-03-22 18:09:51 +01:00
WarmUpTill
9944a1b03b Move interval reset handling 2025-03-22 18:09:51 +01:00
WarmUpTill
eaad4d1bbd Fix Twitch helper caches misbehaving
API calls with different sets of arguments could map to the same key
value pair, which resulted in unexpected behavior for functions using
those caches
2025-03-22 18:09:51 +01:00
25 changed files with 417 additions and 113 deletions

View File

@@ -298,6 +298,13 @@ setup_obs_lib_dependency(${PROJECT_NAME})
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported Qt
# version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.0.0")
target_compile_definitions(${LIB_NAME} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
target_compile_options(
${PROJECT_NAME}
PRIVATE

View File

@@ -286,6 +286,12 @@ function(setup_advss_plugin target)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported
# Qt version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.7.0")
target_compile_definitions(${target} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
set_target_properties(
${target}
PROPERTIES AUTOMOC ON

View File

@@ -1113,6 +1113,8 @@ AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}"
AdvSceneSwitcher.action.projector="Projector"
AdvSceneSwitcher.action.projector.action.open="Open"
AdvSceneSwitcher.action.projector.action.close="Close"
AdvSceneSwitcher.action.projector.type.source="Source"
AdvSceneSwitcher.action.projector.type.scene="Scene"
AdvSceneSwitcher.action.projector.type.preview="Preview"
@@ -1121,8 +1123,9 @@ AdvSceneSwitcher.action.projector.type.multiview="Multiview"
AdvSceneSwitcher.action.projector.display="Display"
AdvSceneSwitcher.action.projector.windowed="Windowed"
AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
AdvSceneSwitcher.action.projector.entry.close="{{actions}}projector with name{{projectorWindowName}}{{regex}}"
AdvSceneSwitcher.action.projector.entry.open.windowed="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.open.fullscreen="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}on{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}"

View File

@@ -718,8 +718,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivue"
AdvSceneSwitcher.action.projector.display="Affichage"
AdvSceneSwitcher.action.projector.windowed="Fenêtré"
AdvSceneSwitcher.action.projector.fullscreen="Plein écran"
AdvSceneSwitcher.action.projector.entry="Ouvrir le projecteur{{windowTypes}}de{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="sur{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}"

View File

@@ -1068,8 +1068,6 @@ AdvSceneSwitcher.action.projector.type.multiview="マルチビュー"
AdvSceneSwitcher.action.projector.display="ディスプレイ"
AdvSceneSwitcher.action.projector.windowed="ウィンドウ"
AdvSceneSwitcher.action.projector.fullscreen="フルスクリーン"
AdvSceneSwitcher.action.projector.entry="{{types}}{{scenes}}{{sources}}の{{windowTypes}}プロジェクターを開きます"
AdvSceneSwitcher.action.projector.entry.monitor="{{monitors}}上で"
; AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="メッセージを{{device}}に送信します:"
AdvSceneSwitcher.action.midi.entry.listen="MIDI メッセージの選択を {{listenDevices}} で受信するメッセージに設定します:{{listenButton}}"

View File

@@ -1049,8 +1049,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivisão"
AdvSceneSwitcher.action.projector.display="Exibição"
AdvSceneSwitcher.action.projector.windowed="Janela"
AdvSceneSwitcher.action.projector.fullscreen="Tela cheia"
AdvSceneSwitcher.action.projector.entry="Abrir{{windowTypes}}projetor de{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="em{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Enviar mensagem para{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Definir seleção de mensagem MIDI para mensagens recebidas em{{listenDevices}}:{{listenButton}}"

View File

@@ -644,8 +644,6 @@ AdvSceneSwitcher.action.projector.type.multiview="多视图"
AdvSceneSwitcher.action.projector.display="显示"
AdvSceneSwitcher.action.projector.windowed="窗口"
AdvSceneSwitcher.action.projector.fullscreen="全屏"
AdvSceneSwitcher.action.projector.entry="打开为{{windowTypes}} 的 {{types}}{{scenes}}{{sources}}投影"
AdvSceneSwitcher.action.projector.entry.monitor="在 {{monitors}} 上 (从不咕咕的阿坤说:谨慎开启!)"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="发送消息到 {{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="将MIDI消息选择设置为 {{listenDevices}} 上传入的消息: {{listenButton}}"

View File

@@ -307,7 +307,7 @@ void SwitcherData::Thread()
}
}
ResetForNextInterval();
RunIntervalResetSteps();
if (match) {
if (macroMatch) {
@@ -357,14 +357,6 @@ void SwitcherData::SetPreconditions()
InvalidateMacroTempVarValues();
}
void SwitcherData::ResetForNextInterval()
{
// Plugin reset functions
for (const auto &func : resetIntervalSteps) {
func();
}
}
bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger,
bool &setPrevSceneAfterLinger,
@@ -445,7 +437,7 @@ void AutoStartActionQueues();
void SwitcherData::Start()
{
if (!(th && th->isRunning())) {
ResetForNextInterval();
RunIntervalResetSteps();
ResetMacros();
AutoStartActionQueues();
@@ -453,10 +445,7 @@ void SwitcherData::Start()
th = new SwitcherThread();
th->start((QThread::Priority)threadPriority);
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
nullptr);
RunStartSteps();
}
if (showSystemTrayNotifications) {
@@ -483,11 +472,7 @@ void SwitcherData::Stop()
th->wait();
delete th;
th = nullptr;
writeToStatusFile("Advanced Scene Switcher stopped");
if (!obsIsShuttingDown) {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
nullptr);
}
RunStopSteps();
}
if (showSystemTrayNotifications) {

View File

@@ -21,6 +21,29 @@ bool FileSwitch::pause = false;
static QObject *addPulse = nullptr;
static std::hash<std::string> strHash;
static void writeToStatusFile(const QString &msg)
{
if (!GetSwitcher() || !GetSwitcher()->fileIO.writeEnabled ||
GetSwitcher()->fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(GetSwitcher()->fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << Qt::endl;
}
file.close();
}
static bool _ = []() {
AddStartStep(
[]() { writeToStatusFile("Advanced Scene Switcher running"); });
AddStopStep(
[]() { writeToStatusFile("Advanced Scene Switcher stopped"); });
return true;
}();
void AdvSceneSwitcher::on_browseButton_clicked()
{
QString path = QFileDialog::getOpenFileName(
@@ -113,20 +136,6 @@ void SwitcherData::writeSceneInfoToFile()
}
}
void SwitcherData::writeToStatusFile(const QString &msg)
{
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << Qt::endl;
}
file.close();
}
bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene,
OBSWeakSource &transition)
{

View File

@@ -67,9 +67,7 @@ void SetMacroSwitchedScene(bool value)
{
static bool setupDone = false;
if (!setupDone) {
// Will always be called with switcher lock already held
AddIntervalResetStep([]() { macroSceneSwitched = false; },
false);
AddIntervalResetStep([]() { macroSceneSwitched = false; });
setupDone = true;
}
macroSceneSwitched = value;

View File

@@ -108,6 +108,24 @@ static const std::string setTempVarValueDeclString =
tempVarIdParam.data() + ", in string " + valueParam.data() +
", in int " + GetInstanceIdParamName().data() + ")";
/* Plugin status */
static constexpr std::string_view stopSignalName = "advss_plugin_stopped";
static constexpr std::string_view startSignalName = "advss_plugin_started";
static constexpr std::string_view getRunningStatusFuncName =
"advss_plugin_running";
static constexpr std::string_view resetIntervalSignalName =
"advss_interval_reset";
static const std::string stopSignalDeclString =
std::string("void ") + stopSignalName.data() + "()";
static const std::string startSignalDeclString =
std::string("void ") + startSignalName.data() + "()";
static const std::string resetIntervalDeclString =
std::string("void ") + resetIntervalSignalName.data() + "()";
static const std::string getRunningStatusDeclString =
std::string("bool ") + getRunningStatusFuncName.data() + "()";
static bool setup();
static bool setupDone = setup();
@@ -134,6 +152,25 @@ static bool setup()
&ScriptHandler::DeregisterAllTempVars, nullptr);
proc_handler_add(ph, setTempVarValueDeclString.c_str(),
&ScriptHandler::SetTempVarValue, nullptr);
proc_handler_add(ph, getRunningStatusDeclString.c_str(),
&ScriptHandler::GetRunningStatus, nullptr);
auto sh = obs_get_signal_handler();
signal_handler_add(sh, stopSignalDeclString.c_str());
signal_handler_add(sh, startSignalDeclString.c_str());
signal_handler_add(sh, resetIntervalDeclString.c_str());
static constexpr auto triggerSignal = [](const std::string_view &name) {
auto sh = obs_get_signal_handler();
struct calldata data;
calldata_init(&data);
signal_handler_signal(sh, name.data(), &data);
calldata_free(&data);
};
AddIntervalResetStep([]() { triggerSignal(resetIntervalSignalName); });
AddStartStep([]() { triggerSignal(startSignalName); });
AddStopStep([]() { triggerSignal(stopSignalName); });
return true;
}
@@ -476,6 +513,11 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
RETURN_SUCCESS();
}
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data)
{
calldata_set_bool(data, "is_running", PluginIsRunning());
}
void ScriptHandler::RegisterTempVar(void *, calldata_t *data)
{
const char *variableId;

View File

@@ -32,6 +32,7 @@ public:
static void DeregisterAllTempVars(void *ctx, calldata_t *data);
static void SetTempVarValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data);
static void GetRunningStatus(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);

View File

@@ -168,17 +168,6 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
obs_data_set_string(obj, "version", currentVersion.c_str());
}
void SwitcherData::AddIntervalResetStep(std::function<void()> function,
bool tryLock)
{
if (!tryLock) {
resetIntervalSteps.emplace_back(function);
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
resetIntervalSteps.emplace_back(function);
}
void SwitcherData::RunPostLoadSteps()
{
for (const auto &func : postLoadSteps) {

View File

@@ -57,11 +57,9 @@ public:
bool AnySceneTransitionStarted();
void SetPreconditions();
void ResetForNextInterval();
void AddSaveStep(std::function<void(obs_data_t *)>);
void AddLoadStep(std::function<void(obs_data_t *)>);
void AddPostLoadStep(std::function<void()>);
void AddIntervalResetStep(std::function<void()>, bool lock = true);
void RunPostLoadSteps();
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger, bool &setPreviousSceneAsMatch,
@@ -100,7 +98,6 @@ public:
std::vector<std::function<void(obs_data_t *)>> saveSteps;
std::vector<std::function<void(obs_data_t *)>> loadSteps;
std::vector<std::function<void()>> postLoadSteps;
std::vector<std::function<void()>> resetIntervalSteps;
bool firstBoot = true;
bool transitionActive = false;
@@ -233,7 +230,6 @@ public:
bool checkPause();
void checkDefaultSceneTransitions();
void writeSceneInfoToFile();
void writeToStatusFile(const QString &msg);
void checkSwitchCooldown(bool &match);
std::deque<WindowSwitch> windowSwitches;

View File

@@ -21,6 +21,24 @@ static std::vector<std::function<void()>> &getPluginCleanupSteps()
return steps;
}
static std::vector<std::function<void()>> &getResetIntervalSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getStartSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getStopSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::mutex mutex;
void SavePluginSettings(obs_data_t *obj)
@@ -48,9 +66,10 @@ void AddPostLoadStep(std::function<void()> step)
GetSwitcher()->AddPostLoadStep(step);
}
void AddIntervalResetStep(std::function<void()> step, bool lock)
void AddIntervalResetStep(std::function<void()> step)
{
GetSwitcher()->AddIntervalResetStep(step, lock);
std::lock_guard<std::mutex> lock(mutex);
getResetIntervalSteps().emplace_back(step);
}
void RunPostLoadSteps()
@@ -100,6 +119,42 @@ void RunPluginCleanupSteps()
}
}
void RunIntervalResetSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getResetIntervalSteps()) {
step();
}
}
void AddStartStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
getStartSteps().emplace_back(step);
}
void AddStopStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
getStopSteps().emplace_back(step);
}
void RunStartSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getStartSteps()) {
step();
}
}
void RunStopSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getStopSteps()) {
step();
}
}
void StopPlugin()
{
GetSwitcher()->Stop();

View File

@@ -11,7 +11,7 @@ EXPORT void LoadPluginSettings(obs_data_t *);
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
EXPORT void AddPostLoadStep(std::function<void()>);
EXPORT void AddIntervalResetStep(std::function<void()>, bool lock = true);
EXPORT void AddIntervalResetStep(std::function<void()>);
EXPORT void RunPostLoadSteps();
EXPORT void AddPluginInitStep(std::function<void()>);
@@ -25,6 +25,11 @@ EXPORT void StopPlugin();
EXPORT void StartPlugin();
EXPORT bool PluginIsRunning();
EXPORT int GetIntervalValue();
void AddStartStep(std::function<void()>);
void AddStopStep(std::function<void()>);
void RunStartSteps();
void RunStopSteps();
void RunIntervalResetSteps();
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior);

View File

@@ -23,6 +23,14 @@ static bool setupDone = setup();
bool setup()
{
AddPluginPostLoadStep(registerWebsocketVendor);
AddStartStep([]() {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
nullptr);
});
AddStopStep([]() {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
nullptr);
});
return true;
}
@@ -109,6 +117,9 @@ void RegisterWebsocketRequest(
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
{
if (OBSIsShuttingDown()) {
return;
}
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
}

View File

@@ -5,6 +5,8 @@
#include "source-helpers.hpp"
#include <obs-frontend-api.h>
#include <QApplication>
#include <QWindow>
namespace advss {
@@ -28,8 +30,34 @@ const static std::map<MacroActionProjector::Type, std::string> selectionTypes =
"AdvSceneSwitcher.action.projector.type.multiview"},
};
static void closeOBSProjectorWindows(const std::string expectedWindowTitle,
const RegexConfig &regex)
{
for (QWindow *widget : QApplication::allWindows()) {
if (!widget->property("isOBSProjectorWindow").toBool()) {
continue;
}
auto const windowTitle = widget->title().toStdString();
if (!regex.Enabled() && expectedWindowTitle != windowTitle) {
continue;
}
if (!regex.Matches(windowTitle, expectedWindowTitle)) {
continue;
}
widget->close();
}
}
bool MacroActionProjector::PerformAction()
{
if (_action == Action::CLOSE) {
closeOBSProjectorWindows(_projectorWindowName, _regex);
return true;
}
std::string name = "";
const char *type = "";
@@ -78,10 +106,16 @@ bool MacroActionProjector::PerformAction()
void MacroActionProjector::LogAction() const
{
if (_action == Action::CLOSE) {
ablog(LOG_INFO, "closing projector window \"%s\"",
_projectorWindowName.c_str());
return;
}
auto it = selectionTypes.find(_type);
if (it != selectionTypes.end()) {
ablog(LOG_INFO,
"performed projector action \"%s\" with"
"open projector \"%s\" with"
"source \"%s\","
"scene \"%s\","
"monitor %d",
@@ -96,24 +130,30 @@ void MacroActionProjector::LogAction() const
bool MacroActionProjector::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_int(obj, "monitor", _monitor);
obs_data_set_string(obj, "monitorName", _monitorName.c_str());
obs_data_set_bool(obj, "fullscreen", _fullscreen);
_scene.Save(obj);
_source.Save(obj);
_projectorWindowName.Save(obj, "projectorWindowName");
_regex.Save(obj);
return true;
}
bool MacroActionProjector::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_monitor = obs_data_get_int(obj, "monitor");
_monitorName = obs_data_get_string(obj, "monitorName");
_fullscreen = obs_data_get_bool(obj, "fullscreen");
_scene.Load(obj);
_source.Load(obj);
_projectorWindowName.Load(obj, "projectorWindowName");
_regex.Load(obj);
return true;
}
@@ -131,6 +171,7 @@ void MacroActionProjector::ResolveVariablesToFixedValues()
{
_source.ResolveVariables();
_scene.ResolveVariables();
_projectorWindowName.ResolveVariables();
}
void MacroActionProjector::SetMonitor(int idx)
@@ -166,14 +207,22 @@ bool MacroActionProjector::MonitorSetupChanged() const
QString::fromStdString(_monitorName);
}
static inline void populateSelectionTypes(QComboBox *list)
static void populateActionSelection(QComboBox *list)
{
for (auto entry : selectionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.projector.action.open"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.projector.action.close"));
}
static void populateSelectionTypes(QComboBox *list)
{
for (const auto &[_, name] : selectionTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
static inline void populateWindowTypes(QComboBox *list)
static void populateWindowTypes(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.action.projector.windowed"));
@@ -184,14 +233,18 @@ static inline void populateWindowTypes(QComboBox *list)
MacroActionProjectorEdit::MacroActionProjectorEdit(
QWidget *parent, std::shared_ptr<MacroActionProjector> entryData)
: QWidget(parent),
_windowTypes(new QComboBox()),
_actions(new QComboBox()),
_types(new QComboBox()),
_windowTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, false, true, true,
true)),
_sources(new SourceSelectionWidget(window(), QStringList(), true)),
_monitorSelection(new QHBoxLayout()),
_monitors(new QComboBox())
_monitors(new QComboBox()),
_projectorWindowName(new VariableLineEdit(this)),
_regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout(this))
{
populateActionSelection(_actions);
populateWindowTypes(_windowTypes);
populateSelectionTypes(_types);
auto sources = GetSourceNames();
@@ -201,6 +254,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
_monitors->setPlaceholderText(
obs_module_text("AdvSceneSwitcher.selectDisplay"));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(WindowTypeChanged(int)));
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
@@ -212,24 +267,15 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
SLOT(SourceChanged(const SourceSelection &)));
QWidget::connect(_monitors, SIGNAL(currentIndexChanged(int)), this,
SLOT(MonitorChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{windowTypes}}", _windowTypes}, {"{{types}}", _types},
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
{"{{monitors}}", _monitors},
};
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.projector.entry.monitor"),
_monitorSelection, widgetPlaceholders);
QHBoxLayout *mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.projector.entry"),
mainLayout, widgetPlaceholders);
mainLayout->insertLayout(mainLayout->count() - 1, _monitorSelection);
setLayout(mainLayout);
QWidget::connect(_projectorWindowName, SIGNAL(editingFinished()), this,
SLOT(ProjectorWindowNameChanged()));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
_entryData = entryData;
SetWidgetLayout();
setLayout(_layout);
UpdateEntryData();
_loading = false;
}
@@ -239,11 +285,15 @@ void MacroActionProjectorEdit::UpdateEntryData()
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_windowTypes->setCurrentIndex(_entryData->_fullscreen ? 1 : 0);
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
_scenes->SetScene(_entryData->_scene);
_sources->SetSource(_entryData->_source);
_monitors->setCurrentIndex(_entryData->GetMonitor());
_projectorWindowName->setText(_entryData->_projectorWindowName);
_regex->SetRegexConfig(_entryData->_regex);
SetWidgetVisibility();
}
@@ -265,12 +315,26 @@ void MacroActionProjectorEdit::MonitorChanged(int value)
_entryData->SetMonitor(value);
}
void MacroActionProjectorEdit::ProjectorWindowNameChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_projectorWindowName =
_projectorWindowName->text().toStdString();
}
void MacroActionProjectorEdit::RegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_regex = regex;
}
void MacroActionProjectorEdit::WindowTypeChanged(int)
{
GUARD_LOADING_AND_LOCK();
_entryData->_fullscreen =
_windowTypes->currentText() ==
obs_module_text("AdvSceneSwitcher.action.projector.fullscreen");
SetWidgetLayout();
SetWidgetVisibility();
}
@@ -281,17 +345,69 @@ void MacroActionProjectorEdit::TypeChanged(int value)
SetWidgetVisibility();
}
void MacroActionProjectorEdit::ActionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionProjector::Action>(idx);
SetWidgetLayout();
SetWidgetVisibility();
}
void MacroActionProjectorEdit::SetWidgetLayout()
{
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
{"{{windowTypes}}", _windowTypes},
{"{{types}}", _types},
{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{monitors}}", _monitors},
{"{{projectorWindowName}}", _projectorWindowName},
{"{{regex}}", _regex},
};
for (const auto &[_, widget] : widgetPlaceholders) {
_layout->removeWidget(widget);
}
ClearLayout(_layout);
const char *layoutText;
if (_entryData->_action == MacroActionProjector::Action::CLOSE) {
layoutText = "AdvSceneSwitcher.action.projector.entry.close";
} else if (_entryData->_fullscreen) {
layoutText =
"AdvSceneSwitcher.action.projector.entry.open.fullscreen";
} else {
layoutText =
"AdvSceneSwitcher.action.projector.entry.open.windowed";
}
PlaceWidgets(obs_module_text(layoutText), _layout, widgetPlaceholders);
}
void MacroActionProjectorEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_scenes->setVisible(_entryData->_type ==
MacroActionProjector::Type::SCENE);
_sources->setVisible(_entryData->_type ==
MacroActionProjector::Type::SOURCE);
SetLayoutVisible(_monitorSelection, _entryData->_fullscreen);
_projectorWindowName->setVisible(_entryData->_action ==
MacroActionProjector::Action::CLOSE);
_regex->setVisible(_entryData->_action ==
MacroActionProjector::Action::CLOSE);
_types->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN);
_windowTypes->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN);
_scenes->setVisible(
_entryData->_action == MacroActionProjector::Action::OPEN &&
_entryData->_type == MacroActionProjector::Type::SCENE);
_sources->setVisible(
_entryData->_action == MacroActionProjector::Action::OPEN &&
_entryData->_type == MacroActionProjector::Type::SOURCE);
_monitors->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN &&
_entryData->_fullscreen);
adjustSize();
updateGeometry();

View File

@@ -1,7 +1,9 @@
#pragma once
#include "macro-action-edit.hpp"
#include "regex-config.hpp"
#include "scene-selection.hpp"
#include "source-selection.hpp"
#include "variable-line-edit.hpp"
namespace advss {
@@ -19,6 +21,11 @@ public:
void SetMonitor(int);
int GetMonitor() const;
enum class Action {
OPEN,
CLOSE,
};
enum class Type {
SOURCE,
SCENE,
@@ -27,10 +34,13 @@ public:
MULTIVIEW,
};
Action _action = Action::OPEN;
Type _type = Type::SCENE;
SourceSelection _source;
SceneSelection _scene;
bool _fullscreen = true;
StringVariable _projectorWindowName = "Windowed Projector";
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig(true);
private:
bool MonitorSetupChanged() const;
@@ -60,21 +70,28 @@ public:
}
private slots:
void ActionChanged(int value);
void WindowTypeChanged(int value);
void TypeChanged(int value);
void SceneChanged(const SceneSelection &);
void SourceChanged(const SourceSelection &);
void MonitorChanged(int value);
void ProjectorWindowNameChanged();
void RegexChanged(const RegexConfig &);
private:
void SetWidgetLayout();
void SetWidgetVisibility();
QComboBox *_windowTypes;
QComboBox *_actions;
QComboBox *_types;
QComboBox *_windowTypes;
SceneSelectionWidget *_scenes;
SourceSelectionWidget *_sources;
QHBoxLayout *_monitorSelection;
QComboBox *_monitors;
VariableLineEdit *_projectorWindowName;
RegexConfigWidget *_regex;
QHBoxLayout *_layout;
std::shared_ptr<MacroActionProjector> _entryData;
bool _loading = true;

View File

@@ -826,7 +826,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_userInfoQueryType(new QComboBox(this)),
_userLogin(new VariableLineEdit(this)),
_userId(new VariableSpinBox(this)),
_pointsReward(new TwitchPointsRewardWidget(this)),
_pointsReward(new TwitchPointsRewardWidget(this, false)),
_rewardVariable(new VariableSelection(this)),
_toggleRewardSelection(new QPushButton())
{

View File

@@ -1450,7 +1450,7 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
_tokens(new TwitchConnectionSelection()),
_tokenWarning(new QLabel()),
_channel(new TwitchChannelSelection(this)),
_pointsReward(new TwitchPointsRewardWidget(this)),
_pointsReward(new TwitchPointsRewardWidget(this, true)),
_streamTitle(new VariableLineEdit(this)),
_regexTitle(new RegexConfigWidget(parent)),
_chatMesageEdit(new ChatMessageEdit(this)),

View File

@@ -135,9 +135,10 @@ void TwitchPointsRewardSelection::SelectionChanged(int index)
emit PointsRewardChanged(pointsReward);
}
TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent)
TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent,
bool allowAny)
: QWidget(parent),
_selection(new TwitchPointsRewardSelection(this)),
_selection(new TwitchPointsRewardSelection(this, allowAny)),
_refreshButton(new QPushButton(this))
{
_refreshButton->setMaximumWidth(22);

View File

@@ -20,7 +20,7 @@ class TwitchPointsRewardSelection : public FilterComboBox {
Q_OBJECT
public:
TwitchPointsRewardSelection(QWidget *parent, bool allowAny = true);
TwitchPointsRewardSelection(QWidget *parent, bool allowAny);
void SetPointsReward(const TwitchPointsReward &pointsReward);
void SetChannel(const TwitchChannel &channel);
@@ -53,7 +53,7 @@ class TwitchPointsRewardWidget : public QWidget {
Q_OBJECT
public:
TwitchPointsRewardWidget(QWidget *parent);
TwitchPointsRewardWidget(QWidget *parent, bool allowAny);
void SetPointsReward(const TwitchPointsReward &pointsReward);
void SetChannel(const TwitchChannel &channel);

View File

@@ -29,17 +29,23 @@ public:
_headers(headers)
{
}
bool operator<(const Args &other) const
bool operator==(const Args &other) const
{
bool ret = true;
ret = ret && _uri < other._uri;
ret = ret && _path < other._path;
ret = ret && _params < other._params;
ret = ret && _data < other._data;
ret = ret && _headers < other._headers;
ret = ret && _uri == other._uri;
ret = ret && _path == other._path;
ret = ret && _params == other._params;
ret = ret && _data == other._data;
ret = ret && _headers == other._headers;
return ret;
}
const std::string &uri() const { return _uri; }
const std::string &path() const { return _path; }
const std::string &data() const { return _data; }
const httplib::Params &params() const { return _params; }
const httplib::Headers &headers() const { return _headers; }
private:
std::string _uri;
std::string _path;
@@ -48,6 +54,37 @@ private:
httplib::Headers _headers;
};
}; // namespace advss
template<> struct std::hash<advss::Args> {
inline std::size_t operator()(const advss::Args &args) const
{
static constexpr auto hash_combine = [](std::size_t &seed,
std::size_t hashValue) {
seed ^= hashValue + 0x9e3779b9 + (seed << 6) +
(seed >> 2);
};
std::size_t seed = 0;
hash_combine(seed, std::hash<std::string>()(args.uri()));
hash_combine(seed, std::hash<std::string>()(args.path()));
hash_combine(seed, std::hash<std::string>()(args.data()));
for (const auto &[key, value] : args.params()) {
hash_combine(seed, std::hash<std::string>()(key));
hash_combine(seed, std::hash<std::string>()(value));
}
for (const auto &[key, value] : args.headers()) {
hash_combine(seed, std::hash<std::string>()(key));
hash_combine(seed, std::hash<std::string>()(value));
}
return seed;
}
};
namespace advss {
struct CacheEntry {
RequestResult result;
std::chrono::system_clock::time_point cacheTime =
@@ -61,14 +98,15 @@ static bool cacheIsTooOld(const CacheEntry &cache)
return diff >= std::chrono::seconds(cacheTimeoutSeconds);
}
static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
static bool cacheIsValid(const std::unordered_map<Args, CacheEntry> &cache,
const Args &args)
{
auto it = cache.find(args);
return it != cache.end() && !cacheIsTooOld(it->second);
}
static void cleanupCache(std::map<Args, CacheEntry> &cache, std::mutex &mtx)
static void cleanupCache(std::unordered_map<Args, CacheEntry> &cache,
std::mutex &mtx)
{
std::lock_guard<std::mutex> lock(mtx);
cache.clear();
@@ -181,7 +219,7 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
return {};
}
static std::map<Args, CacheEntry> cache;
static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx;
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -251,7 +289,7 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
return {};
}
static std::map<Args, CacheEntry> cache;
static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx;
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -322,7 +360,7 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
return {};
}
static std::map<Args, CacheEntry> cache;
static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx;
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -393,7 +431,7 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
return {};
}
static std::map<Args, CacheEntry> cache;
static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx;
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });

View File

@@ -148,6 +148,39 @@ def script_load(settings):
],
)
# Other signals and procedures, which might be useful
def plugin_stop_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from stop handler!")
get_running_status()
def plugin_start_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from start handler!")
get_running_status()
def get_running_status():
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.proc_handler_call(proc_handler, "advss_plugin_running", data)
success = obs.calldata_bool(data, "is_running")
obs.script_log(
obs.LOG_INFO, f"The advanced scene switcher is currently running: {success}"
)
obs.calldata_destroy(data)
def interval_reset_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from reset handler!")
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(
signal_handler, "advss_plugin_stopped", plugin_stop_handler
)
obs.signal_handler_connect(
signal_handler, "advss_plugin_started", plugin_start_handler
)
obs.signal_handler_connect(
signal_handler, "advss_interval_reset", interval_reset_handler
)
def script_unload():
# Deregistering is useful if you plan on reloading the script files