#include "macro-action-plugin-state.hpp" #include "layout-helpers.hpp" #include "plugin-state-helpers.hpp" #include "selection-helpers.hpp" #include "source-helpers.hpp" #include "ui-helpers.hpp" #include #include #include #include using namespace std::chrono_literals; namespace advss { const std::string MacroActionPluginState::id = "plugin_state"; bool MacroActionPluginState::_registered = MacroActionFactory::Register( MacroActionPluginState::id, {MacroActionPluginState::Create, MacroActionPluginStateEdit::Create, "AdvSceneSwitcher.action.pluginState"}); const static std::map actionTypes = { {PluginStateAction::STOP, "AdvSceneSwitcher.action.pluginState.type.stop"}, {PluginStateAction::NO_MATCH_BEHAVIOUR, "AdvSceneSwitcher.action.pluginState.type.noMatch"}, {PluginStateAction::IMPORT_SETTINGS, "AdvSceneSwitcher.action.pluginState.type.import"}, {PluginStateAction::TERMINATE, "AdvSceneSwitcher.action.pluginState.type.terminate"}, }; const static std::map noMatchValues = { {NoMatchBehavior::NO_SWITCH, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch"}, {NoMatchBehavior::SWITCH, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo"}, {NoMatchBehavior::RANDOM_SWITCH, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom"}, }; static void stopPlugin() { std::thread t([]() { StopPlugin(); }); t.detach(); } static void importSettings(const std::string &path) { if (SettingsWindowIsOpened()) { return; } OBSDataAutoRelease obj = obs_data_create_from_json_file(path.c_str()); if (!obj) { return; } LoadPluginSettings(obj); } static void setNoMatchBehaviour(int value, OBSWeakSource &scene) { SetPluginNoMatchBehavior(static_cast(value)); if (GetPluginNoMatchBehavior() == NoMatchBehavior::SWITCH) { SetNoMatchScene(scene); } } static void closeOBSWindow() { blog(LOG_WARNING, "closing OBS window now!"); auto obsWindow = static_cast(obs_frontend_get_main_window()); if (obsWindow) { obsWindow->close(); } else { blog(LOG_WARNING, "OBS shutdown was aborted - failed to get QMainWindow"); } } static void terminateOBS(void *) { static std::mutex mtx; static std::mutex waitMutex; static std::condition_variable cv; static bool abortTerminate; static bool stopWaiting; static std::chrono::high_resolution_clock::time_point lastShutdownAttempt{}; // Don't allow multiple simultaneous instances of the shutdown dialog std::unique_lock lock(mtx, std::try_to_lock); if (!lock.owns_lock()) { blog(LOG_INFO, "OBS shutdown dialog already triggered - ignoring additional request"); return; } // Prevent the user from locking himself out of controlling OBS by // continuously opening the shutdown dialog auto now = std::chrono::high_resolution_clock::now(); if (lastShutdownAttempt + 5s > now) { blog(LOG_INFO, "OBS shutdown dialog already triggered recently - ignoring request"); return; } lastShutdownAttempt = now; abortTerminate = false; stopWaiting = false; // Give the user a 10s grace period during which the shutdown can be aborted std::thread thread([]() { std::unique_lock lock(waitMutex); if (cv.wait_for(lock, 10s, [] { return stopWaiting; })) { if (abortTerminate) { blog(LOG_INFO, "OBS shutdown was aborted"); } else { closeOBSWindow(); } } else { closeOBSWindow(); } }); thread.detach(); // Ask the user how to proceed abortTerminate = !DisplayMessage( obs_module_text( "AdvSceneSwitcher.action.pluginState.terminateConfirm"), true, false); stopWaiting = true; // This closes OBS immediately if shutdown was confirmed cv.notify_all(); lock.unlock(); } bool MacroActionPluginState::PerformAction() { switch (_action) { case PluginStateAction::STOP: stopPlugin(); break; case PluginStateAction::NO_MATCH_BEHAVIOUR: setNoMatchBehaviour(_value, _scene); break; case PluginStateAction::IMPORT_SETTINGS: importSettings(_settingsPath); // There is no point in continuing // The settings will be invalid return false; case PluginStateAction::TERMINATE: { std::thread thread([]() { obs_queue_task(OBS_TASK_UI, terminateOBS, nullptr, false); }); thread.detach(); break; } default: break; } return true; } void MacroActionPluginState::LogAction() const { switch (_action) { case PluginStateAction::STOP: blog(LOG_INFO, "stop() called by macro"); break; case PluginStateAction::NO_MATCH_BEHAVIOUR: ablog(LOG_INFO, "setting no match to %d", _value); break; case PluginStateAction::IMPORT_SETTINGS: ablog(LOG_INFO, "importing settings from %s", _settingsPath.c_str()); break; case PluginStateAction::TERMINATE: ablog(LOG_INFO, "sending terminate signal to OBS in 10s"); break; default: blog(LOG_WARNING, "ignored unknown pluginState action %d", static_cast(_action)); break; } } bool MacroActionPluginState::Save(obs_data_t *obj) const { MacroAction::Save(obj); obs_data_set_int(obj, "action", static_cast(_action)); obs_data_set_int(obj, "value", _value); obs_data_set_string(obj, "scene", GetWeakSourceName(_scene).c_str()); _settingsPath.Save(obj, "settingsPath"); return true; } bool MacroActionPluginState::Load(obs_data_t *obj) { MacroAction::Load(obj); _action = static_cast(obs_data_get_int(obj, "action")); _value = obs_data_get_int(obj, "value"); const char *sceneName = obs_data_get_string(obj, "scene"); _scene = GetWeakSourceByName(sceneName); _settingsPath.Load(obj, "settingsPath"); return true; } std::shared_ptr MacroActionPluginState::Create(Macro *m) { return std::make_shared(m); } std::shared_ptr MacroActionPluginState::Copy() const { return std::make_shared(*this); } void MacroActionPluginState::ResolveVariablesToFixedValues() { _settingsPath = std::string(_settingsPath); } static inline void populateActionSelection(QComboBox *list) { for (const auto &[_, name] : actionTypes) { list->addItem(obs_module_text(name.c_str())); } } static inline void populateValueSelection(QComboBox *list, PluginStateAction action) { if (action == PluginStateAction::NO_MATCH_BEHAVIOUR) { for (const auto &[_, name] : noMatchValues) { list->addItem(obs_module_text(name.c_str())); } } } MacroActionPluginStateEdit::MacroActionPluginStateEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent) { _actions = new QComboBox(); _values = new QComboBox(); _scenes = new QComboBox(); _settings = new FileSelection(); _settingsWarning = new QLabel(obs_module_text( "AdvSceneSwitcher.action.pluginState.importWarning")); populateActionSelection(_actions); PopulateSceneSelection(_scenes); QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, SLOT(ActionChanged(int))); QWidget::connect(_values, SIGNAL(currentIndexChanged(int)), this, SLOT(ValueChanged(int))); QWidget::connect(_scenes, SIGNAL(currentTextChanged(const QString &)), this, SLOT(SceneChanged(const QString &))); QWidget::connect(_settings, SIGNAL(PathChanged(const QString &)), this, SLOT(PathChanged(const QString &))); QHBoxLayout *mainLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{actions}}", _actions}, {"{{values}}", _values}, {"{{scenes}}", _scenes}, {"{{settings}}", _settings}, {"{{settingsWarning}}", _settingsWarning}, }; PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.pluginState.entry"), mainLayout, widgetPlaceholders); setLayout(mainLayout); _entryData = entryData; UpdateEntryData(); _loading = false; } void MacroActionPluginStateEdit::UpdateEntryData() { if (!_entryData) { return; } _actions->setCurrentIndex(static_cast(_entryData->_action)); populateValueSelection(_values, _entryData->_action); _values->setCurrentIndex(_entryData->_value); _scenes->setCurrentText(GetWeakSourceName(_entryData->_scene).c_str()); _settings->SetPath(_entryData->_settingsPath); SetWidgetVisibility(); } void MacroActionPluginStateEdit::ActionChanged(int value) { if (_loading || !_entryData) { return; } { auto lock = LockContext(); _entryData->_action = static_cast(value); SetWidgetVisibility(); } _values->clear(); populateValueSelection(_values, _entryData->_action); } void MacroActionPluginStateEdit::ValueChanged(int value) { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_value = value; SetWidgetVisibility(); } void MacroActionPluginStateEdit::SceneChanged(const QString &text) { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_scene = GetWeakSourceByQString(text); } void MacroActionPluginStateEdit::PathChanged(const QString &text) { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_settingsPath = text.toStdString(); } void MacroActionPluginStateEdit::SetWidgetVisibility() { if (!_entryData) { return; } _values->hide(); _scenes->hide(); _settings->hide(); _settingsWarning->hide(); switch (_entryData->_action) { case PluginStateAction::STOP: break; case PluginStateAction::NO_MATCH_BEHAVIOUR: _values->show(); if (static_cast(_entryData->_value) == NoMatchBehavior::SWITCH) { _scenes->show(); } break; case PluginStateAction::IMPORT_SETTINGS: _settings->show(); _settingsWarning->show(); break; default: break; } } } // namespace advss