Add "Run" condition

Allows to use external programs as conditions
This commit is contained in:
WarmUpTill 2023-02-22 17:15:49 +01:00 committed by WarmUpTill
parent b77f8717fe
commit 16fa91c2a1
3 changed files with 311 additions and 5 deletions

View File

@ -416,6 +416,9 @@ AdvSceneSwitcher.condition.variable.type.equalsVariable="equals variable"
AdvSceneSwitcher.condition.variable.type.lessThanVariable="is less than variable"
AdvSceneSwitcher.condition.variable.type.greaterThanVariable="is greater than variable"
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
AdvSceneSwitcher.condition.run="Run"
AdvSceneSwitcher.condition.run.entry="Process exits before timeout of{{timeout}} seconds"
AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}Check for exit code{{exitCode}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
@ -463,11 +466,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Stop streaming"
AdvSceneSwitcher.action.streaming.type.start="Start streaming"
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.run.arguments="Arguments:"
AdvSceneSwitcher.action.run.addArgument="Add argument"
AdvSceneSwitcher.action.run.addArgumentDescription="Add new argument:"
AdvSceneSwitcher.action.run.entry="Run {{filePath}}"
AdvSceneSwitcher.action.run.entry.workingDirectory="Working directory:{{workingDirectory}}"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
@ -921,6 +919,13 @@ AdvSceneSwitcher.regex.dotMatchNewline=". matches newlines"
AdvSceneSwitcher.regex.multiLine="^ and $ match start/end of line"
AdvSceneSwitcher.regex.extendedPattern="Enable Qt's ExtendedPatternSyntax"
AdvSceneSwitcher.process.showAdvanced="Show advanced settings"
AdvSceneSwitcher.process.arguments="Arguments:"
AdvSceneSwitcher.process.addArgument="Add argument"
AdvSceneSwitcher.process.addArgumentDescription="Add new argument:"
AdvSceneSwitcher.process.entry="Run{{filePath}}{{advancedSettings}}"
AdvSceneSwitcher.process.entry.workingDirectory="Working directory:{{workingDirectory}}"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@ -0,0 +1,222 @@
#include "macro-condition-edit.hpp"
#include "macro-condition-run.hpp"
#include "utility.hpp"
#include "advanced-scene-switcher.hpp"
#include <QProcess>
#include <QDesktopServices>
const std::string MacroConditionRun::id = "run";
bool MacroConditionRun::_registered = MacroConditionFactory::Register(
MacroConditionRun::id,
{MacroConditionRun::Create, MacroConditionRunEdit::Create,
"AdvSceneSwitcher.condition.run"});
MacroConditionRun::~MacroConditionRun()
{
if (_thread.joinable()) {
_thread.join();
}
}
bool MacroConditionRun::CheckCondition()
{
if (!_threadDone) {
return false;
}
bool ret = false;
switch (_procStatus) {
case MacroConditionRun::Status::FAILED_TO_START:
SetVariableValue("Failed to start process");
ret = false;
break;
case MacroConditionRun::Status::TIMEOUT:
SetVariableValue("Timeout while running process");
ret = false;
break;
case MacroConditionRun::Status::OK:
ret = _checkExitCode ? _exitCode == _procExitCode : true;
SetVariableValue(std::to_string(_procExitCode));
break;
default:
break;
}
if (_thread.joinable()) {
_thread.join();
}
_threadDone = false;
_thread = std::thread(&MacroConditionRun::RunProcess, this);
return ret;
}
void MacroConditionRun::RunProcess()
{
QProcess process;
process.setWorkingDirectory(
QString::fromStdString(_procConfig.WorkingDir()));
process.start(QString::fromStdString(_procConfig.Path()),
_procConfig.Args());
int timeout = _timeout.seconds * 1000;
vblog(LOG_INFO, "run \"%s\" with a timeout of %d ms",
_procConfig.Path().c_str(), timeout);
bool procFinishedInTime = process.waitForFinished(timeout);
if (!procFinishedInTime) {
if (process.error() == QProcess::FailedToStart) {
vblog(LOG_INFO, "failed to start \"%s\"!",
_procConfig.Path().c_str());
_procStatus = Status::FAILED_TO_START;
} else {
vblog(LOG_INFO,
"timeout while running \"%s\"\nAttempting to kill process!",
_procConfig.Path().c_str());
process.kill();
process.waitForFinished();
_procStatus = Status::TIMEOUT;
}
}
bool validExitCode = process.exitStatus() == QProcess::NormalExit;
if ((_checkExitCode && !validExitCode) || !procFinishedInTime) {
_threadDone = true;
return;
}
_procExitCode = process.exitCode();
_procStatus = Status::OK;
_threadDone = true;
}
bool MacroConditionRun::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_procConfig.Save(obj);
obs_data_set_bool(obj, "checkExitCode", _checkExitCode);
obs_data_set_int(obj, "exitCode", _exitCode);
_timeout.Save(obj, "timeout");
return true;
}
bool MacroConditionRun::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_procConfig.Load(obj);
_checkExitCode = obs_data_get_bool(obj, "checkExitCode");
_exitCode = obs_data_get_int(obj, "exitCode");
_timeout.Load(obj, "timeout");
return true;
}
std::string MacroConditionRun::GetShortDesc() const
{
return _procConfig.Path();
}
MacroConditionRunEdit::MacroConditionRunEdit(
QWidget *parent, std::shared_ptr<MacroConditionRun> entryData)
: QWidget(parent),
_procConfig(new ProcessConfigEdit(this)),
_checkExitCode(new QCheckBox()),
_exitCode(new QSpinBox()),
_timeout(new DurationSelection(this, false, 0.1))
{
_exitCode->setMinimum(-99999);
_exitCode->setMaximum(999999);
QWidget::connect(_procConfig,
SIGNAL(ConfigChanged(const ProcessConfig &)), this,
SLOT(ProcessConfigChanged(const ProcessConfig &)));
QWidget::connect(_timeout, SIGNAL(DurationChanged(double)), this,
SLOT(TimeoutChanged(double)));
QWidget::connect(_checkExitCode, SIGNAL(stateChanged(int)), this,
SLOT(CheckExitCodeChanged(int)));
QWidget::connect(_exitCode, SIGNAL(valueChanged(int)), this,
SLOT(ExitCodeChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{checkExitCode}}", _checkExitCode},
{"{{exitCode}}", _exitCode},
{"{{timeout}}", _timeout},
};
auto exitLayout = new QHBoxLayout();
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.run.entry.exit"),
exitLayout, widgetPlaceholders);
auto timeoutLayout = new QHBoxLayout();
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.run.entry"),
timeoutLayout, widgetPlaceholders);
auto *layout = new QVBoxLayout;
layout->addLayout(timeoutLayout);
layout->addWidget(_procConfig);
layout->addLayout(exitLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionRunEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_procConfig->SetProcessConfig(_entryData->_procConfig);
_timeout->SetDuration(_entryData->_timeout);
_checkExitCode->setChecked(_entryData->_checkExitCode);
_exitCode->setValue(_entryData->_exitCode);
}
void MacroConditionRunEdit::TimeoutChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_timeout.seconds = seconds;
}
void MacroConditionRunEdit::CheckExitCodeChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_checkExitCode = state;
}
void MacroConditionRunEdit::ExitCodeChanged(int exitCode)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_exitCode = exitCode;
}
void MacroConditionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_procConfig = conf;
adjustSize();
updateGeometry();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

View File

@ -0,0 +1,79 @@
#pragma once
#include "macro.hpp"
#include "process-config.hpp"
#include "duration-control.hpp"
#include <QCheckBox>
#include <QSpinBox>
class MacroConditionRun : public MacroCondition {
public:
MacroConditionRun(Macro *m) : MacroCondition(m, true) {}
~MacroConditionRun();
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionRun>(m);
}
ProcessConfig _procConfig;
bool _checkExitCode = true;
int _exitCode = 0;
Duration _timeout = Duration(0.1);
private:
enum class Status {
NONE,
FAILED_TO_START,
TIMEOUT,
OK,
};
void RunProcess();
std::thread _thread;
std::atomic_bool _threadDone{true};
Status _procStatus = Status::NONE;
int _procExitCode = 0;
static bool _registered;
static const std::string id;
};
class MacroConditionRunEdit : public QWidget {
Q_OBJECT
public:
MacroConditionRunEdit(QWidget *parent,
std::shared_ptr<MacroConditionRun> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionRunEdit(
parent,
std::dynamic_pointer_cast<MacroConditionRun>(cond));
}
private slots:
void ProcessConfigChanged(const ProcessConfig &);
void TimeoutChanged(double);
void CheckExitCodeChanged(int);
void ExitCodeChanged(int);
signals:
void HeaderInfoChanged(const QString &);
protected:
ProcessConfigEdit *_procConfig;
QCheckBox *_checkExitCode;
QSpinBox *_exitCode;
DurationSelection *_timeout;
std::shared_ptr<MacroConditionRun> _entryData;
private:
bool _loading = true;
};