mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-10-02 14:29:52 -05:00
Restructure library and plugins
The "core" macro conditions and actions have been extracted out to the "base" plugin. The library now mostly contains functionality which is required across all plugins and (e.g. definitions for macro segments). The goal is to reduce the complexity and cross-dependencies and group the source files in a better way. This should relsove the "library limit of 65535 objects exceeded" build issue occuring in some Windows build environments.
This commit is contained in:
196
plugins/base/CMakeLists.txt
Normal file
196
plugins/base/CMakeLists.txt
Normal file
@@ -0,0 +1,196 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(advanced-scene-switcher-base)
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE macro-action-audio.cpp
|
||||
macro-action-audio.hpp
|
||||
macro-action-clipboard.cpp
|
||||
macro-action-clipboard.hpp
|
||||
macro-action-file.cpp
|
||||
macro-action-file.hpp
|
||||
macro-action-filter.cpp
|
||||
macro-action-filter.hpp
|
||||
macro-action-hotkey.cpp
|
||||
macro-action-hotkey.hpp
|
||||
macro-action-http.cpp
|
||||
macro-action-http.hpp
|
||||
macro-action-media.cpp
|
||||
macro-action-media.hpp
|
||||
macro-action-osc.cpp
|
||||
macro-action-osc.hpp
|
||||
macro-action-plugin-state.cpp
|
||||
macro-action-plugin-state.hpp
|
||||
macro-action-profile.cpp
|
||||
macro-action-profile.hpp
|
||||
macro-action-projector.cpp
|
||||
macro-action-projector.hpp
|
||||
macro-action-random.cpp
|
||||
macro-action-random.hpp
|
||||
macro-action-recording.cpp
|
||||
macro-action-recording.hpp
|
||||
macro-action-replay-buffer.cpp
|
||||
macro-action-replay-buffer.hpp
|
||||
macro-action-run.cpp
|
||||
macro-action-run.hpp
|
||||
macro-action-scene-collection.cpp
|
||||
macro-action-scene-collection.hpp
|
||||
macro-action-scene-lock.cpp
|
||||
macro-action-scene-lock.hpp
|
||||
macro-action-scene-order.cpp
|
||||
macro-action-scene-order.hpp
|
||||
macro-action-scene-switch.cpp
|
||||
macro-action-scene-switch.hpp
|
||||
macro-action-scene-transform.cpp
|
||||
macro-action-scene-transform.hpp
|
||||
macro-action-scene-visibility.cpp
|
||||
macro-action-scene-visibility.hpp
|
||||
macro-action-screenshot.cpp
|
||||
macro-action-screenshot.hpp
|
||||
macro-action-sequence.cpp
|
||||
macro-action-sequence.hpp
|
||||
macro-action-source.cpp
|
||||
macro-action-source.hpp
|
||||
macro-action-streaming.cpp
|
||||
macro-action-streaming.hpp
|
||||
macro-action-studio-mode.cpp
|
||||
macro-action-studio-mode.hpp
|
||||
macro-action-systray.cpp
|
||||
macro-action-systray.hpp
|
||||
macro-action-timer.cpp
|
||||
macro-action-timer.hpp
|
||||
macro-action-transition.cpp
|
||||
macro-action-transition.hpp
|
||||
macro-action-virtual-cam.cpp
|
||||
macro-action-virtual-cam.hpp
|
||||
macro-action-wait.cpp
|
||||
macro-action-wait.hpp
|
||||
macro-action-websocket.cpp
|
||||
macro-action-websocket.hpp
|
||||
macro-condition-audio.cpp
|
||||
macro-condition-audio.hpp
|
||||
macro-condition-cursor.cpp
|
||||
macro-condition-cursor.hpp
|
||||
macro-condition-date.cpp
|
||||
macro-condition-date.hpp
|
||||
macro-condition-display.cpp
|
||||
macro-condition-display.hpp
|
||||
macro-condition-file.cpp
|
||||
macro-condition-file.hpp
|
||||
macro-condition-filter.cpp
|
||||
macro-condition-filter.hpp
|
||||
macro-condition-hotkey.cpp
|
||||
macro-condition-hotkey.hpp
|
||||
macro-condition-idle.cpp
|
||||
macro-condition-idle.hpp
|
||||
macro-condition-media.cpp
|
||||
macro-condition-media.hpp
|
||||
macro-condition-obs-stats.cpp
|
||||
macro-condition-obs-stats.hpp
|
||||
macro-condition-plugin-state.cpp
|
||||
macro-condition-plugin-state.hpp
|
||||
macro-condition-process.cpp
|
||||
macro-condition-process.hpp
|
||||
macro-condition-profile.cpp
|
||||
macro-condition-profile.hpp
|
||||
macro-condition-recording.cpp
|
||||
macro-condition-recording.hpp
|
||||
macro-condition-replay-buffer.cpp
|
||||
macro-condition-replay-buffer.hpp
|
||||
macro-condition-run.cpp
|
||||
macro-condition-run.hpp
|
||||
macro-condition-scene-order.cpp
|
||||
macro-condition-scene-order.hpp
|
||||
macro-condition-scene-transform.cpp
|
||||
macro-condition-scene-transform.hpp
|
||||
macro-condition-scene-visibility.cpp
|
||||
macro-condition-scene-visibility.hpp
|
||||
macro-condition-scene.cpp
|
||||
macro-condition-scene.hpp
|
||||
macro-condition-slideshow.cpp
|
||||
macro-condition-slideshow.hpp
|
||||
macro-condition-source.cpp
|
||||
macro-condition-source.hpp
|
||||
macro-condition-streaming.cpp
|
||||
macro-condition-streaming.hpp
|
||||
macro-condition-studio-mode.cpp
|
||||
macro-condition-studio-mode.hpp
|
||||
macro-condition-timer.cpp
|
||||
macro-condition-timer.hpp
|
||||
macro-condition-transition.cpp
|
||||
macro-condition-transition.hpp
|
||||
macro-condition-virtual-cam.cpp
|
||||
macro-condition-virtual-cam.hpp
|
||||
macro-condition-websocket.cpp
|
||||
macro-condition-websocket.hpp
|
||||
macro-condition-window.cpp
|
||||
macro-condition-window.hpp)
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE utils/audio-helpers.cpp
|
||||
utils/audio-helpers.hpp
|
||||
utils/connection-manager.cpp
|
||||
utils/connection-manager.hpp
|
||||
utils/cursor-helpers.cpp
|
||||
utils/cursor-helpers.hpp
|
||||
utils/filter-selection.cpp
|
||||
utils/filter-selection.hpp
|
||||
utils/hotkey-helpers.cpp
|
||||
utils/hotkey-helpers.hpp
|
||||
utils/json-helpers.cpp
|
||||
utils/json-helpers.hpp
|
||||
utils/monitor-helpers.cpp
|
||||
utils/monitor-helpers.hpp
|
||||
utils/osc-helpers.cpp
|
||||
utils/osc-helpers.hpp
|
||||
utils/process-config.cpp
|
||||
utils/process-config.hpp
|
||||
utils/profile-helpers.cpp
|
||||
utils/profile-helpers.hpp
|
||||
utils/scene-item-selection.cpp
|
||||
utils/scene-item-selection.hpp
|
||||
utils/scene-item-transform-helpers.cpp
|
||||
utils/scene-item-transform-helpers.hpp
|
||||
utils/source-settings-helpers.cpp
|
||||
utils/source-settings-helpers.hpp
|
||||
utils/source-setting.cpp
|
||||
utils/source-setting.hpp
|
||||
utils/string-list.cpp
|
||||
utils/string-list.hpp
|
||||
utils/striped-frame.cpp
|
||||
utils/striped-frame.hpp
|
||||
utils/text-helpers.cpp
|
||||
utils/text-helpers.hpp
|
||||
utils/transition-selection.cpp
|
||||
utils/transition-selection.hpp
|
||||
utils/websocket-helpers.cpp
|
||||
utils/websocket-helpers.hpp)
|
||||
|
||||
if(OS_WINDOWS)
|
||||
target_sources(${PROJECT_NAME} PRIVATE utils/windows/windows.cpp)
|
||||
elseif(OS_MACOS)
|
||||
target_sources(${PROJECT_NAME} PRIVATE utils/osx/osx.mm)
|
||||
else()
|
||||
target_sources(${PROJECT_NAME} PRIVATE utils/linux/linux.cpp)
|
||||
find_package(X11 REQUIRED COMPONENTS Xtst)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${X11_INCLUDE_DIR}"
|
||||
"${X11_Xtst_INCLUDE_PATH}")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${X11_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/utils")
|
||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||
add_definitions(-DASIO_STANDALONE)
|
||||
target_include_directories(
|
||||
${LIB_NAME}
|
||||
PUBLIC "${ADVSS_SOURCE_DIR}/deps/asio/asio/include"
|
||||
"${ADVSS_SOURCE_DIR}/deps/websocketpp"
|
||||
"${ADVSS_SOURCE_DIR}/deps/obs-websocket/lib")
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
install_advss_plugin(${PROJECT_NAME})
|
||||
726
plugins/base/macro-action-audio.cpp
Normal file
726
plugins/base/macro-action-audio.cpp
Normal file
@@ -0,0 +1,726 @@
|
||||
#include "macro-action-audio.hpp"
|
||||
#include "audio-helpers.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace advss {
|
||||
|
||||
constexpr int64_t nsPerMs = 1000000;
|
||||
const std::string MacroActionAudio::id = "audio";
|
||||
|
||||
bool MacroActionAudio::_registered = MacroActionFactory::Register(
|
||||
MacroActionAudio::id,
|
||||
{MacroActionAudio::Create, MacroActionAudioEdit::Create,
|
||||
"AdvSceneSwitcher.action.audio"});
|
||||
|
||||
static const std::map<MacroActionAudio::Action, std::string> actionTypes = {
|
||||
{MacroActionAudio::Action::MUTE,
|
||||
"AdvSceneSwitcher.action.audio.type.mute"},
|
||||
{MacroActionAudio::Action::UNMUTE,
|
||||
"AdvSceneSwitcher.action.audio.type.unmute"},
|
||||
{MacroActionAudio::Action::SOURCE_VOLUME,
|
||||
"AdvSceneSwitcher.action.audio.type.sourceVolume"},
|
||||
{MacroActionAudio::Action::MASTER_VOLUME,
|
||||
"AdvSceneSwitcher.action.audio.type.masterVolume"},
|
||||
{MacroActionAudio::Action::SYNC_OFFSET,
|
||||
"AdvSceneSwitcher.action.audio.type.syncOffset"},
|
||||
{MacroActionAudio::Action::MONITOR,
|
||||
"AdvSceneSwitcher.action.audio.type.monitor"},
|
||||
{MacroActionAudio::Action::BALANCE,
|
||||
"AdvSceneSwitcher.action.audio.type.balance"},
|
||||
};
|
||||
|
||||
static const std::map<MacroActionAudio::FadeType, std::string> fadeTypes = {
|
||||
{MacroActionAudio::FadeType::DURATION,
|
||||
"AdvSceneSwitcher.action.audio.fade.type.duration"},
|
||||
{MacroActionAudio::FadeType::RATE,
|
||||
"AdvSceneSwitcher.action.audio.fade.type.rate"},
|
||||
};
|
||||
|
||||
namespace {
|
||||
struct FadeInfo {
|
||||
std::atomic_bool active = {false};
|
||||
std::atomic_int id = {0};
|
||||
};
|
||||
}
|
||||
|
||||
static FadeInfo masterAudioFade;
|
||||
static std::unordered_map<std::string, FadeInfo> audioFades;
|
||||
|
||||
constexpr auto fadeInterval = std::chrono::milliseconds(100);
|
||||
constexpr float minFade = 0.000001f;
|
||||
|
||||
static float decibelToPercent(float db)
|
||||
{
|
||||
return pow(10, (db / 20));
|
||||
}
|
||||
|
||||
// For backwards compatibility
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(29, 0, 0)
|
||||
static float get_master_volume()
|
||||
{
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
static void set_master_volume(float)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#else
|
||||
auto get_master_volume = obs_get_master_volume;
|
||||
auto set_master_volume = obs_set_master_volume;
|
||||
#endif
|
||||
|
||||
void MacroActionAudio::SetFadeActive(bool value) const
|
||||
{
|
||||
if (_action == Action::SOURCE_VOLUME) {
|
||||
audioFades[_audioSource.ToString()].active = value;
|
||||
} else {
|
||||
masterAudioFade.active = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionAudio::FadeActive() const
|
||||
{
|
||||
bool active = true;
|
||||
if (_action == Action::SOURCE_VOLUME) {
|
||||
auto it = audioFades.find(_audioSource.ToString());
|
||||
if (it == audioFades.end()) {
|
||||
return false;
|
||||
}
|
||||
active = it->second.active;
|
||||
} else {
|
||||
active = masterAudioFade.active;
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
std::atomic_int *MacroActionAudio::GetFadeIdPtr() const
|
||||
{
|
||||
|
||||
if (_action == Action::SOURCE_VOLUME) {
|
||||
auto it = audioFades.find(_audioSource.ToString());
|
||||
if (it == audioFades.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second.id;
|
||||
}
|
||||
return &masterAudioFade.id;
|
||||
}
|
||||
|
||||
float MacroActionAudio::GetVolume() const
|
||||
{
|
||||
return _useDb ? decibelToPercent(_volumeDB) : (float)_volume / 100.0f;
|
||||
}
|
||||
|
||||
void MacroActionAudio::SetVolume(float vol) const
|
||||
{
|
||||
if (_action == Action::SOURCE_VOLUME) {
|
||||
auto s = obs_weak_source_get_source(_audioSource.GetSource());
|
||||
obs_source_set_volume(s, vol);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
set_master_volume(vol);
|
||||
}
|
||||
}
|
||||
|
||||
float MacroActionAudio::GetCurrentVolume() const
|
||||
{
|
||||
float curVol;
|
||||
if (_action == Action::SOURCE_VOLUME) {
|
||||
auto s = obs_weak_source_get_source(_audioSource.GetSource());
|
||||
if (!s) {
|
||||
return 0.;
|
||||
}
|
||||
curVol = obs_source_get_volume(s);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
curVol = get_master_volume();
|
||||
}
|
||||
return curVol;
|
||||
}
|
||||
|
||||
void MacroActionAudio::FadeVolume() const
|
||||
{
|
||||
float vol = GetVolume();
|
||||
float curVol = GetCurrentVolume();
|
||||
bool volIncrease = curVol <= vol;
|
||||
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
|
||||
int nrSteps = 0;
|
||||
float volStep = 0.;
|
||||
if (_fadeType == FadeType::DURATION) {
|
||||
nrSteps = _duration.Milliseconds() / fadeInterval.count();
|
||||
volStep = volDiff / nrSteps;
|
||||
} else {
|
||||
volStep = _rate / 1000.0f;
|
||||
nrSteps = volDiff / volStep;
|
||||
}
|
||||
|
||||
if (volStep < minFade || nrSteps <= 1) {
|
||||
SetVolume(vol);
|
||||
SetFadeActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = GetMacro();
|
||||
int step = 0;
|
||||
auto fadeId = GetFadeIdPtr();
|
||||
int expectedFadeId = ++(*fadeId);
|
||||
for (; step < nrSteps && !MacroIsStopped(macro) &&
|
||||
expectedFadeId == *fadeId;
|
||||
++step) {
|
||||
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
|
||||
SetVolume(curVol);
|
||||
std::this_thread::sleep_for(fadeInterval);
|
||||
}
|
||||
|
||||
// As a final step set desired volume once again in case floating-point
|
||||
// precision errors compounded to a noticeable error
|
||||
if (step == nrSteps) {
|
||||
SetVolume(vol);
|
||||
}
|
||||
|
||||
SetFadeActive(false);
|
||||
}
|
||||
|
||||
void MacroActionAudio::StartFade() const
|
||||
{
|
||||
if (_action == Action::SOURCE_VOLUME && !_audioSource.GetSource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FadeActive() && !_abortActiveFade) {
|
||||
blog(LOG_WARNING,
|
||||
"Audio fade for volume of %s already active! New fade request will be ignored!",
|
||||
(_action == Action::SOURCE_VOLUME)
|
||||
? _audioSource.ToString(true).c_str()
|
||||
: "master volume");
|
||||
return;
|
||||
}
|
||||
SetFadeActive(true);
|
||||
|
||||
if (_wait) {
|
||||
FadeVolume();
|
||||
} else {
|
||||
AddMacroHelperThread(GetMacro(),
|
||||
std::thread(&MacroActionAudio::FadeVolume,
|
||||
this));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionAudio::PerformAction()
|
||||
{
|
||||
auto s = obs_weak_source_get_source(_audioSource.GetSource());
|
||||
switch (_action) {
|
||||
case Action::MUTE:
|
||||
obs_source_set_muted(s, true);
|
||||
break;
|
||||
case Action::UNMUTE:
|
||||
obs_source_set_muted(s, false);
|
||||
break;
|
||||
case Action::SOURCE_VOLUME:
|
||||
case Action::MASTER_VOLUME:
|
||||
if (_fade) {
|
||||
StartFade();
|
||||
} else {
|
||||
SetVolume(GetVolume());
|
||||
}
|
||||
break;
|
||||
case Action::SYNC_OFFSET:
|
||||
obs_source_set_sync_offset(s, _syncOffset * nsPerMs);
|
||||
break;
|
||||
case Action::MONITOR:
|
||||
obs_source_set_monitoring_type(s, _monitorType);
|
||||
break;
|
||||
case Action::BALANCE:
|
||||
obs_source_set_balance_value(s, _balance);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_source_release(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionAudio::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
auto &[_, action] = *it;
|
||||
vblog(LOG_INFO,
|
||||
"performed action \"%s\" for source \"%s\" with volume %f with fade %d %f",
|
||||
action.c_str(), _audioSource.ToString(true).c_str(),
|
||||
GetVolume(), _fade, _duration.Seconds());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown audio action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionAudio::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_duration.Save(obj);
|
||||
_audioSource.Save(obj, "audioSource");
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "monitor", _monitorType);
|
||||
_syncOffset.Save(obj, "syncOffset");
|
||||
_balance.Save(obj, "balance");
|
||||
_volume.Save(obj, "volume");
|
||||
_rate.Save(obj, "rate");
|
||||
obs_data_set_bool(obj, "fade", _fade);
|
||||
obs_data_set_int(obj, "fadeType", static_cast<int>(_fadeType));
|
||||
obs_data_set_bool(obj, "wait", _wait);
|
||||
obs_data_set_bool(obj, "abortActiveFade", _abortActiveFade);
|
||||
obs_data_set_bool(obj, "useDb", _useDb);
|
||||
_volumeDB.Save(obj, "volumeDB");
|
||||
obs_data_set_int(obj, "version", 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionAudio::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_duration.Load(obj);
|
||||
_audioSource.Load(obj, "audioSource");
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_monitorType = static_cast<obs_monitoring_type>(
|
||||
obs_data_get_int(obj, "monitor"));
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_syncOffset = obs_data_get_int(obj, "syncOffset");
|
||||
_balance = obs_data_get_double(obj, "balance");
|
||||
_volume = obs_data_get_int(obj, "volume");
|
||||
_rate = obs_data_get_double(obj, "rate");
|
||||
} else {
|
||||
_syncOffset.Load(obj, "syncOffset");
|
||||
_balance.Load(obj, "balance");
|
||||
_volume.Load(obj, "volume");
|
||||
_rate.Load(obj, "rate");
|
||||
}
|
||||
_fade = obs_data_get_bool(obj, "fade");
|
||||
if (obs_data_has_user_value(obj, "wait")) {
|
||||
_wait = obs_data_get_bool(obj, "wait");
|
||||
} else {
|
||||
_wait = false;
|
||||
}
|
||||
if (obs_data_has_user_value(obj, "fadeType")) {
|
||||
_fadeType = static_cast<FadeType>(
|
||||
obs_data_get_int(obj, "fadeType"));
|
||||
} else {
|
||||
_fadeType = FadeType::DURATION;
|
||||
}
|
||||
if (obs_data_has_user_value(obj, "abortActiveFade")) {
|
||||
_abortActiveFade = obs_data_get_bool(obj, "abortActiveFade");
|
||||
} else {
|
||||
_abortActiveFade = false;
|
||||
}
|
||||
|
||||
if (obs_data_get_int(obj, "version") != 2) {
|
||||
_useDb = false;
|
||||
_volumeDB = 0.0;
|
||||
} else {
|
||||
_useDb = obs_data_get_bool(obj, "useDb");
|
||||
_volumeDB.Load(obj, "volumeDB");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionAudio::GetShortDesc() const
|
||||
{
|
||||
return _audioSource.ToString();
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[action, name] : actionTypes) {
|
||||
if (action == MacroActionAudio::Action::MONITOR &&
|
||||
!obs_audio_monitoring_available()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(action));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateFadeTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : fadeTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionAudioEdit::MacroActionAudioEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionAudio> entryData)
|
||||
: QWidget(parent),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_actions(new QComboBox),
|
||||
_fadeTypes(new QComboBox),
|
||||
_syncOffset(new VariableSpinBox),
|
||||
_monitorTypes(new QComboBox),
|
||||
_balance(new SliderSpinBox(
|
||||
0., 1., "",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.audio.balance.description"))),
|
||||
_volumePercent(new VariableSpinBox),
|
||||
_volumeDB(new VariableDoubleSpinBox),
|
||||
_percentDBToggle(new QPushButton),
|
||||
_fade(new QCheckBox),
|
||||
_duration(new DurationSelection(parent, false)),
|
||||
_rate(new VariableDoubleSpinBox),
|
||||
_wait(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.wait"))),
|
||||
_abortActiveFade(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.abort"))),
|
||||
_fadeTypeLayout(new QHBoxLayout),
|
||||
_fadeOptionsLayout(new QVBoxLayout)
|
||||
{
|
||||
_syncOffset->setMinimum(-950);
|
||||
_syncOffset->setMaximum(20000);
|
||||
_syncOffset->setSuffix("ms");
|
||||
|
||||
_volumePercent->setMinimum(0);
|
||||
_volumePercent->setMaximum(2000);
|
||||
_volumePercent->setSuffix("%");
|
||||
|
||||
_volumeDB->setMinimum(-60);
|
||||
_volumeDB->setMaximum(0);
|
||||
_volumeDB->setSuffix("db");
|
||||
_volumeDB->specialValueText("-inf");
|
||||
|
||||
_rate->setMinimum(0.01);
|
||||
_rate->setMaximum(999.);
|
||||
_rate->setSuffix("%");
|
||||
|
||||
populateActionSelection(_actions);
|
||||
auto sources = GetAudioSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
populateFadeTypeSelection(_fadeTypes);
|
||||
PopulateMonitorTypeSelection(_monitorTypes);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(
|
||||
_syncOffset,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(SyncOffsetChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_balance,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(BalanceChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_monitorTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MonitorTypeChanged(int)));
|
||||
QWidget::connect(
|
||||
_volumePercent,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(VolumeChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_volumeDB,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(VolumeDBChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_percentDBToggle, SIGNAL(clicked()), this,
|
||||
SLOT(PercentDBClicked()));
|
||||
QWidget::connect(_fade, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(FadeChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(
|
||||
_rate,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(RateChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_wait, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(WaitChanged(int)));
|
||||
QWidget::connect(_abortActiveFade, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(AbortActiveFadeChanged(int)));
|
||||
QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(FadeTypeChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{audioSources}}", _sources},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{syncOffset}}", _syncOffset},
|
||||
{"{{monitorTypes}}", _monitorTypes},
|
||||
{"{{balance}}", _balance},
|
||||
{"{{volume}}", _volumePercent},
|
||||
{"{{volumeDB}}", _volumeDB},
|
||||
{"{{percentDBToggle}}", _percentDBToggle},
|
||||
{"{{fade}}", _fade},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{rate}}", _rate},
|
||||
{"{{wait}}", _wait},
|
||||
{"{{abortActiveFade}}", _abortActiveFade},
|
||||
{"{{fadeTypes}}", _fadeTypes},
|
||||
};
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
_fadeTypeLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.duration"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
|
||||
_fadeOptionsLayout->addLayout(_fadeTypeLayout);
|
||||
_fadeOptionsLayout->addWidget(_abortActiveFade);
|
||||
_fadeOptionsLayout->addWidget(_wait);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_balance);
|
||||
mainLayout->addLayout(_fadeOptionsLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
static bool hasVolumeControl(MacroActionAudio::Action action)
|
||||
{
|
||||
return action == MacroActionAudio::Action::SOURCE_VOLUME ||
|
||||
action == MacroActionAudio::Action::MASTER_VOLUME;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::SetWidgetVisibility()
|
||||
{
|
||||
_volumePercent->setVisible(hasVolumeControl(_entryData->_action) &&
|
||||
!_entryData->_useDb);
|
||||
_volumeDB->setVisible(hasVolumeControl(_entryData->_action) &&
|
||||
_entryData->_useDb);
|
||||
_percentDBToggle->setText(_entryData->_useDb ? "db" : "%");
|
||||
_percentDBToggle->setVisible(hasVolumeControl(_entryData->_action));
|
||||
_sources->setVisible(_entryData->_action !=
|
||||
MacroActionAudio::Action::MASTER_VOLUME);
|
||||
_syncOffset->setVisible(_entryData->_action ==
|
||||
MacroActionAudio::Action::SYNC_OFFSET);
|
||||
_monitorTypes->setVisible(_entryData->_action ==
|
||||
MacroActionAudio::Action::MONITOR);
|
||||
_balance->setVisible(_entryData->_action ==
|
||||
MacroActionAudio::Action::BALANCE);
|
||||
|
||||
_fadeTypeLayout->removeWidget(_fade);
|
||||
_fadeTypeLayout->removeWidget(_fadeTypes);
|
||||
_fadeTypeLayout->removeWidget(_duration);
|
||||
_fadeTypeLayout->removeWidget(_rate);
|
||||
ClearLayout(_fadeTypeLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{fade}}", _fade},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{rate}}", _rate},
|
||||
{"{{fadeTypes}}", _fadeTypes},
|
||||
};
|
||||
if (_entryData->_fadeType == MacroActionAudio::FadeType::DURATION) {
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.audio.fade.duration"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
} else {
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.audio.fade.rate"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
}
|
||||
|
||||
_duration->setVisible(_entryData->_fadeType ==
|
||||
MacroActionAudio::FadeType::DURATION);
|
||||
_rate->setVisible(_entryData->_fadeType ==
|
||||
MacroActionAudio::FadeType::RATE);
|
||||
|
||||
SetLayoutVisible(_fadeTypeLayout,
|
||||
hasVolumeControl(_entryData->_action));
|
||||
SetLayoutVisible(_fadeOptionsLayout,
|
||||
hasVolumeControl(_entryData->_action));
|
||||
_abortActiveFade->setVisible(hasVolumeControl(_entryData->_action) &&
|
||||
_entryData->_fade);
|
||||
_wait->setVisible(hasVolumeControl(_entryData->_action) &&
|
||||
_entryData->_fade);
|
||||
|
||||
// TODO: Remove this in a future version:
|
||||
if (_entryData->_action != MacroActionAudio::Action::MASTER_VOLUME &&
|
||||
obs_get_version() >= MAKE_SEMANTIC_VERSION(29, 0, 0)) {
|
||||
_actions->removeItem(_actions->findText(obs_module_text(
|
||||
actionTypes.at(MacroActionAudio::Action::MASTER_VOLUME)
|
||||
.c_str())));
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sources->SetSource(_entryData->_audioSource);
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_syncOffset->SetValue(_entryData->_syncOffset);
|
||||
_monitorTypes->setCurrentIndex(_entryData->_monitorType);
|
||||
_balance->SetDoubleValue(_entryData->_balance);
|
||||
_volumePercent->SetValue(_entryData->_volume);
|
||||
_volumeDB->SetValue(_entryData->_volumeDB);
|
||||
_fade->setChecked(_entryData->_fade);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_rate->SetValue(_entryData->_rate);
|
||||
_wait->setChecked(_entryData->_wait);
|
||||
_abortActiveFade->setChecked(_entryData->_abortActiveFade);
|
||||
_fadeTypes->setCurrentIndex(static_cast<int>(_entryData->_fadeType));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_audioSource = source;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::ActionChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionAudio::Action>(
|
||||
_actions->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::SyncOffsetChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_syncOffset = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::MonitorTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_monitorType = static_cast<obs_monitoring_type>(value);
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::BalanceChanged(const NumberVariable<double> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_balance = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::VolumeDBChanged(const NumberVariable<double> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_volumeDB = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::PercentDBClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_useDb = !_entryData->_useDb;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::VolumeChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_volume = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::FadeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_fade = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::RateChanged(const NumberVariable<double> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rate = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::WaitChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_wait = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::AbortActiveFadeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_abortActiveFade = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::FadeTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_fadeType = static_cast<MacroActionAudio::FadeType>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
132
plugins/base/macro-action-audio.hpp
Normal file
132
plugins/base/macro-action-audio.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "slider-spinbox.hpp"
|
||||
#include "source-selection.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionAudio : public MacroAction {
|
||||
public:
|
||||
MacroActionAudio(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionAudio>(m);
|
||||
}
|
||||
|
||||
SourceSelection _audioSource;
|
||||
|
||||
enum class Action {
|
||||
MUTE,
|
||||
UNMUTE,
|
||||
SOURCE_VOLUME,
|
||||
MASTER_VOLUME,
|
||||
SYNC_OFFSET,
|
||||
MONITOR,
|
||||
BALANCE,
|
||||
};
|
||||
|
||||
enum class FadeType {
|
||||
DURATION,
|
||||
RATE,
|
||||
};
|
||||
|
||||
Action _action = Action::MUTE;
|
||||
FadeType _fadeType = FadeType::DURATION;
|
||||
NumberVariable<int> _syncOffset = 0;
|
||||
obs_monitoring_type _monitorType = OBS_MONITORING_TYPE_NONE;
|
||||
NumberVariable<double> _balance = 0.5;
|
||||
bool _useDb = false;
|
||||
NumberVariable<int> _volume = 0;
|
||||
NumberVariable<double> _volumeDB = 0.0;
|
||||
bool _fade = false;
|
||||
Duration _duration;
|
||||
NumberVariable<double> _rate = 100.;
|
||||
bool _wait = false;
|
||||
bool _abortActiveFade = false;
|
||||
|
||||
private:
|
||||
void StartFade() const;
|
||||
void FadeVolume() const;
|
||||
void SetVolume(float vol) const;
|
||||
float GetCurrentVolume() const;
|
||||
void SetFadeActive(bool value) const;
|
||||
bool FadeActive() const;
|
||||
std::atomic_int *GetFadeIdPtr() const;
|
||||
float GetVolume() const;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionAudioEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionAudioEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionAudio> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionAudioEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionAudio>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void ActionChanged(int value);
|
||||
void SyncOffsetChanged(const NumberVariable<int> &value);
|
||||
void MonitorTypeChanged(int value);
|
||||
void BalanceChanged(const NumberVariable<double> &value);
|
||||
void VolumeChanged(const NumberVariable<int> &value);
|
||||
void VolumeDBChanged(const NumberVariable<double> &value);
|
||||
void PercentDBClicked();
|
||||
void FadeChanged(int value);
|
||||
void DurationChanged(const Duration &seconds);
|
||||
void RateChanged(const NumberVariable<double> &value);
|
||||
void WaitChanged(int value);
|
||||
void AbortActiveFadeChanged(int value);
|
||||
void FadeTypeChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SourceSelectionWidget *_sources;
|
||||
QComboBox *_actions;
|
||||
QComboBox *_fadeTypes;
|
||||
VariableSpinBox *_syncOffset;
|
||||
QComboBox *_monitorTypes;
|
||||
SliderSpinBox *_balance;
|
||||
VariableSpinBox *_volumePercent;
|
||||
VariableDoubleSpinBox *_volumeDB;
|
||||
QPushButton *_percentDBToggle;
|
||||
QCheckBox *_fade;
|
||||
DurationSelection *_duration;
|
||||
VariableDoubleSpinBox *_rate;
|
||||
QCheckBox *_wait;
|
||||
QCheckBox *_abortActiveFade;
|
||||
QHBoxLayout *_fadeTypeLayout;
|
||||
QVBoxLayout *_fadeOptionsLayout;
|
||||
std::shared_ptr<MacroActionAudio> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
256
plugins/base/macro-action-clipboard.cpp
Normal file
256
plugins/base/macro-action-clipboard.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "macro-action-clipboard.hpp"
|
||||
#include "curl-helper.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QImage>
|
||||
#include <QMimeData>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionClipboard::id = "clipboard";
|
||||
|
||||
bool MacroActionClipboard::_registered = MacroActionFactory::Register(
|
||||
MacroActionClipboard::id,
|
||||
{MacroActionClipboard::Create, MacroActionClipboardEdit::Create,
|
||||
"AdvSceneSwitcher.action.clipboard"});
|
||||
|
||||
const static std::map<MacroActionClipboard::Action, std::string> actionTypes = {
|
||||
{MacroActionClipboard::Action::COPY_TEXT,
|
||||
"AdvSceneSwitcher.action.clipboard.type.copy.text"},
|
||||
{MacroActionClipboard::Action::COPY_IMAGE,
|
||||
"AdvSceneSwitcher.action.clipboard.type.copy.image"},
|
||||
};
|
||||
|
||||
static size_t writeCallback(void *ptr, size_t size, size_t nmemb,
|
||||
QByteArray *buffer)
|
||||
{
|
||||
buffer->append((char *)ptr, nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static std::optional<QImage> getImageFromUrl(const char *url)
|
||||
{
|
||||
QByteArray response;
|
||||
|
||||
CurlHelper::SetOpt(CURLOPT_URL, url);
|
||||
CurlHelper::SetOpt(CURLOPT_HTTPGET, 1L);
|
||||
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, 30000);
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, writeCallback);
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &response);
|
||||
auto code = CurlHelper::Perform();
|
||||
|
||||
if (code != CURLE_OK) {
|
||||
blog(LOG_WARNING,
|
||||
"Retrieving image failed in %s with error: %s", __func__,
|
||||
CurlHelper::GetError(code));
|
||||
return {};
|
||||
}
|
||||
|
||||
return QImage::fromData(response);
|
||||
}
|
||||
|
||||
static void setMimeTypeParams(ClipboardQueueParams *params,
|
||||
QClipboard *clipboard)
|
||||
{
|
||||
auto mimeData = clipboard->mimeData();
|
||||
if (!mimeData) {
|
||||
return;
|
||||
}
|
||||
auto mimeTypeList = mimeData->formats();
|
||||
if (mimeTypeList.empty()) {
|
||||
return;
|
||||
}
|
||||
params->mimeTypePrimary = mimeTypeList.first().toStdString();
|
||||
params->mimeTypeAll = mimeTypeList.join(" ").toStdString();
|
||||
}
|
||||
|
||||
static void copyText(void *param)
|
||||
{
|
||||
auto params = static_cast<ClipboardTextQueueParams *>(param);
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
|
||||
clipboard->setText(params->text.c_str());
|
||||
|
||||
setMimeTypeParams(params, clipboard);
|
||||
}
|
||||
|
||||
static void copyImageFromUrl(void *param)
|
||||
{
|
||||
auto params = static_cast<ClipboardImageQueueParams *>(param);
|
||||
auto url = params->url.c_str();
|
||||
auto image = getImageFromUrl(url);
|
||||
|
||||
if (!image || (*image).isNull()) {
|
||||
blog(LOG_WARNING, "Failed to convert %s URL to image!", url);
|
||||
return;
|
||||
}
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setImage(*image);
|
||||
|
||||
setMimeTypeParams(params, clipboard);
|
||||
}
|
||||
|
||||
bool MacroActionClipboard::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case Action::COPY_TEXT: {
|
||||
ClipboardTextQueueParams params{"", "", _text};
|
||||
obs_queue_task(OBS_TASK_UI, copyText, ¶ms, true);
|
||||
SetTempVarValues(params);
|
||||
break;
|
||||
}
|
||||
case Action::COPY_IMAGE: {
|
||||
ClipboardImageQueueParams params{"", "", _url};
|
||||
obs_queue_task(OBS_TASK_UI, copyImageFromUrl, ¶ms, true);
|
||||
SetTempVarValues(params);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionClipboard::SetupTempVars()
|
||||
{
|
||||
MacroAction::SetupTempVars();
|
||||
|
||||
AddTempvar(
|
||||
"mimeType.primary",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description"));
|
||||
AddTempvar(
|
||||
"mimeType.all",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description"));
|
||||
}
|
||||
|
||||
void MacroActionClipboard::SetTempVarValues(const ClipboardQueueParams ¶ms)
|
||||
{
|
||||
SetTempVarValue("mimeType.primary", params.mimeTypePrimary);
|
||||
SetTempVarValue("mimeType.all", params.mimeTypeAll);
|
||||
}
|
||||
|
||||
bool MacroActionClipboard::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_text.Save(obj, "text");
|
||||
_url.Save(obj, "url");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionClipboard::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_text.Load(obj, "text");
|
||||
_url.Load(obj, "url");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[action, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(action));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionClipboardEdit::MacroActionClipboardEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionClipboard> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new FilterComboBox(this)),
|
||||
_text(new VariableLineEdit(this)),
|
||||
_url(new VariableLineEdit(this))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
_url->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip"));
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_text, SIGNAL(editingFinished()), this,
|
||||
SLOT(TextChanged()));
|
||||
QWidget::connect(_url, SIGNAL(editingFinished()), this,
|
||||
SLOT(UrlChanged()));
|
||||
|
||||
auto mainLayout = new QHBoxLayout();
|
||||
mainLayout->addWidget(_actions);
|
||||
mainLayout->addWidget(_text);
|
||||
mainLayout->addWidget(_url);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionClipboardEdit::ActionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionClipboard::Action>(
|
||||
_actions->itemData(index).toInt());
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionClipboardEdit::TextChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_text = _text->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionClipboardEdit::UrlChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_url = _url->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionClipboardEdit::SetWidgetVisibility()
|
||||
{
|
||||
_text->setVisible(_entryData->_action ==
|
||||
MacroActionClipboard::Action::COPY_TEXT);
|
||||
_url->setVisible(_entryData->_action ==
|
||||
MacroActionClipboard::Action::COPY_IMAGE);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionClipboardEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_text->setText(_entryData->_text);
|
||||
_url->setText(_entryData->_url);
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
89
plugins/base/macro-action-clipboard.hpp
Normal file
89
plugins/base/macro-action-clipboard.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
|
||||
#include <variable-line-edit.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct ClipboardQueueParams {
|
||||
std::string mimeTypePrimary;
|
||||
std::string mimeTypeAll;
|
||||
};
|
||||
|
||||
struct ClipboardTextQueueParams : ClipboardQueueParams {
|
||||
StringVariable text;
|
||||
};
|
||||
|
||||
struct ClipboardImageQueueParams : ClipboardQueueParams {
|
||||
StringVariable url;
|
||||
};
|
||||
|
||||
class MacroActionClipboard : public MacroAction {
|
||||
public:
|
||||
MacroActionClipboard(Macro *m) : MacroAction(m) {}
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionClipboard>(m);
|
||||
}
|
||||
std::string GetId() const { return id; };
|
||||
|
||||
bool PerformAction();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
enum class Action {
|
||||
COPY_TEXT,
|
||||
COPY_IMAGE,
|
||||
};
|
||||
|
||||
Action _action = Action::COPY_TEXT;
|
||||
|
||||
StringVariable _text = obs_module_text(
|
||||
"AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder");
|
||||
StringVariable _url = obs_module_text(
|
||||
"AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder");
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
void SetTempVarValues(const ClipboardQueueParams ¶ms);
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionClipboardEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionClipboardEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionClipboard> entryData = nullptr);
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionClipboardEdit(
|
||||
parent, std::dynamic_pointer_cast<MacroActionClipboard>(
|
||||
action));
|
||||
}
|
||||
void UpdateEntryData();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionClipboard> _entryData;
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int index);
|
||||
void TextChanged();
|
||||
void UrlChanged();
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
|
||||
FilterComboBox *_actions;
|
||||
VariableLineEdit *_text;
|
||||
VariableLineEdit *_url;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
172
plugins/base/macro-action-file.cpp
Normal file
172
plugins/base/macro-action-file.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "macro-action-file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QFileDialog>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionFile::id = "file";
|
||||
|
||||
bool MacroActionFile::_registered = MacroActionFactory::Register(
|
||||
MacroActionFile::id,
|
||||
{MacroActionFile::Create, MacroActionFileEdit::Create,
|
||||
"AdvSceneSwitcher.action.file"});
|
||||
|
||||
const static std::map<FileAction, std::string> actionTypes = {
|
||||
{FileAction::WRITE, "AdvSceneSwitcher.action.file.type.write"},
|
||||
{FileAction::APPEND, "AdvSceneSwitcher.action.file.type.append"},
|
||||
};
|
||||
|
||||
bool MacroActionFile::PerformAction()
|
||||
{
|
||||
QString path = QString::fromStdString(_file);
|
||||
QFile file(path);
|
||||
bool open = false;
|
||||
switch (_action) {
|
||||
case FileAction::WRITE:
|
||||
open = file.open(QIODevice::WriteOnly);
|
||||
break;
|
||||
case FileAction::APPEND:
|
||||
open = file.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (open) {
|
||||
QTextStream out(&file);
|
||||
out << QString::fromStdString(_text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionFile::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\" for file \"%s\"",
|
||||
it->second.c_str(), _file.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown file action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionFile::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_file.Save(obj, "file");
|
||||
_text.Save(obj, "text");
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionFile::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_file.Load(obj, "file");
|
||||
_text.Load(obj, "text");
|
||||
_action = static_cast<FileAction>(obs_data_get_int(obj, "action"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionFile::GetShortDesc() const
|
||||
{
|
||||
return _file.UnresolvedValue();
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionFileEdit::MacroActionFileEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionFile> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_filePath = new FileSelection(FileSelection::Type::WRITE);
|
||||
_text = new VariableTextEdit(this);
|
||||
_actions = new QComboBox();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
QWidget::connect(_text, SIGNAL(textChanged()), this,
|
||||
SLOT(TextChanged()));
|
||||
;
|
||||
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{filePath}}", _filePath},
|
||||
{"{{matchText}}", _text},
|
||||
{"{{actions}}", _actions},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.file.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_text);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_filePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_text->setPlainText(_entryData->_text);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::PathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_file = text.toUtf8().constData();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::TextChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_text = _text->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<FileAction>(value);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
71
plugins/base/macro-action-file.hpp
Normal file
71
plugins/base/macro-action-file.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class FileAction {
|
||||
WRITE,
|
||||
APPEND,
|
||||
};
|
||||
|
||||
class MacroActionFile : public MacroAction {
|
||||
public:
|
||||
MacroActionFile(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionFile>(m);
|
||||
}
|
||||
|
||||
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
FileAction _action = FileAction::WRITE;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionFileEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionFileEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionFile> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionFileEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionFile>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void PathChanged(const QString &text);
|
||||
void TextChanged();
|
||||
void ActionChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
FileSelection *_filePath;
|
||||
VariableTextEdit *_text;
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionFile> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
444
plugins/base/macro-action-filter.cpp
Normal file
444
plugins/base/macro-action-filter.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
#include "macro-action-filter.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "source-settings-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionFilter::id = "filter";
|
||||
|
||||
bool MacroActionFilter::_registered = MacroActionFactory::Register(
|
||||
MacroActionFilter::id,
|
||||
{MacroActionFilter::Create, MacroActionFilterEdit::Create,
|
||||
"AdvSceneSwitcher.action.filter"});
|
||||
|
||||
const static std::map<MacroActionFilter::Action, std::string> actionTypes = {
|
||||
{MacroActionFilter::Action::ENABLE,
|
||||
"AdvSceneSwitcher.action.filter.type.enable"},
|
||||
{MacroActionFilter::Action::DISABLE,
|
||||
"AdvSceneSwitcher.action.filter.type.disable"},
|
||||
{MacroActionFilter::Action::TOGGLE,
|
||||
"AdvSceneSwitcher.action.filter.type.toggle"},
|
||||
{MacroActionFilter::Action::SETTINGS,
|
||||
"AdvSceneSwitcher.action.filter.type.settings"},
|
||||
};
|
||||
|
||||
const static std::map<MacroActionFilter::SettingsInputMethod, std::string>
|
||||
inputMethods = {
|
||||
{MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL,
|
||||
"AdvSceneSwitcher.action.filter.inputMethod.individualManual"},
|
||||
{MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR,
|
||||
"AdvSceneSwitcher.action.filter.inputMethod.individualTempvar"},
|
||||
{MacroActionFilter::SettingsInputMethod::JSON_STRING,
|
||||
"AdvSceneSwitcher.action.filter.inputMethod.json"},
|
||||
};
|
||||
|
||||
static void performActionHelper(
|
||||
MacroActionFilter::Action action, const OBSWeakSource &filter,
|
||||
MacroActionFilter::SettingsInputMethod settingsInputMethod,
|
||||
const SourceSetting &setting, const std::string &manualSettingValue,
|
||||
const TempVariableRef &tempVar, Macro *macro,
|
||||
const StringVariable &settingsString)
|
||||
{
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(filter);
|
||||
switch (action) {
|
||||
case MacroActionFilter::Action::ENABLE:
|
||||
obs_source_set_enabled(source, true);
|
||||
break;
|
||||
case MacroActionFilter::Action::DISABLE:
|
||||
obs_source_set_enabled(source, false);
|
||||
break;
|
||||
case MacroActionFilter::Action::TOGGLE:
|
||||
obs_source_set_enabled(source, !obs_source_enabled(source));
|
||||
break;
|
||||
case MacroActionFilter::Action::SETTINGS:
|
||||
switch (settingsInputMethod) {
|
||||
case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL:
|
||||
SetSourceSetting(source, setting, manualSettingValue);
|
||||
break;
|
||||
case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR: {
|
||||
auto var = tempVar.GetTempVariable(macro);
|
||||
if (!var) {
|
||||
break;
|
||||
}
|
||||
auto value = var->Value();
|
||||
if (!value) {
|
||||
break;
|
||||
}
|
||||
SetSourceSetting(source, setting, *value);
|
||||
break;
|
||||
}
|
||||
case MacroActionFilter::SettingsInputMethod::JSON_STRING:
|
||||
SetSourceSettings(source, settingsString);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionFilter::PerformAction()
|
||||
{
|
||||
auto filters = _filter.GetFilters(_source);
|
||||
for (const auto &filter : filters) {
|
||||
performActionHelper(_action, filter, _settingsInputMethod,
|
||||
_setting, _manualSettingValue, _tempVar,
|
||||
GetMacro(), _settingsString);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionFilter::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"performed action \"%s\" for filter \"%s\" on source \"%s\"",
|
||||
it->second.c_str(), _filter.ToString().c_str(),
|
||||
_source.ToString(true).c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown filter action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionFilter::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_source.Save(obj);
|
||||
_filter.Save(obj, "filter");
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "inputMethod",
|
||||
static_cast<int>(_settingsInputMethod));
|
||||
_setting.Save(obj);
|
||||
_manualSettingValue.Save(obj, "manualSettingValue");
|
||||
_tempVar.Save(obj);
|
||||
_settingsString.Save(obj, "settings");
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionFilter::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_source.Load(obj);
|
||||
_filter.Load(obj, _source, "filter");
|
||||
// TODO: Remove this fallback in future version
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
const auto value = obs_data_get_int(obj, "action");
|
||||
if (value == 2) {
|
||||
_action = Action::SETTINGS;
|
||||
} else {
|
||||
_action = static_cast<Action>(value);
|
||||
}
|
||||
} else {
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
}
|
||||
// TODO: Remove fallback in future version
|
||||
if (obs_data_has_user_value(obj, "inputMethod")) {
|
||||
_settingsInputMethod = static_cast<SettingsInputMethod>(
|
||||
obs_data_get_int(obj, "inputMethod"));
|
||||
} else {
|
||||
_settingsInputMethod = SettingsInputMethod::JSON_STRING;
|
||||
}
|
||||
_setting.Load(obj);
|
||||
_settingsString.Load(obj, "settings");
|
||||
_manualSettingValue.Load(obj, "manualSettingValue");
|
||||
_tempVar.Load(obj, GetMacro());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionFilter::GetShortDesc() const
|
||||
{
|
||||
if (!_filter.ToString().empty() && !_source.ToString().empty()) {
|
||||
return _source.ToString() + " - " + _filter.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateSettingsInputMethods(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (const auto &[value, name] : inputMethods) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionFilterEdit::MacroActionFilterEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionFilter> entryData)
|
||||
: QWidget(parent),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_filters(new FilterSelectionWidget(this, _sources, true)),
|
||||
_actions(new QComboBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.filter.getSettings"))),
|
||||
_settingsLayout(new QHBoxLayout()),
|
||||
_settingsInputMethods(new QComboBox(this)),
|
||||
_manualSettingValue(new VariableTextEdit(this, 5, 1, 1)),
|
||||
_tempVars(new TempVariableSelection(this)),
|
||||
_filterSettings(new SourceSettingSelection(this)),
|
||||
_settingsString(new VariableTextEdit(this))
|
||||
{
|
||||
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
|
||||
populateActionSelection(_actions);
|
||||
auto sources = GetSourcesWithFilterNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
populateSettingsInputMethods(_settingsInputMethods);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_filters,
|
||||
SIGNAL(FilterChanged(const FilterSelection &)), this,
|
||||
SLOT(FilterChanged(const FilterSelection &)));
|
||||
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settingsString, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsStringChanged()));
|
||||
QWidget::connect(_tempVars,
|
||||
SIGNAL(SelectionChanged(const TempVariableRef &)),
|
||||
this, SLOT(SelectionChanged(const TempVariableRef &)));
|
||||
QWidget::connect(_settingsInputMethods,
|
||||
SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SettingsInputMethodChanged(int)));
|
||||
QWidget::connect(_manualSettingValue, SIGNAL(textChanged()), this,
|
||||
SLOT(ManualSettingsValueChanged()));
|
||||
QWidget::connect(_filterSettings,
|
||||
SIGNAL(SelectionChanged(const SourceSetting &)), this,
|
||||
SLOT(SelectionChanged(const SourceSetting &)));
|
||||
|
||||
auto entrylayout = new QHBoxLayout;
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sources}}", _sources},
|
||||
{"{{filters}}", _filters},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{settings}}", _filterSettings},
|
||||
{"{{settingsInputMethod}}", _settingsInputMethods},
|
||||
{"{{settingValue}}", _manualSettingValue},
|
||||
{"{{tempVar}}", _tempVars},
|
||||
{"{{getSettings}}", _getSettings},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.filter.entry"),
|
||||
entrylayout, widgetPlaceholders);
|
||||
_settingsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.filter.entry.settings"),
|
||||
_settingsLayout, widgetPlaceholders);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addWidget(_getSettings);
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entrylayout);
|
||||
mainLayout->addLayout(_settingsLayout);
|
||||
mainLayout->addWidget(_settingsString);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_filters->SetFilter(_entryData->_source, _entryData->_filter);
|
||||
_settingsString->setPlainText(_entryData->_settingsString);
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_filterSettings->SetSource(filters.empty() ? nullptr : filters.at(0));
|
||||
_filterSettings->SetSetting(_entryData->_setting);
|
||||
_settingsInputMethods->setCurrentIndex(_settingsInputMethods->findData(
|
||||
static_cast<int>(_entryData->_settingsInputMethod)));
|
||||
_tempVars->SetVariable(_entryData->_tempVar);
|
||||
_manualSettingValue->setPlainText(_entryData->_manualSettingValue);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = source;
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::FilterChanged(const FilterSelection &filter)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_filter = filter;
|
||||
}
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_filterSettings->SetSource(filters.empty() ? nullptr : filters.at(0));
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionFilter::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::GetSettingsClicked()
|
||||
{
|
||||
if (_loading || !_entryData ||
|
||||
_entryData->_filter.GetFilters(_entryData->_source).empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_entryData->_settingsInputMethod) {
|
||||
case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL: {
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_manualSettingValue->setPlainText(GetSourceSettingValue(
|
||||
filters.empty() ? nullptr : filters.at(0),
|
||||
_entryData->_setting));
|
||||
break;
|
||||
}
|
||||
case MacroActionFilter::SettingsInputMethod::INDIVIDUAL_TEMPVAR:
|
||||
break;
|
||||
case MacroActionFilter::SettingsInputMethod::JSON_STRING: {
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_settingsString->setPlainText(
|
||||
FormatJsonString(GetSourceSettings(
|
||||
filters.empty() ? nullptr : filters.at(0))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SettingsStringChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settingsString =
|
||||
_settingsString->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SettingsInputMethodChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settingsInputMethod =
|
||||
static_cast<MacroActionFilter::SettingsInputMethod>(
|
||||
_settingsInputMethods->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_tempVar = var;
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setting = setting;
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::ManualSettingsValueChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_manualSettingValue =
|
||||
_manualSettingValue->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SetWidgetVisibility()
|
||||
{
|
||||
SetLayoutVisible(_settingsLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionFilter::Action::SETTINGS);
|
||||
_filterSettings->setVisible(
|
||||
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionFilter::SettingsInputMethod::JSON_STRING);
|
||||
_settingsString->setVisible(
|
||||
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionFilter::SettingsInputMethod::JSON_STRING);
|
||||
_getSettings->setVisible(_entryData->_action ==
|
||||
MacroActionFilter::Action::SETTINGS);
|
||||
_tempVars->setVisible(_entryData->_action ==
|
||||
MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionFilter::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
|
||||
if (_entryData->_action == MacroActionFilter::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL) {
|
||||
RemoveStretchIfPresent(_settingsLayout);
|
||||
_manualSettingValue->show();
|
||||
} else {
|
||||
AddStretchIfNecessary(_settingsLayout);
|
||||
_manualSettingValue->hide();
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
104
plugins/base/macro-action-filter.hpp
Normal file
104
plugins/base/macro-action-filter.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "filter-selection.hpp"
|
||||
#include "source-setting.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionFilter : public MacroAction {
|
||||
public:
|
||||
MacroActionFilter(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionFilter>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
ENABLE,
|
||||
DISABLE,
|
||||
TOGGLE,
|
||||
SETTINGS,
|
||||
};
|
||||
|
||||
enum class SettingsInputMethod {
|
||||
INDIVIDUAL_MANUAL,
|
||||
INDIVIDUAL_TEMPVAR,
|
||||
JSON_STRING,
|
||||
};
|
||||
SettingsInputMethod _settingsInputMethod =
|
||||
SettingsInputMethod::INDIVIDUAL_MANUAL;
|
||||
|
||||
SourceSelection _source;
|
||||
FilterSelection _filter;
|
||||
Action _action = Action::ENABLE;
|
||||
StringVariable _settingsString = "";
|
||||
TempVariableRef _tempVar;
|
||||
StringVariable _manualSettingValue = "";
|
||||
SourceSetting _setting;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionFilterEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionFilterEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionFilter> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionFilterEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionFilter>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void FilterChanged(const FilterSelection &);
|
||||
void ActionChanged(int value);
|
||||
void GetSettingsClicked();
|
||||
void SettingsStringChanged();
|
||||
void SettingsInputMethodChanged(int);
|
||||
void SelectionChanged(const TempVariableRef &);
|
||||
void SelectionChanged(const SourceSetting &);
|
||||
void ManualSettingsValueChanged();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SourceSelectionWidget *_sources;
|
||||
FilterSelectionWidget *_filters;
|
||||
QComboBox *_actions;
|
||||
QPushButton *_getSettings;
|
||||
QHBoxLayout *_settingsLayout;
|
||||
QComboBox *_settingsInputMethods;
|
||||
VariableTextEdit *_manualSettingValue;
|
||||
TempVariableSelection *_tempVars;
|
||||
SourceSettingSelection *_filterSettings;
|
||||
VariableTextEdit *_settingsString;
|
||||
|
||||
std::shared_ptr<MacroActionFilter> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
921
plugins/base/macro-action-hotkey.cpp
Normal file
921
plugins/base/macro-action-hotkey.cpp
Normal file
@@ -0,0 +1,921 @@
|
||||
#include "macro-action-hotkey.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <obs-interaction.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionHotkey::id = "hotkey";
|
||||
|
||||
bool MacroActionHotkey::_registered = MacroActionFactory::Register(
|
||||
MacroActionHotkey::id,
|
||||
{MacroActionHotkey::Create, MacroActionHotkeyEdit::Create,
|
||||
"AdvSceneSwitcher.action.hotkey"});
|
||||
|
||||
const static std::map<MacroActionHotkey::Action, std::string> actionTypes = {
|
||||
{MacroActionHotkey::Action::OBS_HOTKEY,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs"},
|
||||
{MacroActionHotkey::Action::CUSTOM,
|
||||
"AdvSceneSwitcher.action.hotkey.type.custom"},
|
||||
};
|
||||
|
||||
const static std::map<obs_hotkey_registerer_type, std::string> hotkeyTypes = {
|
||||
{OBS_HOTKEY_REGISTERER_FRONTEND,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs.type.frontend"},
|
||||
{OBS_HOTKEY_REGISTERER_SOURCE,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs.type.soruce"},
|
||||
{OBS_HOTKEY_REGISTERER_OUTPUT,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs.type.output"},
|
||||
{OBS_HOTKEY_REGISTERER_ENCODER,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs.type.encoder"},
|
||||
{OBS_HOTKEY_REGISTERER_SERVICE,
|
||||
"AdvSceneSwitcher.action.hotkey.type.obs.type.service"},
|
||||
};
|
||||
|
||||
static std::unordered_map<HotkeyType, long> keyTable = {
|
||||
// Chars
|
||||
{HotkeyType::Key_A, OBS_KEY_A},
|
||||
{HotkeyType::Key_B, OBS_KEY_B},
|
||||
{HotkeyType::Key_C, OBS_KEY_C},
|
||||
{HotkeyType::Key_D, OBS_KEY_D},
|
||||
{HotkeyType::Key_E, OBS_KEY_E},
|
||||
{HotkeyType::Key_F, OBS_KEY_F},
|
||||
{HotkeyType::Key_G, OBS_KEY_G},
|
||||
{HotkeyType::Key_H, OBS_KEY_H},
|
||||
{HotkeyType::Key_I, OBS_KEY_I},
|
||||
{HotkeyType::Key_J, OBS_KEY_J},
|
||||
{HotkeyType::Key_K, OBS_KEY_K},
|
||||
{HotkeyType::Key_L, OBS_KEY_L},
|
||||
{HotkeyType::Key_M, OBS_KEY_M},
|
||||
{HotkeyType::Key_N, OBS_KEY_N},
|
||||
{HotkeyType::Key_O, OBS_KEY_O},
|
||||
{HotkeyType::Key_P, OBS_KEY_P},
|
||||
{HotkeyType::Key_Q, OBS_KEY_Q},
|
||||
{HotkeyType::Key_R, OBS_KEY_R},
|
||||
{HotkeyType::Key_S, OBS_KEY_S},
|
||||
{HotkeyType::Key_T, OBS_KEY_T},
|
||||
{HotkeyType::Key_U, OBS_KEY_U},
|
||||
{HotkeyType::Key_V, OBS_KEY_V},
|
||||
{HotkeyType::Key_W, OBS_KEY_W},
|
||||
{HotkeyType::Key_X, OBS_KEY_X},
|
||||
{HotkeyType::Key_Y, OBS_KEY_Y},
|
||||
{HotkeyType::Key_Z, OBS_KEY_Z},
|
||||
|
||||
// Numbers
|
||||
{HotkeyType::Key_0, OBS_KEY_0},
|
||||
{HotkeyType::Key_1, OBS_KEY_1},
|
||||
{HotkeyType::Key_2, OBS_KEY_2},
|
||||
{HotkeyType::Key_3, OBS_KEY_3},
|
||||
{HotkeyType::Key_4, OBS_KEY_4},
|
||||
{HotkeyType::Key_5, OBS_KEY_5},
|
||||
{HotkeyType::Key_6, OBS_KEY_6},
|
||||
{HotkeyType::Key_7, OBS_KEY_7},
|
||||
{HotkeyType::Key_8, OBS_KEY_8},
|
||||
{HotkeyType::Key_9, OBS_KEY_9},
|
||||
|
||||
{HotkeyType::Key_F1, OBS_KEY_F1},
|
||||
{HotkeyType::Key_F2, OBS_KEY_F2},
|
||||
{HotkeyType::Key_F3, OBS_KEY_F3},
|
||||
{HotkeyType::Key_F4, OBS_KEY_F4},
|
||||
{HotkeyType::Key_F5, OBS_KEY_F5},
|
||||
{HotkeyType::Key_F6, OBS_KEY_F6},
|
||||
{HotkeyType::Key_F7, OBS_KEY_F7},
|
||||
{HotkeyType::Key_F8, OBS_KEY_F8},
|
||||
{HotkeyType::Key_F9, OBS_KEY_F9},
|
||||
{HotkeyType::Key_F10, OBS_KEY_F10},
|
||||
{HotkeyType::Key_F11, OBS_KEY_F11},
|
||||
{HotkeyType::Key_F12, OBS_KEY_F12},
|
||||
{HotkeyType::Key_F13, OBS_KEY_F13},
|
||||
{HotkeyType::Key_F14, OBS_KEY_F14},
|
||||
{HotkeyType::Key_F15, OBS_KEY_F15},
|
||||
{HotkeyType::Key_F16, OBS_KEY_F16},
|
||||
{HotkeyType::Key_F17, OBS_KEY_F17},
|
||||
{HotkeyType::Key_F18, OBS_KEY_F18},
|
||||
{HotkeyType::Key_F19, OBS_KEY_F19},
|
||||
{HotkeyType::Key_F20, OBS_KEY_F20},
|
||||
{HotkeyType::Key_F21, OBS_KEY_F21},
|
||||
{HotkeyType::Key_F22, OBS_KEY_F22},
|
||||
{HotkeyType::Key_F23, OBS_KEY_F23},
|
||||
{HotkeyType::Key_F24, OBS_KEY_F24},
|
||||
|
||||
{HotkeyType::Key_Escape, OBS_KEY_ESCAPE},
|
||||
{HotkeyType::Key_Space, OBS_KEY_SPACE},
|
||||
{HotkeyType::Key_Return, OBS_KEY_RETURN},
|
||||
{HotkeyType::Key_Backspace, OBS_KEY_BACKSPACE},
|
||||
{HotkeyType::Key_Tab, OBS_KEY_TAB},
|
||||
|
||||
{HotkeyType::Key_Shift_L, OBS_KEY_SHIFT},
|
||||
{HotkeyType::Key_Shift_R, OBS_KEY_SHIFT},
|
||||
{HotkeyType::Key_Control_L, OBS_KEY_CONTROL},
|
||||
{HotkeyType::Key_Control_R, OBS_KEY_CONTROL},
|
||||
{HotkeyType::Key_Alt_L, OBS_KEY_ALT},
|
||||
{HotkeyType::Key_Alt_R, OBS_KEY_ALT},
|
||||
{HotkeyType::Key_Win_L, OBS_KEY_META},
|
||||
{HotkeyType::Key_Win_R, OBS_KEY_META},
|
||||
{HotkeyType::Key_Apps, OBS_KEY_APPLICATIONLEFT},
|
||||
|
||||
{HotkeyType::Key_CapsLock, OBS_KEY_CAPSLOCK},
|
||||
{HotkeyType::Key_NumLock, OBS_KEY_NUMLOCK},
|
||||
{HotkeyType::Key_ScrollLock, OBS_KEY_SCROLLLOCK},
|
||||
|
||||
{HotkeyType::Key_PrintScreen, OBS_KEY_PRINT},
|
||||
{HotkeyType::Key_Pause, OBS_KEY_PAUSE},
|
||||
|
||||
{HotkeyType::Key_Insert, OBS_KEY_INSERT},
|
||||
{HotkeyType::Key_Delete, OBS_KEY_DELETE},
|
||||
{HotkeyType::Key_PageUP, OBS_KEY_PAGEUP},
|
||||
{HotkeyType::Key_PageDown, OBS_KEY_PAGEDOWN},
|
||||
{HotkeyType::Key_Home, OBS_KEY_HOME},
|
||||
{HotkeyType::Key_End, OBS_KEY_END},
|
||||
|
||||
{HotkeyType::Key_Left, OBS_KEY_LEFT},
|
||||
{HotkeyType::Key_Up, OBS_KEY_UP},
|
||||
{HotkeyType::Key_Right, OBS_KEY_RIGHT},
|
||||
{HotkeyType::Key_Down, OBS_KEY_DOWN},
|
||||
|
||||
{HotkeyType::Key_Numpad0, OBS_KEY_NUM0},
|
||||
{HotkeyType::Key_Numpad1, OBS_KEY_NUM1},
|
||||
{HotkeyType::Key_Numpad2, OBS_KEY_NUM2},
|
||||
{HotkeyType::Key_Numpad3, OBS_KEY_NUM3},
|
||||
{HotkeyType::Key_Numpad4, OBS_KEY_NUM4},
|
||||
{HotkeyType::Key_Numpad5, OBS_KEY_NUM5},
|
||||
{HotkeyType::Key_Numpad6, OBS_KEY_NUM6},
|
||||
{HotkeyType::Key_Numpad7, OBS_KEY_NUM7},
|
||||
{HotkeyType::Key_Numpad8, OBS_KEY_NUM8},
|
||||
{HotkeyType::Key_Numpad9, OBS_KEY_NUM9},
|
||||
|
||||
{HotkeyType::Key_NumpadAdd, OBS_KEY_NUMPLUS},
|
||||
{HotkeyType::Key_NumpadSubtract, OBS_KEY_NUMMINUS},
|
||||
{HotkeyType::Key_NumpadMultiply, OBS_KEY_NUMASTERISK},
|
||||
{HotkeyType::Key_NumpadDivide, OBS_KEY_NUMSLASH},
|
||||
{HotkeyType::Key_NumpadDecimal, OBS_KEY_NUMPERIOD},
|
||||
//{HotkeyType::Key_NumpadEnter, ???},
|
||||
};
|
||||
|
||||
static obs_key_combination
|
||||
keysToOBSKeycombo(const std::vector<HotkeyType> &keys)
|
||||
{
|
||||
obs_key_combination combo{};
|
||||
auto it = keyTable.find(keys.back());
|
||||
if (it != keyTable.end()) {
|
||||
combo.key = (obs_key_t)it->second;
|
||||
}
|
||||
if (keys.size() == 1) {
|
||||
return combo;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < keys.size() - 1; i++) {
|
||||
switch (keys[i]) {
|
||||
case HotkeyType::Key_Shift_L:
|
||||
combo.modifiers |= INTERACT_SHIFT_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Shift_R:
|
||||
combo.modifiers |= INTERACT_SHIFT_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Control_L:
|
||||
combo.modifiers |= INTERACT_CONTROL_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Control_R:
|
||||
combo.modifiers |= INTERACT_CONTROL_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Alt_L:
|
||||
combo.modifiers |= INTERACT_ALT_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Alt_R:
|
||||
combo.modifiers |= INTERACT_ALT_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Win_L:
|
||||
combo.modifiers |= INTERACT_COMMAND_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_Win_R:
|
||||
combo.modifiers |= INTERACT_COMMAND_KEY;
|
||||
break;
|
||||
case HotkeyType::Key_CapsLock:
|
||||
combo.modifiers |= INTERACT_CAPS_KEY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
static void InjectKeys(const std::vector<HotkeyType> &keys, int duration)
|
||||
{
|
||||
auto combo = keysToOBSKeycombo(keys);
|
||||
if (obs_key_combination_is_empty(combo)) {
|
||||
return;
|
||||
}
|
||||
// I am not sure why this is necessary
|
||||
obs_hotkey_inject_event(combo, false);
|
||||
|
||||
obs_hotkey_inject_event(combo, true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||
obs_hotkey_inject_event(combo, false);
|
||||
}
|
||||
|
||||
static void addNamePrefix(std::string &name, obs_hotkey_t *hotkey)
|
||||
{
|
||||
const auto type = obs_hotkey_get_registerer_type(hotkey);
|
||||
std::string prefix;
|
||||
if (type == OBS_HOTKEY_REGISTERER_SOURCE) {
|
||||
auto source = reinterpret_cast<obs_weak_source_t *>(
|
||||
obs_hotkey_get_registerer(hotkey));
|
||||
prefix = "[" + GetWeakSourceName(source) + "] ";
|
||||
} else if (type == OBS_HOTKEY_REGISTERER_OUTPUT) {
|
||||
auto weakOutput = reinterpret_cast<obs_weak_output_t *>(
|
||||
obs_hotkey_get_registerer(hotkey));
|
||||
std::string name;
|
||||
auto output = obs_weak_output_get_output(weakOutput);
|
||||
if (output) {
|
||||
name = obs_output_get_name(output);
|
||||
obs_output_release(output);
|
||||
}
|
||||
prefix = "[" + name + "] ";
|
||||
} else if (type == OBS_HOTKEY_REGISTERER_ENCODER) {
|
||||
auto weakEncoder = reinterpret_cast<obs_weak_encoder_t *>(
|
||||
obs_hotkey_get_registerer(hotkey));
|
||||
std::string name;
|
||||
auto encoder = obs_weak_encoder_get_encoder(weakEncoder);
|
||||
if (encoder) {
|
||||
name = obs_encoder_get_name(encoder);
|
||||
obs_encoder_release(encoder);
|
||||
}
|
||||
prefix = "[" + name + "] ";
|
||||
} else if (type == OBS_HOTKEY_REGISTERER_SERVICE) {
|
||||
auto weakService = reinterpret_cast<obs_weak_service_t *>(
|
||||
obs_hotkey_get_registerer(hotkey));
|
||||
std::string name;
|
||||
auto service = obs_weak_service_get_service(weakService);
|
||||
if (service) {
|
||||
name = obs_service_get_name(service);
|
||||
obs_service_release(service);
|
||||
}
|
||||
prefix = "[" + name + "] ";
|
||||
}
|
||||
name = prefix + name;
|
||||
}
|
||||
|
||||
static obs_hotkey_id getHotkeyIdByName(const std::string &name)
|
||||
{
|
||||
struct Parameters {
|
||||
std::string name;
|
||||
obs_hotkey_id id = OBS_INVALID_HOTKEY_ID;
|
||||
} params;
|
||||
|
||||
auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) {
|
||||
auto params = static_cast<Parameters *>(param);
|
||||
std::string name = obs_hotkey_get_name(hotkey);
|
||||
addNamePrefix(name, hotkey);
|
||||
if (name == params->name) {
|
||||
params->id = id;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
params.name = name;
|
||||
obs_enum_hotkeys(func, ¶ms);
|
||||
return params.id;
|
||||
}
|
||||
|
||||
void MacroActionHotkey::SendOBSHotkey()
|
||||
{
|
||||
auto id = getHotkeyIdByName(_hotkeyName);
|
||||
if (id == OBS_INVALID_HOTKEY_ID) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to get hotkey id for \"%s\" - key will not be pressed",
|
||||
_hotkeyName.c_str());
|
||||
return;
|
||||
}
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
[](void *param) {
|
||||
auto id = (size_t *)param;
|
||||
obs_hotkey_trigger_routed_callback(*id, false);
|
||||
obs_hotkey_trigger_routed_callback(*id, true);
|
||||
obs_hotkey_trigger_routed_callback(*id, false);
|
||||
},
|
||||
&id, true);
|
||||
}
|
||||
|
||||
void MacroActionHotkey::SendCustomHotkey()
|
||||
{
|
||||
std::vector<HotkeyType> keys;
|
||||
if (_leftShift) {
|
||||
keys.push_back(HotkeyType::Key_Shift_L);
|
||||
}
|
||||
if (_rightShift) {
|
||||
keys.push_back(HotkeyType::Key_Shift_R);
|
||||
}
|
||||
if (_leftCtrl) {
|
||||
keys.push_back(HotkeyType::Key_Control_L);
|
||||
}
|
||||
if (_rightCtrl) {
|
||||
keys.push_back(HotkeyType::Key_Control_R);
|
||||
}
|
||||
if (_leftAlt) {
|
||||
keys.push_back(HotkeyType::Key_Alt_L);
|
||||
}
|
||||
if (_rightAlt) {
|
||||
keys.push_back(HotkeyType::Key_Alt_R);
|
||||
}
|
||||
if (_leftMeta) {
|
||||
keys.push_back(HotkeyType::Key_Win_L);
|
||||
}
|
||||
if (_rightMeta) {
|
||||
keys.push_back(HotkeyType::Key_Win_R);
|
||||
}
|
||||
if (_key != HotkeyType::Key_NoKey) {
|
||||
keys.push_back(_key);
|
||||
}
|
||||
|
||||
if (!keys.empty()) {
|
||||
int dur = _duration.Milliseconds();
|
||||
if (_onlySendToObs || !CanSimulateKeyPresses()) {
|
||||
std::thread t([keys, dur]() { InjectKeys(keys, dur); });
|
||||
t.detach();
|
||||
} else {
|
||||
std::thread t([keys, dur]() { PressKeys(keys, dur); });
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionHotkey::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case advss::MacroActionHotkey::Action::OBS_HOTKEY:
|
||||
SendOBSHotkey();
|
||||
break;
|
||||
case advss::MacroActionHotkey::Action::CUSTOM:
|
||||
SendCustomHotkey();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionHotkey::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "sent hotkey type %d", static_cast<int>(_action));
|
||||
}
|
||||
|
||||
bool MacroActionHotkey::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "hotkeyType", _hotkeyType);
|
||||
obs_data_set_string(obj, "hotkeyName", _hotkeyName.c_str());
|
||||
obs_data_set_int(obj, "key", static_cast<int>(_key));
|
||||
obs_data_set_bool(obj, "left_shift", _leftShift);
|
||||
obs_data_set_bool(obj, "right_shift", _rightShift);
|
||||
obs_data_set_bool(obj, "left_ctrl", _leftCtrl);
|
||||
obs_data_set_bool(obj, "right_ctrl", _rightCtrl);
|
||||
obs_data_set_bool(obj, "left_alt", _leftAlt);
|
||||
obs_data_set_bool(obj, "right_alt", _rightAlt);
|
||||
obs_data_set_bool(obj, "left_meta", _leftMeta);
|
||||
obs_data_set_bool(obj, "right_meta", _rightMeta);
|
||||
_duration.Save(obj);
|
||||
obs_data_set_bool(obj, "onlyOBS", _onlySendToObs);
|
||||
obs_data_set_int(obj, "version", 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionHotkey::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
int version = obs_data_get_int(obj, "version");
|
||||
if (version != 2) {
|
||||
_action = Action::CUSTOM;
|
||||
} else {
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
}
|
||||
_hotkeyType = static_cast<obs_hotkey_registerer_type>(
|
||||
obs_data_get_int(obj, "hotkeyType"));
|
||||
_hotkeyName = obs_data_get_string(obj, "hotkeyName");
|
||||
_key = static_cast<HotkeyType>(obs_data_get_int(obj, "key"));
|
||||
_leftShift = obs_data_get_bool(obj, "left_shift");
|
||||
_rightShift = obs_data_get_bool(obj, "right_shift");
|
||||
_leftCtrl = obs_data_get_bool(obj, "left_ctrl");
|
||||
_rightCtrl = obs_data_get_bool(obj, "right_ctrl");
|
||||
_leftAlt = obs_data_get_bool(obj, "left_alt");
|
||||
_rightAlt = obs_data_get_bool(obj, "right_alt");
|
||||
_leftMeta = obs_data_get_bool(obj, "left_meta");
|
||||
_rightMeta = obs_data_get_bool(obj, "right_meta");
|
||||
if (version == 0) {
|
||||
_duration = obs_data_get_int(obj, "duration") / 1000.0;
|
||||
} else {
|
||||
_duration.Load(obj);
|
||||
}
|
||||
_onlySendToObs = obs_data_get_bool(obj, "onlyOBS");
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateKeySelection(QComboBox *list)
|
||||
{
|
||||
list->addItem("No key");
|
||||
list->addItem("A");
|
||||
list->addItem("B");
|
||||
list->addItem("C");
|
||||
list->addItem("D");
|
||||
list->addItem("E");
|
||||
list->addItem("F");
|
||||
list->addItem("G");
|
||||
list->addItem("H");
|
||||
list->addItem("I");
|
||||
list->addItem("J");
|
||||
list->addItem("K");
|
||||
list->addItem("L");
|
||||
list->addItem("M");
|
||||
list->addItem("N");
|
||||
list->addItem("O");
|
||||
list->addItem("P");
|
||||
list->addItem("Q");
|
||||
list->addItem("R");
|
||||
list->addItem("S");
|
||||
list->addItem("T");
|
||||
list->addItem("U");
|
||||
list->addItem("V");
|
||||
list->addItem("W");
|
||||
list->addItem("X");
|
||||
list->addItem("Y");
|
||||
list->addItem("Z");
|
||||
list->addItem("0");
|
||||
list->addItem("1");
|
||||
list->addItem("2");
|
||||
list->addItem("3");
|
||||
list->addItem("4");
|
||||
list->addItem("5");
|
||||
list->addItem("6");
|
||||
list->addItem("7");
|
||||
list->addItem("8");
|
||||
list->addItem("9");
|
||||
list->addItem("F1");
|
||||
list->addItem("F2");
|
||||
list->addItem("F3");
|
||||
list->addItem("F4");
|
||||
list->addItem("F5");
|
||||
list->addItem("F6");
|
||||
list->addItem("F7");
|
||||
list->addItem("F8");
|
||||
list->addItem("F9");
|
||||
list->addItem("F10");
|
||||
list->addItem("F11");
|
||||
list->addItem("F12");
|
||||
list->addItem("F13");
|
||||
list->addItem("F14");
|
||||
list->addItem("F15");
|
||||
list->addItem("F16");
|
||||
list->addItem("F17");
|
||||
list->addItem("F18");
|
||||
list->addItem("F19");
|
||||
list->addItem("F20");
|
||||
list->addItem("F21");
|
||||
list->addItem("F22");
|
||||
list->addItem("F23");
|
||||
list->addItem("F24");
|
||||
list->addItem("Escape");
|
||||
list->addItem("Space");
|
||||
list->addItem("Return");
|
||||
list->addItem("Backspace");
|
||||
list->addItem("Tab");
|
||||
list->addItem("Shift_L");
|
||||
list->addItem("Shift_R");
|
||||
list->addItem("Control_L");
|
||||
list->addItem("Control_R");
|
||||
list->addItem("Alt_L");
|
||||
list->addItem("Alt_R");
|
||||
list->addItem("Win_L");
|
||||
list->addItem("Win_R");
|
||||
list->addItem("Apps");
|
||||
list->addItem("CapsLock");
|
||||
list->addItem("NumLock");
|
||||
list->addItem("ScrollLock");
|
||||
list->addItem("PrintScreen");
|
||||
list->addItem("Pause");
|
||||
list->addItem("Insert");
|
||||
list->addItem("Delete");
|
||||
list->addItem("PageUP");
|
||||
list->addItem("PageDown");
|
||||
list->addItem("Home");
|
||||
list->addItem("End");
|
||||
list->addItem("Left");
|
||||
list->addItem("Right");
|
||||
list->addItem("Up");
|
||||
list->addItem("Down");
|
||||
list->addItem("Numpad0");
|
||||
list->addItem("Numpad1");
|
||||
list->addItem("Numpad2");
|
||||
list->addItem("Numpad3");
|
||||
list->addItem("Numpad4");
|
||||
list->addItem("Numpad5");
|
||||
list->addItem("Numpad6");
|
||||
list->addItem("Numpad7");
|
||||
list->addItem("Numpad8");
|
||||
list->addItem("Numpad9");
|
||||
list->addItem("NumpadAdd");
|
||||
list->addItem("NumpadSubtract");
|
||||
list->addItem("NumpadMultiply");
|
||||
list->addItem("NumpadDivide");
|
||||
list->addItem("NumpadDecimal");
|
||||
list->addItem("NumpadEnter");
|
||||
}
|
||||
|
||||
static void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, value] : actionTypes) {
|
||||
list->addItem(obs_module_text(value.c_str()));
|
||||
}
|
||||
}
|
||||
static void populateHotkeyTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, value] : hotkeyTypes) {
|
||||
list->addItem(obs_module_text(value.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static void addNamePrefix(QString &name, obs_hotkey_t *hotkey)
|
||||
{
|
||||
auto temp = name.toStdString();
|
||||
addNamePrefix(temp, hotkey);
|
||||
name = QString::fromStdString(temp);
|
||||
}
|
||||
|
||||
static void populateHotkeyNames(QComboBox *list,
|
||||
obs_hotkey_registerer_type type)
|
||||
{
|
||||
struct Parameters {
|
||||
QStringList names;
|
||||
QStringList descriptions;
|
||||
obs_hotkey_registerer_type type;
|
||||
} params;
|
||||
|
||||
auto func = [](void *param, obs_hotkey_id, obs_hotkey_t *hotkey) {
|
||||
auto params = static_cast<Parameters *>(param);
|
||||
if (obs_hotkey_get_registerer_type(hotkey) == params->type) {
|
||||
QString description(obs_hotkey_get_description(hotkey));
|
||||
addNamePrefix(description, hotkey);
|
||||
params->descriptions.append(description);
|
||||
QString name(obs_hotkey_get_name(hotkey));
|
||||
addNamePrefix(name, hotkey);
|
||||
params->names.append(name);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
params.type = type;
|
||||
obs_enum_hotkeys(func, ¶ms);
|
||||
|
||||
list->clear();
|
||||
for (int i = 0; i < params.names.size(); i++) {
|
||||
list->addItem(params.descriptions.at(i), params.names.at(i));
|
||||
}
|
||||
AddSelectionEntry(list,
|
||||
obs_module_text("AdvSceneSwitcher.selectHotkey"));
|
||||
}
|
||||
|
||||
MacroActionHotkeyEdit::MacroActionHotkeyEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionHotkey> entryData)
|
||||
: QWidget(parent),
|
||||
_actionType(new QComboBox()),
|
||||
_hotkeyType(new QComboBox()),
|
||||
_obsHotkeys(new QComboBox()),
|
||||
_keys(new QComboBox()),
|
||||
_leftShift(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.leftShift"))),
|
||||
_rightShift(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.hotkey.rightShift"))),
|
||||
_leftCtrl(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.leftCtrl"))),
|
||||
_rightCtrl(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.rightCtrl"))),
|
||||
_leftAlt(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.leftAlt"))),
|
||||
_rightAlt(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.rightAlt"))),
|
||||
_leftMeta(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.leftMeta"))),
|
||||
_rightMeta(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.rightMeta"))),
|
||||
_duration(new DurationSelection(this, false)),
|
||||
_onlySendToOBS(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.onlyOBS"))),
|
||||
_noKeyPressSimulationWarning(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.disabled"))),
|
||||
_entryLayout(new QHBoxLayout()),
|
||||
_keyConfigLayout(new QHBoxLayout())
|
||||
{
|
||||
populateKeySelection(_keys);
|
||||
populateActionSelection(_actionType);
|
||||
populateHotkeyTypeSelection(_hotkeyType);
|
||||
|
||||
QWidget::connect(_actionType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_hotkeyType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(HotkeyTypeChanged(int)));
|
||||
QWidget::connect(_obsHotkeys, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(OBSHotkeyChanged(int)));
|
||||
QWidget::connect(_keys, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(KeyChanged(int)));
|
||||
QWidget::connect(_leftShift, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(LShiftChanged(int)));
|
||||
QWidget::connect(_rightShift, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RShiftChanged(int)));
|
||||
QWidget::connect(_leftCtrl, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(LCtrlChanged(int)));
|
||||
QWidget::connect(_rightCtrl, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RCtrlChanged(int)));
|
||||
QWidget::connect(_leftAlt, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(LAltChanged(int)));
|
||||
QWidget::connect(_rightAlt, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RAltChanged(int)));
|
||||
QWidget::connect(_leftMeta, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(LMetaChanged(int)));
|
||||
QWidget::connect(_rightMeta, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RMetaChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_onlySendToOBS, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(OnlySendToOBSChanged(int)));
|
||||
|
||||
_entryLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_keyConfigLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_keyConfigLayout->addWidget(_leftShift);
|
||||
_keyConfigLayout->addWidget(_rightShift);
|
||||
_keyConfigLayout->addWidget(_leftCtrl);
|
||||
_keyConfigLayout->addWidget(_rightCtrl);
|
||||
_keyConfigLayout->addWidget(_leftAlt);
|
||||
_keyConfigLayout->addWidget(_rightAlt);
|
||||
_keyConfigLayout->addWidget(_leftMeta);
|
||||
_keyConfigLayout->addWidget(_rightMeta);
|
||||
_keyConfigLayout->addStretch();
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_entryLayout);
|
||||
mainLayout->addLayout(_keyConfigLayout);
|
||||
mainLayout->addWidget(_onlySendToOBS);
|
||||
mainLayout->addWidget(_noKeyPressSimulationWarning);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_onlySendToOBS->setEnabled(CanSimulateKeyPresses());
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::RepopulateOBSHotkeySelection()
|
||||
{
|
||||
populateHotkeyNames(_obsHotkeys, _entryData->_hotkeyType);
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::SetWidgetVisibility()
|
||||
{
|
||||
_entryLayout->removeWidget(_actionType);
|
||||
_entryLayout->removeWidget(_hotkeyType);
|
||||
_entryLayout->removeWidget(_obsHotkeys);
|
||||
_entryLayout->removeWidget(_keys);
|
||||
_entryLayout->removeWidget(_duration);
|
||||
ClearLayout(_entryLayout);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
_entryData->_action ==
|
||||
MacroActionHotkey::Action::OBS_HOTKEY
|
||||
? "AdvSceneSwitcher.action.hotkey.entry.obs"
|
||||
: "AdvSceneSwitcher.action.hotkey.entry.custom"),
|
||||
_entryLayout,
|
||||
{
|
||||
{"{{actionType}}", _actionType},
|
||||
{"{{hotkeyType}}", _hotkeyType},
|
||||
{"{{obsHotkeys}}", _obsHotkeys},
|
||||
{"{{keys}}", _keys},
|
||||
{"{{duration}}", _duration},
|
||||
});
|
||||
|
||||
_noKeyPressSimulationWarning->setVisible(!_entryData->_onlySendToObs &&
|
||||
!CanSimulateKeyPresses());
|
||||
SetLayoutVisible(_keyConfigLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionHotkey::Action::CUSTOM);
|
||||
_duration->setVisible(_entryData->_action ==
|
||||
MacroActionHotkey::Action::CUSTOM);
|
||||
_keys->setVisible(_entryData->_action ==
|
||||
MacroActionHotkey::Action::CUSTOM);
|
||||
_onlySendToOBS->setVisible(_entryData->_action ==
|
||||
MacroActionHotkey::Action::CUSTOM);
|
||||
_hotkeyType->setVisible(_entryData->_action ==
|
||||
MacroActionHotkey::Action::OBS_HOTKEY);
|
||||
_obsHotkeys->setVisible(_entryData->_action ==
|
||||
MacroActionHotkey::Action::OBS_HOTKEY);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
static QString getHotkeyDescriptionByName(const std::string &name)
|
||||
{
|
||||
struct Parameters {
|
||||
std::string name;
|
||||
QString description = "";
|
||||
} params;
|
||||
|
||||
auto func = [](void *param, obs_hotkey_id, obs_hotkey_t *hotkey) {
|
||||
auto params = static_cast<Parameters *>(param);
|
||||
std::string name = obs_hotkey_get_name(hotkey);
|
||||
addNamePrefix(name, hotkey);
|
||||
if (name == params->name) {
|
||||
params->description =
|
||||
obs_hotkey_get_description(hotkey);
|
||||
addNamePrefix(params->description, hotkey);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
params.name = name;
|
||||
obs_enum_hotkeys(func, ¶ms);
|
||||
return params.description;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actionType->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_hotkeyType->setCurrentIndex(static_cast<int>(_entryData->_hotkeyType));
|
||||
RepopulateOBSHotkeySelection();
|
||||
_obsHotkeys->setCurrentText(
|
||||
getHotkeyDescriptionByName(_entryData->_hotkeyName));
|
||||
_keys->setCurrentIndex(static_cast<int>(_entryData->_key));
|
||||
_leftShift->setChecked(_entryData->_leftShift);
|
||||
_rightShift->setChecked(_entryData->_rightShift);
|
||||
_leftCtrl->setChecked(_entryData->_leftCtrl);
|
||||
_rightCtrl->setChecked(_entryData->_rightCtrl);
|
||||
_leftAlt->setChecked(_entryData->_leftAlt);
|
||||
_rightAlt->setChecked(_entryData->_rightAlt);
|
||||
_leftMeta->setChecked(_entryData->_leftMeta);
|
||||
_rightMeta->setChecked(_entryData->_rightMeta);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_onlySendToOBS->setChecked(_entryData->_onlySendToObs ||
|
||||
!CanSimulateKeyPresses());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::LShiftChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_leftShift = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::RShiftChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rightShift = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::LCtrlChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_leftCtrl = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::RCtrlChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rightCtrl = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::LAltChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_leftAlt = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::RAltChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rightAlt = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::LMetaChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_leftMeta = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::RMetaChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rightMeta = state;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::OnlySendToOBSChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_onlySendToObs = state;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::KeyChanged(int key)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_key = static_cast<HotkeyType>(key);
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionHotkey::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::HotkeyTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_hotkeyType =
|
||||
static_cast<obs_hotkey_registerer_type>(value);
|
||||
}
|
||||
RepopulateOBSHotkeySelection();
|
||||
}
|
||||
|
||||
void MacroActionHotkeyEdit::OBSHotkeyChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
if (idx == -1) {
|
||||
_entryData->_hotkeyName = "";
|
||||
return;
|
||||
}
|
||||
_entryData->_hotkeyName =
|
||||
_obsHotkeys->itemData(idx).toString().toStdString();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
119
plugins/base/macro-action-hotkey.hpp
Normal file
119
plugins/base/macro-action-hotkey.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "hotkey-helpers.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionHotkey : public MacroAction {
|
||||
public:
|
||||
MacroActionHotkey(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionHotkey>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
OBS_HOTKEY,
|
||||
CUSTOM,
|
||||
};
|
||||
Action _action = Action::OBS_HOTKEY;
|
||||
|
||||
obs_hotkey_registerer_type _hotkeyType = OBS_HOTKEY_REGISTERER_FRONTEND;
|
||||
std::string _hotkeyName;
|
||||
|
||||
HotkeyType _key = HotkeyType::Key_NoKey;
|
||||
bool _leftShift = false;
|
||||
bool _rightShift = false;
|
||||
bool _leftCtrl = false;
|
||||
bool _rightCtrl = false;
|
||||
bool _leftAlt = false;
|
||||
bool _rightAlt = false;
|
||||
bool _leftMeta = false;
|
||||
bool _rightMeta = false;
|
||||
Duration _duration = 0.3;
|
||||
#ifdef __APPLE__
|
||||
bool _onlySendToObs = true;
|
||||
#else
|
||||
bool _onlySendToObs = false;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void SendOBSHotkey();
|
||||
void SendCustomHotkey();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionHotkeyEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionHotkeyEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionHotkey> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionHotkeyEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionHotkey>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int key);
|
||||
void HotkeyTypeChanged(int key);
|
||||
void OBSHotkeyChanged(int);
|
||||
void KeyChanged(int key);
|
||||
void LShiftChanged(int state);
|
||||
void RShiftChanged(int state);
|
||||
void LCtrlChanged(int state);
|
||||
void RCtrlChanged(int state);
|
||||
void LAltChanged(int state);
|
||||
void RAltChanged(int state);
|
||||
void LMetaChanged(int state);
|
||||
void RMetaChanged(int state);
|
||||
void DurationChanged(const Duration &);
|
||||
void OnlySendToOBSChanged(int state);
|
||||
|
||||
protected:
|
||||
QComboBox *_actionType;
|
||||
QComboBox *_hotkeyType;
|
||||
QComboBox *_obsHotkeys;
|
||||
QComboBox *_keys;
|
||||
QCheckBox *_leftShift;
|
||||
QCheckBox *_rightShift;
|
||||
QCheckBox *_leftCtrl;
|
||||
QCheckBox *_rightCtrl;
|
||||
QCheckBox *_leftAlt;
|
||||
QCheckBox *_rightAlt;
|
||||
QCheckBox *_leftMeta;
|
||||
QCheckBox *_rightMeta;
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_onlySendToOBS;
|
||||
QLabel *_noKeyPressSimulationWarning;
|
||||
|
||||
std::shared_ptr<MacroActionHotkey> _entryData;
|
||||
|
||||
private:
|
||||
void RepopulateOBSHotkeySelection();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QHBoxLayout *_entryLayout;
|
||||
QHBoxLayout *_keyConfigLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
298
plugins/base/macro-action-http.cpp
Normal file
298
plugins/base/macro-action-http.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "macro-action-http.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "curl-helper.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionHttp::id = "http";
|
||||
|
||||
bool MacroActionHttp::_registered = MacroActionFactory::Register(
|
||||
MacroActionHttp::id,
|
||||
{MacroActionHttp::Create, MacroActionHttpEdit::Create,
|
||||
"AdvSceneSwitcher.action.http"});
|
||||
|
||||
const static std::map<MacroActionHttp::Method, std::string> methods = {
|
||||
{MacroActionHttp::Method::GET, "AdvSceneSwitcher.action.http.type.get"},
|
||||
{MacroActionHttp::Method::POST,
|
||||
"AdvSceneSwitcher.action.http.type.post"},
|
||||
};
|
||||
|
||||
static size_t DropCB(void *, size_t size, size_t nmemb, std::string *)
|
||||
{
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static size_t WriteCB(void *ptr, size_t size, size_t nmemb, std::string *buffer)
|
||||
{
|
||||
buffer->append((char *)ptr, nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
void MacroActionHttp::SetupHeaders()
|
||||
{
|
||||
if (!_setHeaders) {
|
||||
return;
|
||||
}
|
||||
struct curl_slist *headers = nullptr;
|
||||
for (auto &header : _headers) {
|
||||
headers = CurlHelper::SlistAppend(headers, header.c_str());
|
||||
}
|
||||
if (!_headers.empty()) {
|
||||
CurlHelper::SetOpt(CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionHttp::Get()
|
||||
{
|
||||
CurlHelper::SetOpt(CURLOPT_URL, _url.c_str());
|
||||
CurlHelper::SetOpt(CURLOPT_HTTPGET, 1L);
|
||||
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
|
||||
SetupHeaders();
|
||||
|
||||
std::string response;
|
||||
if (IsReferencedInVars()) {
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCB);
|
||||
} else {
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, DropCB);
|
||||
}
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &response);
|
||||
CurlHelper::Perform();
|
||||
|
||||
SetVariableValue(response);
|
||||
}
|
||||
|
||||
void MacroActionHttp::Post()
|
||||
{
|
||||
CurlHelper::SetOpt(CURLOPT_URL, _url.c_str());
|
||||
CurlHelper::SetOpt(CURLOPT_POSTFIELDS, _data.c_str());
|
||||
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
|
||||
SetupHeaders();
|
||||
CurlHelper::Perform();
|
||||
}
|
||||
|
||||
bool MacroActionHttp::PerformAction()
|
||||
{
|
||||
if (!CurlHelper::Initialized()) {
|
||||
blog(LOG_WARNING,
|
||||
"cannot perform http action (curl not found)");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_method) {
|
||||
case MacroActionHttp::Method::GET:
|
||||
Get();
|
||||
break;
|
||||
case MacroActionHttp::Method::POST:
|
||||
Post();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionHttp::LogAction() const
|
||||
{
|
||||
auto it = methods.find(_method);
|
||||
if (it != methods.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"sent http request \"%s\" to \"%s\" with data \"%s\"",
|
||||
it->second.c_str(), _url.c_str(), _data.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown http action %d",
|
||||
static_cast<int>(_method));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionHttp::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_url.Save(obj, "url");
|
||||
_data.Save(obj, "data");
|
||||
obs_data_set_bool(obj, "setHeaders", _setHeaders);
|
||||
_headers.Save(obj, "headers", "header");
|
||||
obs_data_set_int(obj, "method", static_cast<int>(_method));
|
||||
_timeout.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionHttp::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_url.Load(obj, "url");
|
||||
_data.Load(obj, "data");
|
||||
_setHeaders = obs_data_get_bool(obj, "setHeaders");
|
||||
_headers.Load(obj, "headers", "header");
|
||||
_method = static_cast<Method>(obs_data_get_int(obj, "method"));
|
||||
_timeout.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionHttp::GetShortDesc() const
|
||||
{
|
||||
return _url.UnresolvedValue();
|
||||
}
|
||||
|
||||
static inline void populateMethodSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : methods) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionHttpEdit::MacroActionHttpEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionHttp> entryData)
|
||||
: QWidget(parent),
|
||||
_url(new VariableLineEdit(this)),
|
||||
_methods(new QComboBox()),
|
||||
_data(new VariableTextEdit(this)),
|
||||
_setHeaders(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.setHeaders"))),
|
||||
_headerListLayout(new QVBoxLayout()),
|
||||
_headerList(new StringListEdit(
|
||||
this, obs_module_text("AdvSceneSwitcher.action.http.headers"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.addHeader"))),
|
||||
_timeout(new DurationSelection(this, false))
|
||||
{
|
||||
populateMethodSelection(_methods);
|
||||
_headerList->SetMaxStringSize(4096);
|
||||
|
||||
QWidget::connect(_url, SIGNAL(editingFinished()), this,
|
||||
SLOT(URLChanged()));
|
||||
QWidget::connect(_data, SIGNAL(textChanged()), this,
|
||||
SLOT(DataChanged()));
|
||||
QWidget::connect(_methods, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MethodChanged(int)));
|
||||
QWidget::connect(_setHeaders, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SetHeadersChanged(int)));
|
||||
QWidget::connect(_headerList,
|
||||
SIGNAL(StringListChanged(const StringList &)), this,
|
||||
SLOT(HeadersChanged(const StringList &)));
|
||||
QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(TimeoutChanged(const Duration &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{url}}", _url},
|
||||
{"{{method}}", _methods},
|
||||
{"{{data}}", _data},
|
||||
{"{{timeout}}", _timeout},
|
||||
};
|
||||
auto *actionLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.entry.line1"),
|
||||
actionLayout, widgetPlaceholders);
|
||||
auto *timeoutLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.entry.line2"),
|
||||
timeoutLayout, widgetPlaceholders);
|
||||
|
||||
_headerListLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.http.headers")));
|
||||
_headerListLayout->addWidget(_headerList);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(actionLayout);
|
||||
mainLayout->addWidget(_setHeaders);
|
||||
mainLayout->addLayout(_headerListLayout);
|
||||
mainLayout->addWidget(_data);
|
||||
mainLayout->addLayout(timeoutLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_url->setText(_entryData->_url);
|
||||
_data->setPlainText(_entryData->_data);
|
||||
_setHeaders->setChecked(_entryData->_setHeaders);
|
||||
_headerList->SetStringList(_entryData->_headers);
|
||||
_methods->setCurrentIndex(static_cast<int>(_entryData->_method));
|
||||
_timeout->SetDuration(_entryData->_timeout);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::URLChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_url = _url->text().toStdString();
|
||||
emit(HeaderInfoChanged(_url->text()));
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::MethodChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_method = static_cast<MacroActionHttp::Method>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::TimeoutChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_timeout = dur;
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetHeadersChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setHeaders = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::HeadersChanged(const StringList &headers)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_headers = headers;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::SetWidgetVisibility()
|
||||
{
|
||||
_data->setVisible(_entryData->_method == MacroActionHttp::Method::POST);
|
||||
SetLayoutVisible(_headerListLayout, _entryData->_setHeaders);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionHttpEdit::DataChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_data = _data->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
91
plugins/base/macro-action-http.hpp
Normal file
91
plugins/base/macro-action-http.hpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "string-list.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionHttp : public MacroAction {
|
||||
public:
|
||||
MacroActionHttp(Macro *m) : MacroAction(m, true) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionHttp>(m);
|
||||
}
|
||||
|
||||
enum class Method {
|
||||
GET = 0,
|
||||
POST,
|
||||
};
|
||||
|
||||
StringVariable _url = obs_module_text("AdvSceneSwitcher.enterURL");
|
||||
StringVariable _data = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
bool _setHeaders = false;
|
||||
StringList _headers;
|
||||
Method _method = Method::GET;
|
||||
Duration _timeout = Duration(1.0);
|
||||
|
||||
private:
|
||||
void SetupHeaders();
|
||||
void Get();
|
||||
void Post();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionHttpEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionHttpEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionHttp> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionHttpEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionHttp>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DataChanged();
|
||||
void URLChanged();
|
||||
void MethodChanged(int);
|
||||
void TimeoutChanged(const Duration &seconds);
|
||||
void SetHeadersChanged(int);
|
||||
void HeadersChanged(const StringList &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionHttp> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
VariableLineEdit *_url;
|
||||
QComboBox *_methods;
|
||||
VariableTextEdit *_data;
|
||||
QCheckBox *_setHeaders;
|
||||
QVBoxLayout *_headerListLayout;
|
||||
StringListEdit *_headerList;
|
||||
DurationSelection *_timeout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
348
plugins/base/macro-action-media.cpp
Normal file
348
plugins/base/macro-action-media.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
#include "macro-action-media.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionMedia::id = "media";
|
||||
|
||||
bool MacroActionMedia::_registered = MacroActionFactory::Register(
|
||||
MacroActionMedia::id,
|
||||
{MacroActionMedia::Create, MacroActionMediaEdit::Create,
|
||||
"AdvSceneSwitcher.action.media"});
|
||||
|
||||
static const std::map<MacroActionMedia::Action, std::string> actionTypes = {
|
||||
{MacroActionMedia::Action::PLAY,
|
||||
"AdvSceneSwitcher.action.media.type.play"},
|
||||
{MacroActionMedia::Action::PAUSE,
|
||||
"AdvSceneSwitcher.action.media.type.pause"},
|
||||
{MacroActionMedia::Action::STOP,
|
||||
"AdvSceneSwitcher.action.media.type.stop"},
|
||||
{MacroActionMedia::Action::RESTART,
|
||||
"AdvSceneSwitcher.action.media.type.restart"},
|
||||
{MacroActionMedia::Action::NEXT,
|
||||
"AdvSceneSwitcher.action.media.type.next"},
|
||||
{MacroActionMedia::Action::PREVIOUS,
|
||||
"AdvSceneSwitcher.action.media.type.previous"},
|
||||
{MacroActionMedia::Action::SEEK_DURATION,
|
||||
"AdvSceneSwitcher.action.media.type.seek.duration"},
|
||||
{MacroActionMedia::Action::SEEK_PERCENTAGE,
|
||||
"AdvSceneSwitcher.action.media.type.seek.percentage"},
|
||||
};
|
||||
|
||||
static const std::map<MacroActionMedia::SelectionType, std::string>
|
||||
selectionTypes = {
|
||||
{MacroActionMedia::SelectionType::SOURCE,
|
||||
"AdvSceneSwitcher.action.media.selectionType.source"},
|
||||
{MacroActionMedia::SelectionType::SCENE_ITEM,
|
||||
"AdvSceneSwitcher.action.media.selectionType.sceneItem"},
|
||||
};
|
||||
|
||||
std::string MacroActionMedia::GetShortDesc() const
|
||||
{
|
||||
if (_selection == SelectionType::SOURCE) {
|
||||
return _mediaSource.ToString();
|
||||
}
|
||||
return _scene.ToString() + " - " + _sceneItem.ToString();
|
||||
}
|
||||
|
||||
void MacroActionMedia::SeekToPercentage(obs_source_t *source) const
|
||||
{
|
||||
auto totalTimeMs = obs_source_media_get_duration(source);
|
||||
auto percentageTimeMs = round(totalTimeMs * _seekPercentage / 100);
|
||||
|
||||
obs_source_media_set_time(source, percentageTimeMs);
|
||||
}
|
||||
|
||||
void MacroActionMedia::PerformActionHelper(obs_source_t *source) const
|
||||
{
|
||||
obs_media_state state = obs_source_media_get_state(source);
|
||||
|
||||
switch (_action) {
|
||||
case Action::PLAY:
|
||||
if (state == OBS_MEDIA_STATE_STOPPED ||
|
||||
state == OBS_MEDIA_STATE_ENDED) {
|
||||
obs_source_media_restart(source);
|
||||
} else {
|
||||
obs_source_media_play_pause(source, false);
|
||||
}
|
||||
break;
|
||||
case Action::PAUSE:
|
||||
obs_source_media_play_pause(source, true);
|
||||
break;
|
||||
case Action::STOP:
|
||||
obs_source_media_stop(source);
|
||||
break;
|
||||
case Action::RESTART:
|
||||
obs_source_media_restart(source);
|
||||
break;
|
||||
case Action::NEXT:
|
||||
obs_source_media_next(source);
|
||||
break;
|
||||
case Action::PREVIOUS:
|
||||
obs_source_media_previous(source);
|
||||
break;
|
||||
case Action::SEEK_DURATION:
|
||||
obs_source_media_set_time(source, _seekDuration.Milliseconds());
|
||||
break;
|
||||
case Action::SEEK_PERCENTAGE:
|
||||
SeekToPercentage(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionMedia::PerformAction()
|
||||
{
|
||||
if (_selection == SelectionType::SOURCE) {
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_mediaSource.GetSource());
|
||||
PerformActionHelper(source);
|
||||
} else {
|
||||
const auto items = _sceneItem.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
PerformActionHelper(obs_sceneitem_get_source(item));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionMedia::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\" for source \"%s\"",
|
||||
it->second.c_str(),
|
||||
_selection == SelectionType::SOURCE
|
||||
? _mediaSource.ToString(true).c_str()
|
||||
: _sceneItem.ToString().c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown media action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionMedia::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "selectionType", static_cast<int>(_selection));
|
||||
_seekDuration.Save(obj);
|
||||
_seekPercentage.Save(obj, "seekPercentage");
|
||||
_mediaSource.Save(obj, "mediaSource");
|
||||
_scene.Save(obj);
|
||||
_sceneItem.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionMedia::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_selection = static_cast<SelectionType>(
|
||||
obs_data_get_int(obj, "selectionType"));
|
||||
_seekDuration.Load(obj);
|
||||
_seekPercentage.Load(obj, "seekPercentage");
|
||||
_mediaSource.Load(obj, "mediaSource");
|
||||
_scene.Load(obj);
|
||||
_sceneItem.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateSelectionTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : selectionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionMediaEdit::MacroActionMediaEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionMedia> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_selectionTypes(new QComboBox()),
|
||||
_seekDuration(new DurationSelection()),
|
||||
_seekPercentage(new SliderSpinBox(
|
||||
0, 100,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.media.seek.percentage.label"))),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_sceneItems(new SceneItemSelectionWidget(parent, false)),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, true, true, true))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
populateSelectionTypeSelection(_selectionTypes);
|
||||
auto sources = GetMediaSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_selectionTypes, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(SelectionTypeChanged(int)));
|
||||
QWidget::connect(_seekDuration,
|
||||
SIGNAL(DurationChanged(const Duration &)), this,
|
||||
SLOT(SeekDurationChanged(const Duration &)));
|
||||
QWidget::connect(
|
||||
_seekPercentage,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(SeekPercentageChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_sceneItems,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sceneItems,
|
||||
SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.media.entry"),
|
||||
layout,
|
||||
{{"{{actions}}", _actions},
|
||||
{"{{selectionTypes}}", _selectionTypes},
|
||||
{"{{seekDuration}}", _seekDuration},
|
||||
{"{{seekPercentage}}", _seekPercentage},
|
||||
{"{{mediaSources}}", _sources},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sceneItems}}", _sceneItems}});
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionMedia::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SelectionTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_selection =
|
||||
static_cast<MacroActionMedia::SelectionType>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SeekDurationChanged(const Duration &seekDuration)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_seekDuration = seekDuration;
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SeekPercentageChanged(
|
||||
const NumberVariable<double> &seekPercentage)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_seekPercentage = seekPercentage;
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_mediaSource = source;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sceneItem = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SceneChanged(const SceneSelection &scene)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = scene;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sources->setVisible(_entryData->_selection ==
|
||||
MacroActionMedia::SelectionType::SOURCE);
|
||||
_scenes->setVisible(_entryData->_selection ==
|
||||
MacroActionMedia::SelectionType::SCENE_ITEM);
|
||||
_sceneItems->setVisible(_entryData->_selection ==
|
||||
MacroActionMedia::SelectionType::SCENE_ITEM);
|
||||
|
||||
_seekDuration->setVisible(_entryData->_action ==
|
||||
MacroActionMedia::Action::SEEK_DURATION);
|
||||
_seekPercentage->setVisible(_entryData->_action ==
|
||||
MacroActionMedia::Action::SEEK_PERCENTAGE);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_selectionTypes->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_selection));
|
||||
_seekDuration->SetDuration(_entryData->_seekDuration);
|
||||
_seekPercentage->SetDoubleValue(_entryData->_seekPercentage);
|
||||
_sources->SetSource(_entryData->_mediaSource);
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sceneItems->SetSceneItem((_entryData->_sceneItem));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
99
plugins/base/macro-action-media.hpp
Normal file
99
plugins/base/macro-action-media.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "slider-spinbox.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionMedia : public MacroAction {
|
||||
public:
|
||||
MacroActionMedia(Macro *m) : MacroAction(m) {}
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionMedia>(m);
|
||||
}
|
||||
std::string GetId() const { return id; };
|
||||
std::string GetShortDesc() const;
|
||||
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
enum class Action {
|
||||
PLAY,
|
||||
PAUSE,
|
||||
STOP,
|
||||
RESTART,
|
||||
NEXT,
|
||||
PREVIOUS,
|
||||
SEEK_DURATION,
|
||||
SEEK_PERCENTAGE,
|
||||
};
|
||||
|
||||
enum class SelectionType { SOURCE, SCENE_ITEM };
|
||||
|
||||
Action _action = Action::PLAY;
|
||||
SelectionType _selection = SelectionType::SOURCE;
|
||||
Duration _seekDuration;
|
||||
DoubleVariable _seekPercentage = 50;
|
||||
SourceSelection _mediaSource;
|
||||
SceneItemSelection _sceneItem;
|
||||
SceneSelection _scene;
|
||||
|
||||
private:
|
||||
void PerformActionHelper(obs_source_t *) const;
|
||||
void SeekToPercentage(obs_source_t *source) const;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionMediaEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionMediaEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionMedia> entryData = nullptr);
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionMediaEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionMedia>(action));
|
||||
}
|
||||
void UpdateEntryData();
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void SelectionTypeChanged(int value);
|
||||
void SeekDurationChanged(const Duration &seekDuration);
|
||||
void
|
||||
SeekPercentageChanged(const NumberVariable<double> &seekPercentage);
|
||||
void SourceChanged(const SourceSelection &source);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_actions;
|
||||
QComboBox *_selectionTypes;
|
||||
DurationSelection *_seekDuration;
|
||||
SliderSpinBox *_seekPercentage;
|
||||
SourceSelectionWidget *_sources;
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sceneItems;
|
||||
|
||||
std::shared_ptr<MacroActionMedia> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
318
plugins/base/macro-action-osc.cpp
Normal file
318
plugins/base/macro-action-osc.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
#include "macro-action-osc.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <system_error>
|
||||
#include <QGroupBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionOSC::id = "osc";
|
||||
|
||||
bool MacroActionOSC::_registered = MacroActionFactory::Register(
|
||||
MacroActionOSC::id, {MacroActionOSC::Create, MacroActionOSCEdit::Create,
|
||||
"AdvSceneSwitcher.action.osc"});
|
||||
|
||||
MacroActionOSC::MacroActionOSC(Macro *m)
|
||||
: MacroAction(m),
|
||||
_ctx(asio::io_context()),
|
||||
_tcpSocket(asio::ip::tcp::socket(_ctx)),
|
||||
_udpSocket(asio::ip::udp::socket(_ctx))
|
||||
{
|
||||
}
|
||||
|
||||
void MacroActionOSC::SendOSCTCPMessage(const asio::mutable_buffer &buffer)
|
||||
{
|
||||
try {
|
||||
asio::write(_tcpSocket, asio::buffer(buffer));
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to send OSC message \"%s\" via TCP %s %d: %s",
|
||||
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::SendOSCUDPMessage(const asio::mutable_buffer &buffer)
|
||||
{
|
||||
try {
|
||||
_udpSocket.send_to(buffer, _updEndpoint);
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to send OSC message \"%s\" via UDP %s %d: %s",
|
||||
_message.ToString().c_str(), _ip.c_str(), _port.GetValue(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::UDPReconnect()
|
||||
{
|
||||
asio::error_code ec;
|
||||
asio::ip::udp::resolver resolver(_ctx);
|
||||
auto endpoints = resolver.resolve(asio::ip::udp::v4(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()), ec);
|
||||
if (ec) {
|
||||
endpoints = resolver.resolve(asio::ip::udp::v6(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()),
|
||||
ec);
|
||||
}
|
||||
if (ec) {
|
||||
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
|
||||
_ip.c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_updEndpoint = endpoints.begin()->endpoint();
|
||||
_udpSocket = asio::ip::udp::socket(_ctx);
|
||||
_udpSocket.open(endpoints.begin()->endpoint().protocol());
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING, "failed to connect to UDP %s %d: %s",
|
||||
_ip.c_str(), _port.GetValue(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::TCPReconnect()
|
||||
{
|
||||
asio::error_code ec;
|
||||
asio::ip::tcp::resolver resolver(_ctx);
|
||||
auto endpoints = resolver.resolve(asio::ip::tcp::v4(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()), ec);
|
||||
if (ec) {
|
||||
endpoints = resolver.resolve(asio::ip::tcp::v6(), _ip.c_str(),
|
||||
std::to_string(_port.GetValue()),
|
||||
ec);
|
||||
}
|
||||
if (ec) {
|
||||
blog(LOG_WARNING, "failed to get IP for \"%s\": %s",
|
||||
_ip.c_str(), ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_tcpSocket = asio::ip::tcp::socket(_ctx);
|
||||
_tcpSocket.connect(endpoints.begin()->endpoint());
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING, "failed to connect to TCP %s %d: %s",
|
||||
_ip.c_str(), _port.GetValue(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionOSC::CheckReconnect()
|
||||
{
|
||||
if (_protocol == Protocol::TCP &&
|
||||
(_reconnect || !_tcpSocket.is_open())) {
|
||||
TCPReconnect();
|
||||
}
|
||||
|
||||
if (_protocol == Protocol::UDP &&
|
||||
(_reconnect || !_udpSocket.is_open())) {
|
||||
UDPReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionOSC::PerformAction()
|
||||
{
|
||||
auto buffer = _message.GetBuffer();
|
||||
if (!buffer.has_value()) {
|
||||
blog(LOG_WARNING, "failed to create or fill OSC buffer!");
|
||||
return true;
|
||||
}
|
||||
|
||||
CheckReconnect();
|
||||
|
||||
if (_protocol == Protocol::TCP &&
|
||||
(_reconnect || !_tcpSocket.is_open())) {
|
||||
TCPReconnect();
|
||||
}
|
||||
|
||||
if (_protocol == Protocol::UDP &&
|
||||
(_reconnect || !_udpSocket.is_open())) {
|
||||
UDPReconnect();
|
||||
}
|
||||
|
||||
auto rawMessage = asio::buffer(*buffer);
|
||||
|
||||
switch (_protocol) {
|
||||
case MacroActionOSC::Protocol::TCP:
|
||||
SendOSCTCPMessage(rawMessage);
|
||||
break;
|
||||
case MacroActionOSC::Protocol::UDP:
|
||||
SendOSCUDPMessage(rawMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "sending OSC message '%s' to %s %s %d",
|
||||
_message.ToString().c_str(),
|
||||
_protocol == Protocol::UDP ? "UDP" : "TCP", _ip.c_str(),
|
||||
_port.GetValue());
|
||||
}
|
||||
|
||||
bool MacroActionOSC::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "protocol", static_cast<int>(_protocol));
|
||||
_ip.Save(obj, "ip");
|
||||
_port.Save(obj, "port");
|
||||
_message.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionOSC::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_protocol = static_cast<Protocol>(obs_data_get_int(obj, "protocol"));
|
||||
_ip.Load(obj, "ip");
|
||||
_port.Load(obj, "port");
|
||||
_message.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetProtocol(Protocol p)
|
||||
{
|
||||
_protocol = p;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetIP(const std::string &ip)
|
||||
{
|
||||
_ip = ip;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
void MacroActionOSC::SetPortNr(IntVariable port)
|
||||
{
|
||||
_port = port;
|
||||
_reconnect = true;
|
||||
}
|
||||
|
||||
static void populateProtocolSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem("TCP");
|
||||
list->addItem("UDP");
|
||||
}
|
||||
|
||||
MacroActionOSCEdit::MacroActionOSCEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionOSC> entryData)
|
||||
: QWidget(parent),
|
||||
_protocol(new QComboBox(this)),
|
||||
_ip(new VariableLineEdit(this)),
|
||||
_port(new VariableSpinBox(this)),
|
||||
_message(new OSCMessageEdit(this))
|
||||
{
|
||||
populateProtocolSelection(_protocol);
|
||||
_port->setMaximum(65535);
|
||||
|
||||
auto networkGroup =
|
||||
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.network"));
|
||||
auto networkLayout = new QGridLayout;
|
||||
int row = 0;
|
||||
networkLayout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.protocol")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_protocol, row, 1);
|
||||
++row;
|
||||
networkLayout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.address")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_ip, row, 1);
|
||||
++row;
|
||||
networkLayout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.osc.network.port")),
|
||||
row, 0);
|
||||
networkLayout->addWidget(_port, row, 1);
|
||||
networkGroup->setLayout(networkLayout);
|
||||
|
||||
auto messageGroup =
|
||||
new QGroupBox(obs_module_text("AdvSceneSwitcher.osc.message"));
|
||||
auto messageLayout = new QHBoxLayout();
|
||||
messageLayout->addWidget(_message);
|
||||
messageGroup->setLayout(messageLayout);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(networkGroup);
|
||||
mainLayout->addWidget(messageGroup);
|
||||
setLayout(mainLayout);
|
||||
|
||||
QWidget::connect(_ip, SIGNAL(editingFinished()), this,
|
||||
SLOT(IpChanged()));
|
||||
QWidget::connect(_protocol, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ProtocolChanged(int)));
|
||||
QWidget::connect(
|
||||
_port,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(PortChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_message, SIGNAL(MessageChanged(const OSCMessage &)),
|
||||
this, SLOT(MessageChanged(const OSCMessage &)));
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_protocol->setCurrentIndex(static_cast<int>(_entryData->GetProtocol()));
|
||||
_ip->setText(_entryData->GetIP());
|
||||
_port->SetValue(_entryData->GetPortNr());
|
||||
_message->SetMessage(_entryData->_message);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::IpChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetIP(_ip->text().toStdString());
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::ProtocolChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetProtocol(static_cast<MacroActionOSC::Protocol>(value));
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::PortChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetPortNr(value);
|
||||
}
|
||||
|
||||
void MacroActionOSCEdit::MessageChanged(const OSCMessage &m)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = m;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
94
plugins/base/macro-action-osc.hpp
Normal file
94
plugins/base/macro-action-osc.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "osc-helpers.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <asio.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionOSC : public MacroAction {
|
||||
public:
|
||||
MacroActionOSC(Macro *m);
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionOSC>(m);
|
||||
}
|
||||
|
||||
enum class Protocol {
|
||||
TCP,
|
||||
UDP,
|
||||
};
|
||||
|
||||
void SetProtocol(Protocol);
|
||||
Protocol GetProtocol() const { return _protocol; }
|
||||
void SetIP(const std::string &);
|
||||
StringVariable GetIP() const { return _ip; }
|
||||
void SetPortNr(IntVariable);
|
||||
IntVariable GetPortNr() { return _port; }
|
||||
|
||||
OSCMessage _message;
|
||||
|
||||
private:
|
||||
void SendOSCTCPMessage(const asio::mutable_buffer &);
|
||||
void SendOSCUDPMessage(const asio::mutable_buffer &);
|
||||
|
||||
void CheckReconnect();
|
||||
void TCPReconnect();
|
||||
void UDPReconnect();
|
||||
|
||||
Protocol _protocol = Protocol::UDP;
|
||||
StringVariable _ip = "localhost";
|
||||
IntVariable _port = 12345;
|
||||
bool _reconnect = true;
|
||||
|
||||
asio::io_context _ctx;
|
||||
asio::ip::tcp::socket _tcpSocket;
|
||||
asio::ip::udp::socket _udpSocket;
|
||||
asio::ip::udp::endpoint _updEndpoint;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionOSCEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionOSCEdit(QWidget *parent,
|
||||
std::shared_ptr<MacroActionOSC> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionOSCEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionOSC>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void IpChanged();
|
||||
void MessageChanged(const OSCMessage &);
|
||||
void ProtocolChanged(int);
|
||||
void PortChanged(const NumberVariable<int> &value);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionOSC> _entryData;
|
||||
|
||||
private:
|
||||
QComboBox *_protocol;
|
||||
VariableLineEdit *_ip;
|
||||
VariableSpinBox *_port;
|
||||
OSCMessageEdit *_message;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
358
plugins/base/macro-action-plugin-state.cpp
Normal file
358
plugins/base/macro-action-plugin-state.cpp
Normal file
@@ -0,0 +1,358 @@
|
||||
#include "macro-action-plugin-state.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <QMainWindow>
|
||||
|
||||
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<PluginStateAction, std::string> 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<NoMatchBehavior, std::string> 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<NoMatchBehavior>(value));
|
||||
if (GetPluginNoMatchBehavior() == NoMatchBehavior::SWITCH) {
|
||||
SetNoMatchScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
static void closeOBSWindow()
|
||||
{
|
||||
blog(LOG_WARNING, "closing OBS window now!");
|
||||
auto obsWindow =
|
||||
static_cast<QMainWindow *>(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<std::mutex> 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<std::mutex> 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:
|
||||
vblog(LOG_INFO, "setting no match to %d", _value);
|
||||
break;
|
||||
case PluginStateAction::IMPORT_SETTINGS:
|
||||
vblog(LOG_INFO, "importing settings from %s",
|
||||
_settingsPath.c_str());
|
||||
break;
|
||||
case PluginStateAction::TERMINATE:
|
||||
vblog(LOG_INFO, "sending terminate signal to OBS in 10s");
|
||||
break;
|
||||
default:
|
||||
blog(LOG_WARNING, "ignored unknown pluginState action %d",
|
||||
static_cast<int>(_action));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionPluginState::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_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<PluginStateAction>(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;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateValueSelection(QComboBox *list,
|
||||
PluginStateAction action)
|
||||
{
|
||||
if (action == PluginStateAction::NO_MATCH_BEHAVIOUR) {
|
||||
for (auto entry : noMatchValues) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionPluginStateEdit::MacroActionPluginStateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionPluginState> 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<std::string, QWidget *> 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<int>(_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<PluginStateAction>(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<NoMatchBehavior>(_entryData->_value) ==
|
||||
NoMatchBehavior::SWITCH) {
|
||||
_scenes->show();
|
||||
}
|
||||
break;
|
||||
case PluginStateAction::IMPORT_SETTINGS:
|
||||
_settings->show();
|
||||
_settingsWarning->show();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
76
plugins/base/macro-action-plugin-state.hpp
Normal file
76
plugins/base/macro-action-plugin-state.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLabel>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class PluginStateAction {
|
||||
STOP,
|
||||
NO_MATCH_BEHAVIOUR,
|
||||
IMPORT_SETTINGS,
|
||||
TERMINATE,
|
||||
};
|
||||
|
||||
class MacroActionPluginState : public MacroAction {
|
||||
public:
|
||||
MacroActionPluginState(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionPluginState>(m);
|
||||
}
|
||||
|
||||
PluginStateAction _action = PluginStateAction::STOP;
|
||||
int _value = 0;
|
||||
StringVariable _settingsPath;
|
||||
OBSWeakSource _scene;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionPluginStateEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionPluginStateEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionPluginState> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionPluginStateEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionPluginState>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void ValueChanged(int value);
|
||||
void SceneChanged(const QString &text);
|
||||
void PathChanged(const QString &text);
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
QComboBox *_values;
|
||||
QComboBox *_scenes;
|
||||
FileSelection *_settings;
|
||||
QLabel *_settingsWarning;
|
||||
std::shared_ptr<MacroActionPluginState> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
87
plugins/base/macro-action-profile.cpp
Normal file
87
plugins/base/macro-action-profile.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "macro-action-profile.hpp"
|
||||
#include "profile-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionProfile::id = "profile";
|
||||
|
||||
bool MacroActionProfile::_registered = MacroActionFactory::Register(
|
||||
MacroActionProfile::id,
|
||||
{MacroActionProfile::Create, MacroActionProfileEdit::Create,
|
||||
"AdvSceneSwitcher.action.profile"});
|
||||
|
||||
bool MacroActionProfile::PerformAction()
|
||||
{
|
||||
obs_frontend_set_current_profile(_profile.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionProfile::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "set profile type to \"%s\"", _profile.c_str());
|
||||
}
|
||||
|
||||
bool MacroActionProfile::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_string(obj, "profile", _profile.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionProfile::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_profile = obs_data_get_string(obj, "profile");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionProfile::GetShortDesc() const
|
||||
{
|
||||
return _profile;
|
||||
}
|
||||
|
||||
MacroActionProfileEdit::MacroActionProfileEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionProfile> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_profiles = new QComboBox();
|
||||
PopulateProfileSelection(_profiles);
|
||||
QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(ProfileChanged(const QString &)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{profiles}}", _profiles},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.profile.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionProfileEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_profiles->setCurrentText(QString::fromStdString(_entryData->_profile));
|
||||
}
|
||||
|
||||
void MacroActionProfileEdit::ProfileChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_profile = text.toStdString();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
58
plugins/base/macro-action-profile.hpp
Normal file
58
plugins/base/macro-action-profile.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionProfile : public MacroAction {
|
||||
public:
|
||||
MacroActionProfile(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionProfile>(m);
|
||||
}
|
||||
|
||||
std::string _profile;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionProfileEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionProfileEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionProfile> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionProfileEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionProfile>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ProfileChanged(const QString &text);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_profiles;
|
||||
std::shared_ptr<MacroActionProfile> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
299
plugins/base/macro-action-projector.cpp
Normal file
299
plugins/base/macro-action-projector.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "macro-action-projector.hpp"
|
||||
#include "monitor-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionProjector::id = "projector";
|
||||
|
||||
bool MacroActionProjector::_registered = MacroActionFactory::Register(
|
||||
MacroActionProjector::id,
|
||||
{MacroActionProjector::Create, MacroActionProjectorEdit::Create,
|
||||
"AdvSceneSwitcher.action.projector"});
|
||||
|
||||
const static std::map<MacroActionProjector::Type, std::string> selectionTypes = {
|
||||
{MacroActionProjector::Type::SOURCE,
|
||||
"AdvSceneSwitcher.action.projector.type.source"},
|
||||
{MacroActionProjector::Type::SCENE,
|
||||
"AdvSceneSwitcher.action.projector.type.scene"},
|
||||
{MacroActionProjector::Type::PREVIEW,
|
||||
"AdvSceneSwitcher.action.projector.type.preview"},
|
||||
{MacroActionProjector::Type::PROGRAM,
|
||||
"AdvSceneSwitcher.action.projector.type.program"},
|
||||
{MacroActionProjector::Type::MULTIVIEW,
|
||||
"AdvSceneSwitcher.action.projector.type.multiview"},
|
||||
};
|
||||
|
||||
bool MacroActionProjector::PerformAction()
|
||||
{
|
||||
std::string name = "";
|
||||
const char *type = "";
|
||||
|
||||
switch (_type) {
|
||||
case MacroActionProjector::Type::SOURCE:
|
||||
type = "Source";
|
||||
name = GetWeakSourceName(_source.GetSource());
|
||||
if (name.empty()) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MacroActionProjector::Type::SCENE:
|
||||
type = "Scene";
|
||||
name = GetWeakSourceName(_scene.GetScene());
|
||||
if (name.empty()) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MacroActionProjector::Type::PREVIEW:
|
||||
type = "Preview";
|
||||
break;
|
||||
case MacroActionProjector::Type::PROGRAM:
|
||||
type = "StudioProgram";
|
||||
break;
|
||||
case MacroActionProjector::Type::MULTIVIEW:
|
||||
type = "Multiview";
|
||||
break;
|
||||
}
|
||||
|
||||
if (_fullscreen && _monitor == -1) {
|
||||
blog(LOG_INFO, "refusing to open fullscreen projector"
|
||||
" with invalid display selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_frontend_open_projector(type, _fullscreen ? _monitor : -1, "",
|
||||
name.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionProjector::LogAction() const
|
||||
{
|
||||
auto it = selectionTypes.find(_type);
|
||||
if (it != selectionTypes.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"performed projector action \"%s\" with"
|
||||
"source \"%s\","
|
||||
"scene \"%s\","
|
||||
"monitor %d",
|
||||
it->second.c_str(), _source.ToString(true).c_str(),
|
||||
_scene.ToString(true).c_str(), _monitor);
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown projector action %d",
|
||||
static_cast<int>(_type));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionProjector::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionProjector::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_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);
|
||||
|
||||
if (MonitorSetupChanged()) {
|
||||
blog(LOG_INFO, "monitor setup seems to have changed! "
|
||||
"resetting projector action monitor selection!");
|
||||
_monitor = -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionProjector::SetMonitor(int idx)
|
||||
{
|
||||
_monitor = idx;
|
||||
auto monitorNames = GetMonitorNames();
|
||||
if (_monitor < 0 || _monitor >= monitorNames.size()) {
|
||||
// Monitor setup changed while settings were selected?
|
||||
_monitorName = "";
|
||||
return;
|
||||
}
|
||||
_monitorName = monitorNames.at(_monitor).toStdString();
|
||||
}
|
||||
|
||||
int MacroActionProjector::GetMonitor() const
|
||||
{
|
||||
return _monitor;
|
||||
}
|
||||
|
||||
bool MacroActionProjector::MonitorSetupChanged()
|
||||
{
|
||||
if (_monitorName.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto monitorNames = GetMonitorNames();
|
||||
if (_monitor < 0 || _monitor >= monitorNames.size()) {
|
||||
return true;
|
||||
}
|
||||
return monitorNames.at(_monitor) !=
|
||||
QString::fromStdString(_monitorName);
|
||||
}
|
||||
|
||||
static inline void populateSelectionTypes(QComboBox *list)
|
||||
{
|
||||
for (auto entry : selectionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateWindowTypes(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.action.projector.windowed"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.projector.fullscreen"));
|
||||
}
|
||||
|
||||
MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionProjector> entryData)
|
||||
: QWidget(parent),
|
||||
_windowTypes(new QComboBox()),
|
||||
_types(new QComboBox()),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, true, true,
|
||||
true)),
|
||||
_sources(new SourceSelectionWidget(window(), QStringList(), true)),
|
||||
_monitorSelection(new QHBoxLayout()),
|
||||
_monitors(new QComboBox())
|
||||
{
|
||||
populateWindowTypes(_windowTypes);
|
||||
populateSelectionTypes(_types);
|
||||
auto sources = GetSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
_monitors->addItems(GetMonitorNames());
|
||||
_monitors->setPlaceholderText(
|
||||
obs_module_text("AdvSceneSwitcher.selectDisplay"));
|
||||
|
||||
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(WindowTypeChanged(int)));
|
||||
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
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);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_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());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = source;
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::MonitorChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetMonitor(value);
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::WindowTypeChanged(int)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_fullscreen =
|
||||
_windowTypes->currentText() ==
|
||||
obs_module_text("AdvSceneSwitcher.action.projector.fullscreen");
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::TypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_type = static_cast<MacroActionProjector::Type>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scenes->setVisible(_entryData->_type ==
|
||||
MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(_entryData->_type ==
|
||||
MacroActionProjector::Type::SOURCE);
|
||||
SetLayoutVisible(_monitorSelection, _entryData->_fullscreen);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
84
plugins/base/macro-action-projector.hpp
Normal file
84
plugins/base/macro-action-projector.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "source-selection.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionProjector : public MacroAction {
|
||||
public:
|
||||
MacroActionProjector(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionProjector>(m);
|
||||
}
|
||||
void SetMonitor(int);
|
||||
int GetMonitor() const;
|
||||
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
SCENE,
|
||||
PREVIEW,
|
||||
PROGRAM,
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
Type _type = Type::SCENE;
|
||||
SourceSelection _source;
|
||||
SceneSelection _scene;
|
||||
bool _fullscreen = true;
|
||||
|
||||
private:
|
||||
bool MonitorSetupChanged();
|
||||
|
||||
int _monitor = -1;
|
||||
// Only used to detect display setup changes
|
||||
std::string _monitorName = "";
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionProjectorEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionProjectorEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionProjector> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionProjectorEdit(
|
||||
parent, std::dynamic_pointer_cast<MacroActionProjector>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void WindowTypeChanged(int value);
|
||||
void TypeChanged(int value);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void MonitorChanged(int value);
|
||||
|
||||
protected:
|
||||
QComboBox *_windowTypes;
|
||||
QComboBox *_types;
|
||||
SceneSelectionWidget *_scenes;
|
||||
SourceSelectionWidget *_sources;
|
||||
QHBoxLayout *_monitorSelection;
|
||||
QComboBox *_monitors;
|
||||
std::shared_ptr<MacroActionProjector> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
209
plugins/base/macro-action-random.cpp
Normal file
209
plugins/base/macro-action-random.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "macro-action-random.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionRandom::id = "random";
|
||||
|
||||
bool MacroActionRandom::_registered = MacroActionFactory::Register(
|
||||
MacroActionRandom::id,
|
||||
{MacroActionRandom::Create, MacroActionRandomEdit::Create,
|
||||
"AdvSceneSwitcher.action.random"});
|
||||
|
||||
static bool validNextMacro(const std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
return !MacroIsPaused(macro.get());
|
||||
}
|
||||
|
||||
static std::vector<std::shared_ptr<Macro>>
|
||||
getNextMacros(std::vector<MacroRef> ¯os, MacroRef &lastRandomMacroRef,
|
||||
bool allowRepeat)
|
||||
{
|
||||
std::vector<std::shared_ptr<Macro>> res;
|
||||
if (macros.size() == 1) {
|
||||
auto macro = macros[0].GetMacro();
|
||||
if (validNextMacro(macro)) {
|
||||
res.push_back(macro);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auto lastRandomMacro = lastRandomMacroRef.GetMacro();
|
||||
for (auto &m : macros) {
|
||||
auto macro = m.GetMacro();
|
||||
if (validNextMacro(macro) &&
|
||||
(allowRepeat || (lastRandomMacro != macro))) {
|
||||
res.push_back(macro);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool MacroActionRandom::PerformAction()
|
||||
{
|
||||
if (_macros.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto macros = getNextMacros(_macros, lastRandomMacro, _allowRepeat);
|
||||
if (macros.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
if (macros.size() == 1) {
|
||||
lastRandomMacro = macros[0];
|
||||
return RunMacroActions(macros[0].get());
|
||||
}
|
||||
srand((unsigned int)time(0));
|
||||
size_t idx = std::rand() % (macros.size());
|
||||
lastRandomMacro = macros[idx];
|
||||
return RunMacroActions(macros[idx].get());
|
||||
}
|
||||
|
||||
void MacroActionRandom::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "running random macro");
|
||||
}
|
||||
|
||||
bool MacroActionRandom::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
SaveMacroList(obj, _macros);
|
||||
obs_data_set_bool(obj, "allowRepeat", _allowRepeat);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionRandom::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
LoadMacroList(obj, _macros);
|
||||
_allowRepeat = obs_data_get_bool(obj, "allowRepeat");
|
||||
return true;
|
||||
}
|
||||
|
||||
MacroActionRandomEdit::MacroActionRandomEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionRandom> entryData)
|
||||
: QWidget(parent),
|
||||
_list(new MacroList(this, true, false)),
|
||||
_allowRepeat(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.random.allowRepeat")))
|
||||
{
|
||||
QWidget::connect(_list, SIGNAL(Added(const std::string &)), this,
|
||||
SLOT(Add(const std::string &)));
|
||||
QWidget::connect(_list, SIGNAL(Removed(int)), this, SLOT(Remove(int)));
|
||||
QWidget::connect(_list, SIGNAL(Replaced(int, const std::string &)),
|
||||
this, SLOT(Replace(int, const std::string &)));
|
||||
QWidget::connect(window(), SIGNAL(MacroRemoved(const QString &)), this,
|
||||
SLOT(MacroRemove(const QString &)));
|
||||
QWidget::connect(_allowRepeat, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(AllowRepeatChanged(int)));
|
||||
|
||||
auto *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.random.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_list);
|
||||
mainLayout->addWidget(_allowRepeat);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_list->SetContent(_entryData->_macros);
|
||||
_allowRepeat->setChecked(_entryData->_allowRepeat);
|
||||
_allowRepeat->setVisible(ShouldShowAllowRepeat());
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::MacroRemove(const QString &)
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = _entryData->_macros.begin();
|
||||
while (it != _entryData->_macros.end()) {
|
||||
if (!it->GetMacro()) {
|
||||
it = _entryData->_macros.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::Add(const std::string &name)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
MacroRef macro(name);
|
||||
_entryData->_macros.push_back(macro);
|
||||
_allowRepeat->setVisible(ShouldShowAllowRepeat());
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::Remove(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_macros.erase(std::next(_entryData->_macros.begin(), idx));
|
||||
_allowRepeat->setVisible(ShouldShowAllowRepeat());
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::Replace(int idx, const std::string &name)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
MacroRef macro(name);
|
||||
auto lock = LockContext();
|
||||
_entryData->_macros[idx] = macro;
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionRandomEdit::AllowRepeatChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_allowRepeat = value;
|
||||
}
|
||||
|
||||
bool MacroActionRandomEdit::ShouldShowAllowRepeat()
|
||||
{
|
||||
if (_entryData->_macros.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
const auto macro = _entryData->_macros[0].GetMacro();
|
||||
for (const auto &m : _entryData->_macros) {
|
||||
if (macro != m.GetMacro()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
66
plugins/base/macro-action-random.hpp
Normal file
66
plugins/base/macro-action-random.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "macro-list.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionRandom : public MultiMacroRefAction {
|
||||
public:
|
||||
MacroActionRandom(Macro *m) : MacroAction(m), MultiMacroRefAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionRandom>(m);
|
||||
}
|
||||
|
||||
bool _allowRepeat = false;
|
||||
|
||||
private:
|
||||
MacroRef lastRandomMacro;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionRandomEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionRandomEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionRandom> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionRandomEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionRandom>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void MacroRemove(const QString &name);
|
||||
void Add(const std::string &);
|
||||
void Remove(int);
|
||||
void Replace(int, const std::string &);
|
||||
void AllowRepeatChanged(int);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionRandom> _entryData;
|
||||
|
||||
private:
|
||||
bool ShouldShowAllowRepeat();
|
||||
|
||||
MacroList *_list;
|
||||
QCheckBox *_allowRepeat;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
225
plugins/base/macro-action-recording.cpp
Normal file
225
plugins/base/macro-action-recording.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "macro-action-recording.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <util/config-file.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionRecord::id = "recording";
|
||||
|
||||
bool MacroActionRecord::_registered = MacroActionFactory::Register(
|
||||
MacroActionRecord::id,
|
||||
{MacroActionRecord::Create, MacroActionRecordEdit::Create,
|
||||
"AdvSceneSwitcher.action.recording"});
|
||||
|
||||
const static std::map<MacroActionRecord::Action, std::string> actionTypes = {
|
||||
{MacroActionRecord::Action::STOP,
|
||||
"AdvSceneSwitcher.action.recording.type.stop"},
|
||||
{MacroActionRecord::Action::START,
|
||||
"AdvSceneSwitcher.action.recording.type.start"},
|
||||
{MacroActionRecord::Action::PAUSE,
|
||||
"AdvSceneSwitcher.action.recording.type.pause"},
|
||||
{MacroActionRecord::Action::UNPAUSE,
|
||||
"AdvSceneSwitcher.action.recording.type.unpause"},
|
||||
{MacroActionRecord::Action::SPLIT,
|
||||
"AdvSceneSwitcher.action.recording.type.split"},
|
||||
{MacroActionRecord::Action::FOLDER,
|
||||
"AdvSceneSwitcher.action.recording.type.changeOutputFolder"},
|
||||
{MacroActionRecord::Action::FILE_FORMAT,
|
||||
"AdvSceneSwitcher.action.recording.type.changeOutputFileFormat"},
|
||||
};
|
||||
|
||||
bool MacroActionRecord::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case Action::STOP:
|
||||
if (obs_frontend_recording_active()) {
|
||||
obs_frontend_recording_stop();
|
||||
}
|
||||
break;
|
||||
case Action::START:
|
||||
if (!obs_frontend_recording_active()) {
|
||||
obs_frontend_recording_start();
|
||||
}
|
||||
break;
|
||||
case Action::PAUSE:
|
||||
if (obs_frontend_recording_active() &&
|
||||
!obs_frontend_recording_paused()) {
|
||||
obs_frontend_recording_pause(true);
|
||||
}
|
||||
break;
|
||||
case Action::UNPAUSE:
|
||||
if (obs_frontend_recording_active() &&
|
||||
obs_frontend_recording_paused()) {
|
||||
obs_frontend_recording_pause(false);
|
||||
}
|
||||
break;
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(28, 0, 0)
|
||||
case Action::SPLIT:
|
||||
obs_frontend_recording_split_file();
|
||||
break;
|
||||
#endif
|
||||
case Action::FOLDER: {
|
||||
std::string folder = _folder; // Trigger variable resolve
|
||||
|
||||
auto conf = obs_frontend_get_profile_config();
|
||||
config_set_string(conf, "SimpleOutput", "FilePath",
|
||||
folder.c_str());
|
||||
config_set_string(conf, "AdvOut", "FFFilePath", folder.c_str());
|
||||
config_set_string(conf, "AdvOut", "RecFilePath",
|
||||
folder.c_str());
|
||||
if (config_save(conf) != CONFIG_SUCCESS) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to set recoding output folder");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Action::FILE_FORMAT: {
|
||||
std::string format = _fileFormat; // Trigger variable resolve
|
||||
|
||||
auto conf = obs_frontend_get_profile_config();
|
||||
config_set_string(conf, "Output", "FilenameFormatting",
|
||||
format.c_str());
|
||||
if (config_save(conf) != CONFIG_SUCCESS) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to set recoding file format string");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionRecord::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown recording action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionRecord::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_folder.Save(obj, "folder");
|
||||
_fileFormat.Save(obj, "format");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionRecord::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_folder.Load(obj, "folder");
|
||||
_fileFormat.Load(obj, "format");
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionRecordEdit::MacroActionRecordEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionRecord> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_pauseHint(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.recording.pause.hint"))),
|
||||
_splitHint(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.recording.split.hint"))),
|
||||
_recordFolder(new FileSelection(FileSelection::Type::FOLDER, this)),
|
||||
_recordFileFormat(new VariableLineEdit(this))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_recordFolder, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(FolderChanged(const QString &)));
|
||||
QWidget::connect(_recordFileFormat, SIGNAL(editingFinished()), this,
|
||||
SLOT(FormatStringChanged()));
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.recording.entry"),
|
||||
mainLayout,
|
||||
{{"{{actions}}", _actions},
|
||||
{"{{pauseHint}}", _pauseHint},
|
||||
{"{{splitHint}}", _splitHint},
|
||||
{"{{recordFolder}}", _recordFolder},
|
||||
{"{{recordFileFormat}}", _recordFileFormat}});
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionRecordEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_recordFolder->SetPath(_entryData->_folder);
|
||||
_recordFileFormat->setText(_entryData->_fileFormat);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
static bool isPauseAction(MacroActionRecord::Action a)
|
||||
{
|
||||
return a == MacroActionRecord::Action::PAUSE ||
|
||||
a == MacroActionRecord::Action::UNPAUSE;
|
||||
}
|
||||
|
||||
void MacroActionRecordEdit::FolderChanged(const QString &folder)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_folder = folder.toStdString();
|
||||
}
|
||||
|
||||
void MacroActionRecordEdit::FormatStringChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_fileFormat = _recordFileFormat->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionRecordEdit::SetWidgetVisibility()
|
||||
{
|
||||
_pauseHint->setVisible(isPauseAction(_entryData->_action));
|
||||
_splitHint->setVisible(_entryData->_action ==
|
||||
MacroActionRecord::Action::SPLIT);
|
||||
_recordFolder->setVisible(_entryData->_action ==
|
||||
MacroActionRecord::Action::FOLDER);
|
||||
_recordFileFormat->setVisible(_entryData->_action ==
|
||||
MacroActionRecord::Action::FILE_FORMAT);
|
||||
}
|
||||
|
||||
void MacroActionRecordEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionRecord::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
80
plugins/base/macro-action-recording.hpp
Normal file
80
plugins/base/macro-action-recording.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
#include <QDir>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionRecord : public MacroAction {
|
||||
public:
|
||||
MacroActionRecord(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionRecord>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
STOP,
|
||||
START,
|
||||
PAUSE,
|
||||
UNPAUSE,
|
||||
SPLIT,
|
||||
FOLDER,
|
||||
FILE_FORMAT,
|
||||
};
|
||||
Action _action = Action::STOP;
|
||||
|
||||
StringVariable _folder = QDir::homePath().toStdString() + "/Videos";
|
||||
StringVariable _fileFormat = "%CCYY-%MM-%DD %hh-%mm-%ss";
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionRecordEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionRecordEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionRecord> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionRecordEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionRecord>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void FolderChanged(const QString &);
|
||||
void FormatStringChanged();
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
QLabel *_pauseHint;
|
||||
QLabel *_splitHint;
|
||||
FileSelection *_recordFolder;
|
||||
VariableLineEdit *_recordFileFormat;
|
||||
std::shared_ptr<MacroActionRecord> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
128
plugins/base/macro-action-replay-buffer.cpp
Normal file
128
plugins/base/macro-action-replay-buffer.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "macro-action-replay-buffer.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionReplayBuffer::id = "replay_buffer";
|
||||
|
||||
bool MacroActionReplayBuffer::_registered = MacroActionFactory::Register(
|
||||
MacroActionReplayBuffer::id,
|
||||
{MacroActionReplayBuffer::Create, MacroActionReplayBufferEdit::Create,
|
||||
"AdvSceneSwitcher.action.replay"});
|
||||
|
||||
const static std::map<ReplayBufferAction, std::string> actionTypes = {
|
||||
{ReplayBufferAction::STOP, "AdvSceneSwitcher.action.replay.type.stop"},
|
||||
{ReplayBufferAction::START,
|
||||
"AdvSceneSwitcher.action.replay.type.start"},
|
||||
{ReplayBufferAction::SAVE, "AdvSceneSwitcher.action.replay.type.save"},
|
||||
};
|
||||
|
||||
bool MacroActionReplayBuffer::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case ReplayBufferAction::STOP:
|
||||
if (obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
}
|
||||
break;
|
||||
case ReplayBufferAction::START:
|
||||
if (!obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_start();
|
||||
}
|
||||
break;
|
||||
case ReplayBufferAction::SAVE:
|
||||
if (obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_save();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionReplayBuffer::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown replay buffer action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionReplayBuffer::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionReplayBuffer::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<ReplayBufferAction>(
|
||||
obs_data_get_int(obj, "action"));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionReplayBufferEdit::MacroActionReplayBufferEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionReplayBuffer> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_saveWarning(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.replay.saveWarn")))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{actions}}", _actions},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.replay.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(layout);
|
||||
mainLayout->addWidget(_saveWarning);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionReplayBufferEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_saveWarning->setVisible(_entryData->_action ==
|
||||
ReplayBufferAction::SAVE);
|
||||
}
|
||||
|
||||
void MacroActionReplayBufferEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<ReplayBufferAction>(value);
|
||||
_saveWarning->setVisible(_entryData->_action ==
|
||||
ReplayBufferAction::SAVE);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
64
plugins/base/macro-action-replay-buffer.hpp
Normal file
64
plugins/base/macro-action-replay-buffer.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class ReplayBufferAction {
|
||||
STOP,
|
||||
START,
|
||||
SAVE,
|
||||
};
|
||||
|
||||
class MacroActionReplayBuffer : public MacroAction {
|
||||
public:
|
||||
MacroActionReplayBuffer(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionReplayBuffer>(m);
|
||||
}
|
||||
|
||||
ReplayBufferAction _action = ReplayBufferAction::STOP;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionReplayBufferEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionReplayBufferEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionReplayBuffer> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionReplayBufferEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionReplayBuffer>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
QLabel *_saveWarning;
|
||||
std::shared_ptr<MacroActionReplayBuffer> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
147
plugins/base/macro-action-run.cpp
Normal file
147
plugins/base/macro-action-run.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "macro-action-run.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QDesktopServices>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionRun::id = "run";
|
||||
|
||||
bool MacroActionRun::_registered = MacroActionFactory::Register(
|
||||
MacroActionRun::id, {MacroActionRun::Create, MacroActionRunEdit::Create,
|
||||
"AdvSceneSwitcher.action.run"});
|
||||
|
||||
bool MacroActionRun::PerformAction()
|
||||
{
|
||||
if (_wait) {
|
||||
_procConfig.StartProcessAndWait(_timeout.Milliseconds());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool procStarted = _procConfig.StartProcessDetached();
|
||||
|
||||
// Fall back to using default application to open given file
|
||||
if (!procStarted && _procConfig.Args().empty()) {
|
||||
vblog(LOG_INFO, "run \"%s\" using QDesktopServices",
|
||||
_procConfig.Path().c_str());
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
||||
QString::fromStdString(_procConfig.Path())));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionRun::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "run \"%s\"", _procConfig.UnresolvedPath().c_str());
|
||||
}
|
||||
|
||||
bool MacroActionRun::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_procConfig.Save(obj);
|
||||
_timeout.Save(obj);
|
||||
obs_data_set_bool(obj, "wait", _wait);
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionRun::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_procConfig.Load(obj);
|
||||
// TODO: Remove this fallback in a future version
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
return true;
|
||||
}
|
||||
_timeout.Load(obj);
|
||||
_wait = obs_data_get_bool(obj, "wait");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionRun::GetShortDesc() const
|
||||
{
|
||||
return _procConfig.UnresolvedPath();
|
||||
}
|
||||
|
||||
MacroActionRunEdit::MacroActionRunEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionRun> entryData)
|
||||
: QWidget(parent),
|
||||
_procConfig(new ProcessConfigEdit(this)),
|
||||
_waitLayout(new QHBoxLayout()),
|
||||
_wait(new QCheckBox()),
|
||||
_timeout(new DurationSelection(this, true, 0.1))
|
||||
{
|
||||
QWidget::connect(_procConfig,
|
||||
SIGNAL(ConfigChanged(const ProcessConfig &)), this,
|
||||
SLOT(ProcessConfigChanged(const ProcessConfig &)));
|
||||
QWidget::connect(_procConfig, SIGNAL(AdvancedSettingsEnabled()), this,
|
||||
SLOT(ProcessConfigAdvancedSettingsShown()));
|
||||
QWidget::connect(_wait, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(WaitChanged(int)));
|
||||
QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(TimeoutChanged(const Duration &)));
|
||||
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.run.entry.wait"),
|
||||
_waitLayout,
|
||||
{{"{{wait}}", _wait}, {"{{timeout}}", _timeout}});
|
||||
SetLayoutVisible(_waitLayout, false);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->addWidget(_procConfig);
|
||||
layout->addLayout(_waitLayout);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_procConfig->SetProcessConfig(_entryData->_procConfig);
|
||||
_wait->setChecked(_entryData->_wait);
|
||||
_timeout->SetDuration(_entryData->_timeout);
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::ProcessConfigAdvancedSettingsShown()
|
||||
{
|
||||
SetLayoutVisible(_waitLayout, true);
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::WaitChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
auto lock = LockContext();
|
||||
_entryData->_wait = value;
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::TimeoutChanged(const Duration &timeout)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
auto lock = LockContext();
|
||||
_entryData->_timeout = timeout;
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_procConfig = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
66
plugins/base/macro-action-run.hpp
Normal file
66
plugins/base/macro-action-run.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "process-config.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionRun : public MacroAction {
|
||||
public:
|
||||
MacroActionRun(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionRun>(m);
|
||||
}
|
||||
|
||||
ProcessConfig _procConfig;
|
||||
bool _wait = false;
|
||||
Duration _timeout = 1;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionRunEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionRunEdit(QWidget *parent,
|
||||
std::shared_ptr<MacroActionRun> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionRunEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionRun>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ProcessConfigChanged(const ProcessConfig &);
|
||||
void ProcessConfigAdvancedSettingsShown();
|
||||
void WaitChanged(int);
|
||||
void TimeoutChanged(const Duration &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
ProcessConfigEdit *_procConfig;
|
||||
QHBoxLayout *_waitLayout;
|
||||
QCheckBox *_wait;
|
||||
DurationSelection *_timeout;
|
||||
|
||||
std::shared_ptr<MacroActionRun> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
121
plugins/base/macro-action-scene-collection.cpp
Normal file
121
plugins/base/macro-action-scene-collection.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "macro-action-scene-collection.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSceneCollection::id = "scene_collection";
|
||||
|
||||
bool MacroActionSceneCollection::_registered = MacroActionFactory::Register(
|
||||
MacroActionSceneCollection::id,
|
||||
{MacroActionSceneCollection::Create,
|
||||
MacroActionSceneCollectionEdit::Create,
|
||||
"AdvSceneSwitcher.action.sceneCollection"});
|
||||
|
||||
bool MacroActionSceneCollection::PerformAction()
|
||||
{
|
||||
// Changing the scene collection will also reload the settings of the
|
||||
// scene switcher, so to avoid issues of not being able to change
|
||||
// settings ignore this action if the settings dialog is opened.
|
||||
if (SettingsWindowIsOpened()) {
|
||||
return false;
|
||||
}
|
||||
obs_frontend_set_current_scene_collection(_sceneCollection.c_str());
|
||||
// It does not make sense to continue as the current settings will be
|
||||
// invalid after switching scene collection.
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroActionSceneCollection::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "set scene collection type to \"%s\"",
|
||||
_sceneCollection.c_str());
|
||||
}
|
||||
|
||||
bool MacroActionSceneCollection::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_string(obj, "sceneCollection", _sceneCollection.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSceneCollection::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_sceneCollection = obs_data_get_string(obj, "sceneCollection");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSceneCollection::GetShortDesc() const
|
||||
{
|
||||
return _sceneCollection;
|
||||
}
|
||||
|
||||
void populateSceneCollectionSelection(QComboBox *box)
|
||||
{
|
||||
auto sceneCollections = obs_frontend_get_scene_collections();
|
||||
char **temp = sceneCollections;
|
||||
while (*temp) {
|
||||
const char *name = *temp;
|
||||
box->addItem(name);
|
||||
temp++;
|
||||
}
|
||||
bfree(sceneCollections);
|
||||
box->model()->sort(0);
|
||||
AddSelectionEntry(
|
||||
box, obs_module_text("AdvSceneSwitcher.selectSceneCollection"),
|
||||
false);
|
||||
box->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
MacroActionSceneCollectionEdit::MacroActionSceneCollectionEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneCollection> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_sceneCollections = new QComboBox();
|
||||
populateSceneCollectionSelection(_sceneCollections);
|
||||
QWidget::connect(_sceneCollections,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SceneCollectionChanged(const QString &)));
|
||||
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sceneCollections}}", _sceneCollections},
|
||||
};
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneCollection.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneCollection.warning")));
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSceneCollectionEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sceneCollections->setCurrentText(
|
||||
QString::fromStdString(_entryData->_sceneCollection));
|
||||
}
|
||||
|
||||
void MacroActionSceneCollectionEdit::SceneCollectionChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sceneCollection = text.toStdString();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
59
plugins/base/macro-action-scene-collection.hpp
Normal file
59
plugins/base/macro-action-scene-collection.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSceneCollection : public MacroAction {
|
||||
public:
|
||||
MacroActionSceneCollection(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSceneCollection>(m);
|
||||
}
|
||||
|
||||
std::string _sceneCollection;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSceneCollectionEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSceneCollectionEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSceneCollection> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSceneCollectionEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSceneCollection>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneCollectionChanged(const QString &text);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_sceneCollections;
|
||||
std::shared_ptr<MacroActionSceneCollection> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
176
plugins/base/macro-action-scene-lock.cpp
Normal file
176
plugins/base/macro-action-scene-lock.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "macro-action-scene-lock.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSceneLock::id = "scene_lock";
|
||||
|
||||
bool MacroActionSceneLock::_registered = MacroActionFactory::Register(
|
||||
MacroActionSceneLock::id,
|
||||
{MacroActionSceneLock::Create, MacroActionSceneLockEdit::Create,
|
||||
"AdvSceneSwitcher.action.sceneLock"});
|
||||
|
||||
const static std::map<MacroActionSceneLock::Action, std::string> actionTypes = {
|
||||
{MacroActionSceneLock::Action::LOCK,
|
||||
"AdvSceneSwitcher.action.sceneLock.type.lock"},
|
||||
{MacroActionSceneLock::Action::UNLOCK,
|
||||
"AdvSceneSwitcher.action.sceneLock.type.unlock"},
|
||||
{MacroActionSceneLock::Action::TOGGLE,
|
||||
"AdvSceneSwitcher.action.sceneLock.type.toggle"},
|
||||
};
|
||||
|
||||
static void setSceneItemLock(obs_sceneitem_t *item,
|
||||
MacroActionSceneLock::Action action)
|
||||
{
|
||||
switch (action) {
|
||||
case MacroActionSceneLock::Action::LOCK:
|
||||
obs_sceneitem_set_locked(item, true);
|
||||
break;
|
||||
case MacroActionSceneLock::Action::UNLOCK:
|
||||
obs_sceneitem_set_locked(item, false);
|
||||
break;
|
||||
case MacroActionSceneLock::Action::TOGGLE:
|
||||
obs_sceneitem_set_locked(item, !obs_sceneitem_locked(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneLock::PerformAction()
|
||||
{
|
||||
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
setSceneItemLock(item, _action);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSceneLock::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"performed action \"%s\" for source \"%s\" on scene \"%s\"",
|
||||
it->second.c_str(), _source.ToString(true).c_str(),
|
||||
_scene.ToString(true).c_str());
|
||||
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown scene lock action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneLock::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSceneLock::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSceneLock::GetShortDesc() const
|
||||
{
|
||||
if (!_source.ToString().empty()) {
|
||||
return _scene.ToString() + " - " + _source.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSceneLockEdit::MacroActionSceneLockEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneLock> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, true, true)),
|
||||
_sources(new SceneItemSelectionWidget(parent)),
|
||||
_actions(new QComboBox())
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{actions}}", _actions},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.sceneLock.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSceneLockEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem((_entryData->_source));
|
||||
}
|
||||
|
||||
void MacroActionSceneLockEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroActionSceneLockEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneLockEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionSceneLock::Action>(value);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
69
plugins/base/macro-action-scene-lock.hpp
Normal file
69
plugins/base/macro-action-scene-lock.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSceneLock : public MacroAction {
|
||||
public:
|
||||
MacroActionSceneLock(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSceneLock>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
LOCK,
|
||||
UNLOCK,
|
||||
TOGGLE,
|
||||
};
|
||||
Action _action = Action::LOCK;
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSceneLockEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSceneLockEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSceneLock> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSceneLockEdit(
|
||||
parent, std::dynamic_pointer_cast<MacroActionSceneLock>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void ActionChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionSceneLock> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
252
plugins/base/macro-action-scene-order.cpp
Normal file
252
plugins/base/macro-action-scene-order.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "macro-action-scene-order.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSceneOrder::id = "scene_order";
|
||||
|
||||
bool MacroActionSceneOrder::_registered = MacroActionFactory::Register(
|
||||
MacroActionSceneOrder::id,
|
||||
{MacroActionSceneOrder::Create, MacroActionSceneOrderEdit::Create,
|
||||
"AdvSceneSwitcher.action.sceneOrder"});
|
||||
|
||||
const static std::map<SceneOrderAction, std::string> actionTypes = {
|
||||
{SceneOrderAction::MOVE_UP,
|
||||
"AdvSceneSwitcher.action.sceneOrder.type.moveUp"},
|
||||
{SceneOrderAction::MOVE_DOWN,
|
||||
"AdvSceneSwitcher.action.sceneOrder.type.moveDown"},
|
||||
{SceneOrderAction::MOVE_TOP,
|
||||
"AdvSceneSwitcher.action.sceneOrder.type.moveTop"},
|
||||
{SceneOrderAction::MOVE_BOTTOM,
|
||||
"AdvSceneSwitcher.action.sceneOrder.type.moveBottom"},
|
||||
{SceneOrderAction::POSITION,
|
||||
"AdvSceneSwitcher.action.sceneOrder.type.movePosition"},
|
||||
};
|
||||
|
||||
static void moveSceneItemsUp(std::vector<OBSSceneItem> &items)
|
||||
{
|
||||
// In the case of the same source being in two sequential positions
|
||||
// moving the sources up will cause the sources to swap positions due to
|
||||
// the order scene items are being iterated over, which is most likely
|
||||
// not the desired effect.
|
||||
// Instead we reverse the order of the scene items so all scene items
|
||||
// will be moved up.
|
||||
std::reverse(items.begin(), items.end());
|
||||
|
||||
for (const auto &i : items) {
|
||||
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_UP);
|
||||
}
|
||||
}
|
||||
|
||||
static void moveSceneItemsDown(std::vector<OBSSceneItem> &items)
|
||||
{
|
||||
for (const auto &i : items) {
|
||||
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
static void moveSceneItemsTop(std::vector<OBSSceneItem> &items)
|
||||
{
|
||||
for (const auto &i : items) {
|
||||
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_TOP);
|
||||
}
|
||||
}
|
||||
|
||||
static void moveSceneItemsBottom(std::vector<OBSSceneItem> &items)
|
||||
{
|
||||
for (const auto &i : items) {
|
||||
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
static void moveSceneItemsPos(std::vector<OBSSceneItem> &items, int pos)
|
||||
{
|
||||
for (const auto &i : items) {
|
||||
obs_sceneitem_set_order_position(i, pos);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneOrder::PerformAction()
|
||||
{
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
|
||||
switch (_action) {
|
||||
case SceneOrderAction::MOVE_UP:
|
||||
moveSceneItemsUp(items);
|
||||
break;
|
||||
case SceneOrderAction::MOVE_DOWN:
|
||||
moveSceneItemsDown(items);
|
||||
break;
|
||||
case SceneOrderAction::MOVE_TOP:
|
||||
moveSceneItemsTop(items);
|
||||
break;
|
||||
case SceneOrderAction::MOVE_BOTTOM:
|
||||
moveSceneItemsBottom(items);
|
||||
break;
|
||||
case SceneOrderAction::POSITION:
|
||||
moveSceneItemsPos(items, _position);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSceneOrder::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"performed order action \"%s\" for source \"%s\" on scene \"%s\"",
|
||||
it->second.c_str(), _source.ToString(true).c_str(),
|
||||
_scene.ToString(true).c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown scene order action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneOrder::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "position", _position);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSceneOrder::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "source")) {
|
||||
auto sourceName = obs_data_get_string(obj, "source");
|
||||
obs_data_set_string(obj, "sceneItem", sourceName);
|
||||
}
|
||||
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_action =
|
||||
static_cast<SceneOrderAction>(obs_data_get_int(obj, "action"));
|
||||
_position = obs_data_get_int(obj, "position");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSceneOrder::GetShortDesc() const
|
||||
{
|
||||
if (_source.ToString().empty()) {
|
||||
return "";
|
||||
}
|
||||
return _scene.ToString() + " - " + _source.ToString();
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSceneOrderEdit::MacroActionSceneOrderEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneOrder> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_actions = new QComboBox();
|
||||
_position = new QSpinBox();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_position, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(PositionChanged(int)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{position}}", _position},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.sceneOrder.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSceneOrderEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_position->setValue(_entryData->_position);
|
||||
_position->setVisible(_entryData->_action ==
|
||||
SceneOrderAction::POSITION);
|
||||
}
|
||||
|
||||
void MacroActionSceneOrderEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroActionSceneOrderEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneOrderEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<SceneOrderAction>(value);
|
||||
_position->setVisible(_entryData->_action ==
|
||||
SceneOrderAction::POSITION);
|
||||
}
|
||||
|
||||
void MacroActionSceneOrderEdit::PositionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_position = value;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
79
plugins/base/macro-action-scene-order.hpp
Normal file
79
plugins/base/macro-action-scene-order.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class SceneOrderAction {
|
||||
MOVE_UP,
|
||||
MOVE_DOWN,
|
||||
MOVE_TOP,
|
||||
MOVE_BOTTOM,
|
||||
POSITION,
|
||||
};
|
||||
|
||||
class MacroActionSceneOrder : public MacroAction {
|
||||
public:
|
||||
MacroActionSceneOrder(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSceneOrder>(m);
|
||||
}
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
SceneOrderAction _action = SceneOrderAction::MOVE_UP;
|
||||
int _position = 0;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSceneOrderEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSceneOrderEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSceneOrder> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSceneOrderEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSceneOrder>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void ActionChanged(int value);
|
||||
void PositionChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QComboBox *_actions;
|
||||
QSpinBox *_position;
|
||||
std::shared_ptr<MacroActionSceneOrder> _entryData;
|
||||
|
||||
private:
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
356
plugins/base/macro-action-scene-switch.cpp
Normal file
356
plugins/base/macro-action-scene-switch.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "macro-action-scene-switch.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "scene-switch-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const std::string MacroActionSwitchScene::id =
|
||||
MacroAction::GetDefaultID().data();
|
||||
|
||||
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
|
||||
MacroActionSwitchScene::id,
|
||||
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
|
||||
"AdvSceneSwitcher.action.scene"});
|
||||
|
||||
const static std::map<MacroActionSwitchScene::SceneType, std::string>
|
||||
sceneTypes = {
|
||||
{MacroActionSwitchScene::SceneType::PROGRAM,
|
||||
"AdvSceneSwitcher.action.scene.type.program"},
|
||||
{MacroActionSwitchScene::SceneType::PREVIEW,
|
||||
"AdvSceneSwitcher.action.scene.type.preview"},
|
||||
};
|
||||
|
||||
static void waitForTransitionChange(OBSWeakSource &transition,
|
||||
std::unique_lock<std::mutex> *lock,
|
||||
Macro *macro)
|
||||
{
|
||||
const auto time = 100ms;
|
||||
obs_source_t *source = obs_weak_source_get_source(transition);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool stillTransitioning = true;
|
||||
while (stillTransitioning && !MacroWaitShouldAbort() &&
|
||||
!MacroIsStopped(macro)) {
|
||||
GetMacroTransitionCV().wait_for(*lock, time);
|
||||
float t = obs_transition_get_time(source);
|
||||
stillTransitioning = t < 1.0f && t > 0.0f;
|
||||
}
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
static void waitForTransitionChangeFixedDuration(
|
||||
int duration, std::unique_lock<std::mutex> *lock, Macro *macro)
|
||||
{
|
||||
duration += 200; // It seems to be necessary to add a small buffer
|
||||
auto time = std::chrono::high_resolution_clock::now() +
|
||||
std::chrono::milliseconds(duration);
|
||||
|
||||
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
|
||||
if (GetMacroTransitionCV().wait_until(*lock, time) ==
|
||||
std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int getTransitionOverrideDuration(OBSWeakSource &scene)
|
||||
{
|
||||
int duration = 0;
|
||||
obs_source_t *source = obs_weak_source_get_source(scene);
|
||||
obs_data_t *data = obs_source_get_private_settings(source);
|
||||
auto name = obs_data_get_string(data, "transition");
|
||||
if (strlen(name) != 0) {
|
||||
duration = obs_data_get_int(data, "transition_duration");
|
||||
}
|
||||
obs_data_release(data);
|
||||
obs_source_release(source);
|
||||
return duration;
|
||||
}
|
||||
|
||||
static bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
|
||||
{
|
||||
obs_source_t *source = obs_weak_source_get_source(transition);
|
||||
bool ret = obs_transition_fixed(source);
|
||||
obs_source_release(source);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
|
||||
{
|
||||
OBSWeakSource transition;
|
||||
obs_source_t *source = obs_weak_source_get_source(scene);
|
||||
obs_data_t *data = obs_source_get_private_settings(source);
|
||||
transition = GetWeakTransitionByName(
|
||||
obs_data_get_string(data, "transition"));
|
||||
obs_data_release(data);
|
||||
obs_source_release(source);
|
||||
return transition;
|
||||
}
|
||||
|
||||
static int getExpectedTransitionDuration(OBSWeakSource &scene,
|
||||
OBSWeakSource &transiton_,
|
||||
double duration)
|
||||
{
|
||||
OBSWeakSource transition = transiton_;
|
||||
|
||||
// If we are not modifying the transition override, we will have to
|
||||
// check, if a transition override is set by the user and use its
|
||||
// duration instead
|
||||
if (!ShouldModifyTransitionOverrides()) {
|
||||
auto overrideTransition = getOverrideTransition(scene);
|
||||
if (overrideTransition) {
|
||||
transition = overrideTransition;
|
||||
if (!isUsingFixedLengthTransition(transition)) {
|
||||
return getTransitionOverrideDuration(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isUsingFixedLengthTransition(transition)) {
|
||||
return -1; // no API is available to access the fixed duration
|
||||
}
|
||||
if (duration != 0) {
|
||||
return duration * 1000;
|
||||
}
|
||||
return obs_frontend_get_transition_duration();
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
const int expectedTransitionDuration = getExpectedTransitionDuration(
|
||||
scene, transition, _duration.Seconds());
|
||||
SetMacroAbortWait(false);
|
||||
|
||||
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||
if (expectedTransitionDuration < 0) {
|
||||
waitForTransitionChange(transition, &lock, GetMacro());
|
||||
} else {
|
||||
waitForTransitionChangeFixedDuration(expectedTransitionDuration,
|
||||
&lock, GetMacro());
|
||||
}
|
||||
|
||||
return !MacroWaitShouldAbort();
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::PerformAction()
|
||||
{
|
||||
auto scene = _scene.GetScene();
|
||||
|
||||
if (_sceneType == SceneType::PREVIEW) {
|
||||
OBSSourceAutoRelease previewScneSource =
|
||||
obs_weak_source_get_source(scene);
|
||||
obs_frontend_set_current_preview_scene(previewScneSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto transition = _transition.GetTransition();
|
||||
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
|
||||
obs_frontend_preview_program_mode_active());
|
||||
if (_blockUntilTransitionDone && scene && scene != GetCurrentScene()) {
|
||||
return WaitForTransition(scene, transition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSwitchScene::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "switch%s scene to '%s'",
|
||||
_sceneType == SceneType::PREVIEW ? " preview" : "",
|
||||
_scene.ToString(true).c_str());
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_transition.Save(obj);
|
||||
_duration.Save(obj);
|
||||
obs_data_set_bool(obj, "blockUntilTransitionDone",
|
||||
_blockUntilTransitionDone);
|
||||
obs_data_set_int(obj, "sceneType", static_cast<int>(_sceneType));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_transition.Load(obj);
|
||||
_duration.Load(obj);
|
||||
_blockUntilTransitionDone =
|
||||
obs_data_get_bool(obj, "blockUntilTransitionDone");
|
||||
_sceneType = static_cast<SceneType>(obs_data_get_int(obj, "sceneType"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSwitchScene::GetShortDesc() const
|
||||
{
|
||||
return _scene.ToString();
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : sceneTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true)),
|
||||
_transitions(new TransitionSelectionWidget(this)),
|
||||
_duration(new DurationSelection(parent, false)),
|
||||
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
|
||||
_sceneTypes(new QComboBox()),
|
||||
_entryLayout(new QHBoxLayout())
|
||||
{
|
||||
_duration->SpinBox()->setSpecialValueText("-");
|
||||
populateTypeSelection(_sceneTypes);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_transitions,
|
||||
SIGNAL(TransitionChanged(const TransitionSelection &)),
|
||||
this,
|
||||
SLOT(TransitionChanged(const TransitionSelection &)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(BlockUntilTransitionDoneChanged(int)));
|
||||
QWidget::connect(_sceneTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SceneTypeChanged(int)));
|
||||
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
|
||||
_entryLayout,
|
||||
{{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{sceneTypes}}", _sceneTypes}});
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_entryLayout);
|
||||
mainLayout->addWidget(_blockUntilTransitionDone);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
_sceneTypes->setCurrentIndex(static_cast<int>(_entryData->_sceneType));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_transitions->SetTransition(_entryData->_transition);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_blockUntilTransitionDone->setChecked(
|
||||
_entryData->_blockUntilTransitionDone);
|
||||
SetWidgetVisibility();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_blockUntilTransitionDone = state;
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SceneTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sceneType =
|
||||
static_cast<MacroActionSwitchScene::SceneType>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SetWidgetVisibility()
|
||||
{
|
||||
_entryLayout->removeWidget(_scenes);
|
||||
_entryLayout->removeWidget(_transitions);
|
||||
_entryLayout->removeWidget(_duration);
|
||||
_entryLayout->removeWidget(_sceneTypes);
|
||||
ClearLayout(_entryLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{sceneTypes}}", _sceneTypes},
|
||||
};
|
||||
|
||||
if (_entryData->_sceneType ==
|
||||
MacroActionSwitchScene::SceneType::PREVIEW) {
|
||||
_transitions->hide();
|
||||
_duration->hide();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.entry.preview"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
return;
|
||||
}
|
||||
|
||||
_transitions->show();
|
||||
if (_entryData->_transition.GetType() !=
|
||||
TransitionSelection::Type::TRANSITION) {
|
||||
_duration->show();
|
||||
}
|
||||
const bool fixedDuration = isUsingFixedLengthTransition(
|
||||
_entryData->_transition.GetTransition());
|
||||
_duration->setVisible(!fixedDuration);
|
||||
|
||||
if (fixedDuration) {
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.entry.noDuration"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
} else {
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.scene.entry"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_transition = t;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
79
plugins/base/macro-action-scene-switch.hpp
Normal file
79
plugins/base/macro-action-scene-switch.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "transition-selection.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSwitchScene : public MacroAction {
|
||||
public:
|
||||
MacroActionSwitchScene(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSwitchScene>(m);
|
||||
}
|
||||
|
||||
enum class SceneType { PROGRAM, PREVIEW };
|
||||
SceneType _sceneType = SceneType::PROGRAM;
|
||||
SceneSelection _scene;
|
||||
TransitionSelection _transition;
|
||||
Duration _duration;
|
||||
bool _blockUntilTransitionDone = true;
|
||||
|
||||
private:
|
||||
bool WaitForTransition(OBSWeakSource &scene, OBSWeakSource &transition);
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSwitchSceneEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSwitchSceneEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSwitchScene> entryData = nullptr);
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSwitchSceneEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSwitchScene>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void TransitionChanged(const TransitionSelection &);
|
||||
void DurationChanged(const Duration &seconds);
|
||||
void BlockUntilTransitionDoneChanged(int state);
|
||||
void SceneTypeChanged(int);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
SceneSelectionWidget *_scenes;
|
||||
TransitionSelectionWidget *_transitions;
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_blockUntilTransitionDone;
|
||||
QComboBox *_sceneTypes;
|
||||
QHBoxLayout *_entryLayout;
|
||||
|
||||
std::shared_ptr<MacroActionSwitchScene> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
524
plugins/base/macro-action-scene-transform.cpp
Normal file
524
plugins/base/macro-action-scene-transform.cpp
Normal file
@@ -0,0 +1,524 @@
|
||||
#include "macro-action-scene-transform.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "scene-item-transform-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <graphics/matrix4.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSceneTransform::id = "scene_transform";
|
||||
|
||||
bool MacroActionSceneTransform::_registered = MacroActionFactory::Register(
|
||||
MacroActionSceneTransform::id,
|
||||
{MacroActionSceneTransform::Create,
|
||||
MacroActionSceneTransformEdit::Create,
|
||||
"AdvSceneSwitcher.action.sceneTransform"});
|
||||
|
||||
const static std::map<MacroActionSceneTransform::Action, std::string> actions = {
|
||||
{MacroActionSceneTransform::Action::MANUAL_TRANSFORM,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.manual"},
|
||||
{MacroActionSceneTransform::Action::RESET,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.reset"},
|
||||
{MacroActionSceneTransform::Action::ROTATE,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.rotate"},
|
||||
{MacroActionSceneTransform::Action::FLIP_HORIZONTAL,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal"},
|
||||
{MacroActionSceneTransform::Action::FLIP_VERTICAL,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.flipVertical"},
|
||||
{MacroActionSceneTransform::Action::FIT_TO_SCREEN,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.fitToScreen"},
|
||||
{MacroActionSceneTransform::Action::STRETCH_TO_SCREEN,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.stretchToScreen"},
|
||||
{MacroActionSceneTransform::Action::CENTER_TO_SCREEN,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.centerToScreen"},
|
||||
{MacroActionSceneTransform::Action::CENTER_VERTICALLY,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.centerVertically"},
|
||||
{MacroActionSceneTransform::Action::CENTER_HORIZONTALLY,
|
||||
"AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally"},
|
||||
};
|
||||
|
||||
/* --------------- Helpers based on OBS window-basic-main.cpp --------------- */
|
||||
|
||||
static void getItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
|
||||
{
|
||||
matrix4 boxTransform;
|
||||
obs_sceneitem_get_box_transform(item, &boxTransform);
|
||||
|
||||
vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
|
||||
vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
|
||||
|
||||
auto GetMinPos = [&](float x, float y) {
|
||||
vec3 pos;
|
||||
vec3_set(&pos, x, y, 0.0f);
|
||||
vec3_transform(&pos, &pos, &boxTransform);
|
||||
vec3_min(&tl, &tl, &pos);
|
||||
vec3_max(&br, &br, &pos);
|
||||
};
|
||||
|
||||
GetMinPos(0.0f, 0.0f);
|
||||
GetMinPos(1.0f, 0.0f);
|
||||
GetMinPos(0.0f, 1.0f);
|
||||
GetMinPos(1.0f, 1.0f);
|
||||
}
|
||||
|
||||
static vec3 getItemTL(obs_sceneitem_t *item)
|
||||
{
|
||||
vec3 tl, br;
|
||||
getItemBox(item, tl, br);
|
||||
return tl;
|
||||
}
|
||||
|
||||
static void setItemTL(obs_sceneitem_t *item, const vec3 &tl)
|
||||
{
|
||||
vec3 newTL;
|
||||
vec2 pos;
|
||||
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
newTL = getItemTL(item);
|
||||
pos.x += tl.x - newTL.x;
|
||||
pos.y += tl.y - newTL.y;
|
||||
obs_sceneitem_set_pos(item, &pos);
|
||||
}
|
||||
|
||||
static void reset(obs_sceneitem_t *item)
|
||||
{
|
||||
obs_transform_info info;
|
||||
vec2_set(&info.pos, 0.0f, 0.0f);
|
||||
vec2_set(&info.scale, 1.0f, 1.0f);
|
||||
info.rot = 0.0f;
|
||||
info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
|
||||
info.bounds_type = OBS_BOUNDS_NONE;
|
||||
info.bounds_alignment = OBS_ALIGN_CENTER;
|
||||
vec2_set(&info.bounds, 0.0f, 0.0f);
|
||||
obs_sceneitem_set_info(item, &info);
|
||||
|
||||
obs_sceneitem_crop crop = {};
|
||||
obs_sceneitem_set_crop(item, &crop);
|
||||
}
|
||||
|
||||
static void rotate(obs_sceneitem_t *item, float rot)
|
||||
{
|
||||
vec3 tl = getItemTL(item);
|
||||
rot += obs_sceneitem_get_rot(item);
|
||||
if (rot >= 360.0f) {
|
||||
rot -= 360.0f;
|
||||
} else if (rot <= -360.0f) {
|
||||
rot += 360.0f;
|
||||
}
|
||||
obs_sceneitem_set_rot(item, rot);
|
||||
obs_sceneitem_force_update_transform(item);
|
||||
setItemTL(item, tl);
|
||||
};
|
||||
|
||||
static void flip(obs_sceneitem_t *item, const vec2 &mul)
|
||||
{
|
||||
vec3 tl = getItemTL(item);
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
vec2_mul(&scale, &scale, &mul);
|
||||
obs_sceneitem_set_scale(item, &scale);
|
||||
obs_sceneitem_force_update_transform(item);
|
||||
setItemTL(item, tl);
|
||||
}
|
||||
|
||||
static void centerAlign(obs_sceneitem_t *item, obs_bounds_type boundsType)
|
||||
{
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
|
||||
obs_transform_info itemInfo;
|
||||
vec2_set(&itemInfo.pos, 0.0f, 0.0f);
|
||||
vec2_set(&itemInfo.scale, 1.0f, 1.0f);
|
||||
itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
|
||||
itemInfo.rot = 0.0f;
|
||||
|
||||
vec2_set(&itemInfo.bounds, float(ovi.base_width),
|
||||
float(ovi.base_height));
|
||||
itemInfo.bounds_type = boundsType;
|
||||
itemInfo.bounds_alignment = OBS_ALIGN_CENTER;
|
||||
|
||||
obs_sceneitem_set_info(item, &itemInfo);
|
||||
}
|
||||
|
||||
enum class CenterType {
|
||||
SCREEN,
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
};
|
||||
|
||||
static void center(obs_sceneitem_t *item, const CenterType ¢erType)
|
||||
{
|
||||
// Get center x, y coordinates of items
|
||||
vec3 tl, br;
|
||||
getItemBox(item, tl, br);
|
||||
|
||||
float left = tl.x;
|
||||
float top = tl.y;
|
||||
float right = br.x;
|
||||
float bottom = br.y;
|
||||
|
||||
vec3 center;
|
||||
center.x = (right + left) / 2.0f;
|
||||
center.y = (top + bottom) / 2.0f;
|
||||
center.z = 0.0f;
|
||||
|
||||
// Get coordinates of screen center
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
|
||||
vec3 screenCenter;
|
||||
vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height),
|
||||
0.0f);
|
||||
|
||||
vec3_mulf(&screenCenter, &screenCenter, 0.5f);
|
||||
|
||||
// Calculate difference between screen center and item center
|
||||
vec3 offset;
|
||||
vec3_sub(&offset, &screenCenter, ¢er);
|
||||
|
||||
// Shift item by offset
|
||||
vec3_add(&tl, &tl, &offset);
|
||||
|
||||
vec3 itemTL = getItemTL(item);
|
||||
|
||||
if (centerType == CenterType::VERTICAL) {
|
||||
tl.x = itemTL.x;
|
||||
} else if (centerType == CenterType::HORIZONTAL) {
|
||||
tl.y = itemTL.y;
|
||||
}
|
||||
|
||||
setItemTL(item, tl);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void MacroActionSceneTransform::Transform(obs_scene_item *item)
|
||||
{
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_action) {
|
||||
case MacroActionSceneTransform::Action::RESET:
|
||||
obs_sceneitem_defer_update_begin(item);
|
||||
reset(item);
|
||||
obs_sceneitem_defer_update_end(item);
|
||||
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::ROTATE:
|
||||
rotate(item, _rotation);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::FLIP_HORIZONTAL: {
|
||||
vec2 scale;
|
||||
vec2_set(&scale, -1.0f, 1.0f);
|
||||
flip(item, scale);
|
||||
break;
|
||||
}
|
||||
case MacroActionSceneTransform::Action::FLIP_VERTICAL: {
|
||||
vec2 scale;
|
||||
vec2_set(&scale, 1.0f, -1.0f);
|
||||
flip(item, scale);
|
||||
break;
|
||||
}
|
||||
case MacroActionSceneTransform::Action::FIT_TO_SCREEN:
|
||||
centerAlign(item, OBS_BOUNDS_SCALE_INNER);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::STRETCH_TO_SCREEN:
|
||||
centerAlign(item, OBS_BOUNDS_STRETCH);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::CENTER_TO_SCREEN:
|
||||
center(item, CenterType::SCREEN);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::CENTER_VERTICALLY:
|
||||
center(item, CenterType::VERTICAL);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::CENTER_HORIZONTALLY:
|
||||
center(item, CenterType::HORIZONTAL);
|
||||
break;
|
||||
case MacroActionSceneTransform::Action::MANUAL_TRANSFORM:
|
||||
ApplySettings(_settings); // Resolves variables
|
||||
obs_sceneitem_defer_update_begin(item);
|
||||
obs_sceneitem_set_info(item, &_info);
|
||||
obs_sceneitem_set_crop(item, &_crop);
|
||||
obs_sceneitem_defer_update_end(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneTransform::PerformAction()
|
||||
{
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
Transform(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSceneTransform::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO,
|
||||
"performed transform action %d for source \"%s\" on scene \"%s\"",
|
||||
static_cast<int>(_action), _source.ToString(true).c_str(),
|
||||
_scene.ToString(true).c_str());
|
||||
}
|
||||
|
||||
bool MacroActionSceneTransform::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
_rotation.Save(obj, "rotation");
|
||||
_settings.Save(obj, "settings");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSceneTransform::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "source")) {
|
||||
auto sourceName = obs_data_get_string(obj, "source");
|
||||
obs_data_set_string(obj, "sceneItem", sourceName);
|
||||
}
|
||||
|
||||
MacroAction::Load(obj);
|
||||
if (!obs_data_has_user_value(obj, "action")) {
|
||||
_action =
|
||||
Action::MANUAL_TRANSFORM; // TODO: Remove fallback in future
|
||||
} else {
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
}
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_rotation.Load(obj, "rotation");
|
||||
_settings.Load(obj, "settings");
|
||||
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (!obs_data_has_user_value(obj, "settings")) {
|
||||
LoadTransformState(obj, _info, _crop);
|
||||
_settings = ConvertSettings();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSceneTransform::GetShortDesc() const
|
||||
{
|
||||
if (_source.ToString().empty()) {
|
||||
return "";
|
||||
}
|
||||
return _scene.ToString() + " - " + _source.ToString();
|
||||
}
|
||||
|
||||
std::string MacroActionSceneTransform::ConvertSettings()
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
SaveTransformState(data, _info, _crop);
|
||||
std::string json = obs_data_get_json(data);
|
||||
obs_data_release(data);
|
||||
return json;
|
||||
}
|
||||
|
||||
void MacroActionSceneTransform::ApplySettings(const std::string &settings)
|
||||
{
|
||||
auto data = obs_data_create_from_json(settings.c_str());
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
LoadTransformState(data, _info, _crop);
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
if (obs_data_has_user_value(data, "size")) {
|
||||
auto obj = obs_data_get_obj(data, "size");
|
||||
auto source = obs_sceneitem_get_source(items[0]);
|
||||
if (double h = obs_data_get_double(obj, "height")) {
|
||||
_info.scale.y =
|
||||
h / double(obs_source_get_height(source));
|
||||
}
|
||||
if (double w = obs_data_get_double(obj, "width")) {
|
||||
_info.scale.x =
|
||||
w / double(obs_source_get_width(source));
|
||||
}
|
||||
obs_data_release(obj);
|
||||
}
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[type, name] : actions) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneTransform> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
|
||||
_sources(new SceneItemSelectionWidget(parent)),
|
||||
_action(new QComboBox()),
|
||||
_rotation(new VariableDoubleSpinBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneTransform.getTransform"))),
|
||||
_settings(new VariableTextEdit(this)),
|
||||
_buttonLayout(new QHBoxLayout())
|
||||
{
|
||||
_rotation->setMinimum(-360);
|
||||
_rotation->setMaximum(360);
|
||||
_rotation->setSuffix("°");
|
||||
|
||||
populateActionSelection(_action);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_action, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(
|
||||
_rotation,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(RotationChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes}, {"{{rotation}}", _rotation},
|
||||
{"{{sources}}", _sources}, {"{{action}}", _action},
|
||||
{"{{settings}}", _settings}, {"{{getSettings}}", _getSettings},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.sceneTransform.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
|
||||
_buttonLayout->addWidget(_getSettings);
|
||||
_buttonLayout->addStretch();
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_settings);
|
||||
mainLayout->addLayout(_buttonLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_action->setCurrentIndex(
|
||||
_action->findData(static_cast<int>(_entryData->_action)));
|
||||
_rotation->SetValue(_entryData->_rotation);
|
||||
_settings->setPlainText(_entryData->_settings);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::ActionChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionSceneTransform::Action>(
|
||||
_action->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::RotationChanged(const DoubleVariable &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_rotation = value;
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::GetSettingsClicked()
|
||||
{
|
||||
if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto settings = GetSceneItemTransform(items[0]);
|
||||
_settings->setPlainText(FormatJsonString(settings));
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::SettingsChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::SetWidgetVisibility()
|
||||
{
|
||||
SetLayoutVisible(
|
||||
_buttonLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
|
||||
_settings->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
|
||||
_rotation->setVisible(_entryData->_action ==
|
||||
MacroActionSceneTransform::Action::ROTATE);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
99
plugins/base/macro-action-scene-transform.hpp
Normal file
99
plugins/base/macro-action-scene-transform.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSceneTransform : public MacroAction {
|
||||
public:
|
||||
MacroActionSceneTransform(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSceneTransform>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
RESET,
|
||||
ROTATE,
|
||||
FLIP_HORIZONTAL,
|
||||
FLIP_VERTICAL,
|
||||
FIT_TO_SCREEN,
|
||||
STRETCH_TO_SCREEN,
|
||||
CENTER_TO_SCREEN,
|
||||
CENTER_VERTICALLY,
|
||||
CENTER_HORIZONTALLY,
|
||||
MANUAL_TRANSFORM = 100,
|
||||
};
|
||||
|
||||
Action _action = Action::RESET;
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
StringVariable _settings = "";
|
||||
DoubleVariable _rotation = 90.0;
|
||||
|
||||
private:
|
||||
void Transform(obs_scene_item *);
|
||||
void ApplySettings(const std::string &);
|
||||
std::string ConvertSettings();
|
||||
|
||||
struct obs_transform_info _info = {};
|
||||
struct obs_sceneitem_crop _crop = {};
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSceneTransformEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSceneTransformEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSceneTransform> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSceneTransformEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSceneTransform>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void ActionChanged(int);
|
||||
void RotationChanged(const NumberVariable<double> &value);
|
||||
void GetSettingsClicked();
|
||||
void SettingsChanged();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QComboBox *_action;
|
||||
VariableDoubleSpinBox *_rotation;
|
||||
QPushButton *_getSettings;
|
||||
VariableTextEdit *_settings;
|
||||
QHBoxLayout *_buttonLayout;
|
||||
|
||||
std::shared_ptr<MacroActionSceneTransform> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
191
plugins/base/macro-action-scene-visibility.cpp
Normal file
191
plugins/base/macro-action-scene-visibility.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "macro-action-scene-visibility.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSceneVisibility::id = "scene_visibility";
|
||||
|
||||
bool MacroActionSceneVisibility::_registered = MacroActionFactory::Register(
|
||||
MacroActionSceneVisibility::id,
|
||||
{MacroActionSceneVisibility::Create,
|
||||
MacroActionSceneVisibilityEdit::Create,
|
||||
"AdvSceneSwitcher.action.sceneVisibility"});
|
||||
|
||||
const static std::map<MacroActionSceneVisibility::Action, std::string>
|
||||
actionTypes = {
|
||||
{MacroActionSceneVisibility::Action::SHOW,
|
||||
"AdvSceneSwitcher.action.sceneVisibility.type.show"},
|
||||
{MacroActionSceneVisibility::Action::HIDE,
|
||||
"AdvSceneSwitcher.action.sceneVisibility.type.hide"},
|
||||
{MacroActionSceneVisibility::Action::TOGGLE,
|
||||
"AdvSceneSwitcher.action.sceneVisibility.type.toggle"},
|
||||
};
|
||||
|
||||
static void setSceneItemVisibility(obs_sceneitem_t *item,
|
||||
MacroActionSceneVisibility::Action action)
|
||||
{
|
||||
switch (action) {
|
||||
case MacroActionSceneVisibility::Action::SHOW:
|
||||
obs_sceneitem_set_visible(item, true);
|
||||
break;
|
||||
case MacroActionSceneVisibility::Action::HIDE:
|
||||
obs_sceneitem_set_visible(item, false);
|
||||
break;
|
||||
case MacroActionSceneVisibility::Action::TOGGLE:
|
||||
obs_sceneitem_set_visible(item, !obs_sceneitem_visible(item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneVisibility::PerformAction()
|
||||
{
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
setSceneItemVisibility(item, _action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibility::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO,
|
||||
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
|
||||
it->second.c_str(), _source.ToString(true).c_str(),
|
||||
_scene.ToString(true).c_str());
|
||||
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown SceneVisibility action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSceneVisibility::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSceneVisibility::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "source")) {
|
||||
auto sourceName = obs_data_get_string(obj, "source");
|
||||
obs_data_set_string(obj, "sceneItem", sourceName);
|
||||
}
|
||||
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_action = static_cast<MacroActionSceneVisibility::Action>(
|
||||
obs_data_get_int(obj, "action"));
|
||||
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_get_int(obj, "sourceType") != 0) {
|
||||
auto sourceGroup = obs_data_get_string(obj, "sourceGroup");
|
||||
_source.SetSourceTypeSelection(sourceGroup);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSceneVisibility::GetShortDesc() const
|
||||
{
|
||||
if (!_source.ToString().empty()) {
|
||||
return _scene.ToString() + " - " + _source.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, true, true)),
|
||||
_sources(new SceneItemSelectionWidget(parent)),
|
||||
_actions(new QComboBox())
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneVisibility.entry"),
|
||||
layout,
|
||||
{{"{{scenes}}", _scenes},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{actions}}", _actions}});
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibilityEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem((_entryData->_source));
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibilityEdit::SourceChanged(
|
||||
const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibilityEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action =
|
||||
static_cast<MacroActionSceneVisibility::Action>(value);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
73
plugins/base/macro-action-scene-visibility.hpp
Normal file
73
plugins/base/macro-action-scene-visibility.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSceneVisibility : public MacroAction {
|
||||
public:
|
||||
MacroActionSceneVisibility(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSceneVisibility>(m);
|
||||
}
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
|
||||
enum class Action {
|
||||
SHOW,
|
||||
HIDE,
|
||||
TOGGLE,
|
||||
};
|
||||
Action _action = Action::SHOW;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSceneVisibilityEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSceneVisibilityEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSceneVisibility> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSceneVisibilityEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSceneVisibility>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void ActionChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionSceneVisibility> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
277
plugins/base/macro-action-screenshot.cpp
Normal file
277
plugins/base/macro-action-screenshot.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#include "macro-action-screenshot.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const uint32_t MacroActionScreenshot::_version = 1;
|
||||
|
||||
const std::string MacroActionScreenshot::id = "screenshot";
|
||||
|
||||
bool MacroActionScreenshot::_registered = MacroActionFactory::Register(
|
||||
MacroActionScreenshot::id,
|
||||
{MacroActionScreenshot::Create, MacroActionScreenshotEdit::Create,
|
||||
"AdvSceneSwitcher.action.screenshot"});
|
||||
|
||||
void MacroActionScreenshot::FrontendScreenshot(OBSWeakSource &source)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
|
||||
if (source) {
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
obs_frontend_take_source_screenshot(s);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
obs_frontend_take_screenshot();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MacroActionScreenshot::CustomScreenshot(OBSWeakSource &source)
|
||||
{
|
||||
if (!source && _targetType == TargetType::SCENE) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
_screenshot.~ScreenshotHelper();
|
||||
new (&_screenshot) ScreenshotHelper(s, QRect(), false, 0, true, _path);
|
||||
obs_source_release(s);
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::PerformAction()
|
||||
{
|
||||
OBSWeakSource source = nullptr;
|
||||
switch (_targetType) {
|
||||
case MacroActionScreenshot::TargetType::SOURCE:
|
||||
source = _source.GetSource();
|
||||
break;
|
||||
case MacroActionScreenshot::TargetType::SCENE:
|
||||
source = _scene.GetScene(false);
|
||||
break;
|
||||
case MacroActionScreenshot::TargetType::MAIN_OUTPUT:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_saveType) {
|
||||
case MacroActionScreenshot::SaveType::OBS_DEFAULT:
|
||||
FrontendScreenshot(source);
|
||||
break;
|
||||
case MacroActionScreenshot::SaveType::CUSTOM:
|
||||
CustomScreenshot(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionScreenshot::LogAction() const
|
||||
{
|
||||
switch (_targetType) {
|
||||
case MacroActionScreenshot::TargetType::SOURCE:
|
||||
vblog(LOG_INFO, "trigger screenshot of \"%s\"",
|
||||
_source.ToString(true).c_str());
|
||||
break;
|
||||
case MacroActionScreenshot::TargetType::SCENE:
|
||||
vblog(LOG_INFO, "trigger screenshot of \"%s\"",
|
||||
_scene.ToString(true).c_str());
|
||||
break;
|
||||
case MacroActionScreenshot::TargetType::MAIN_OUTPUT:
|
||||
vblog(LOG_INFO, "trigger screenshot of main output");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_int(obj, "saveType", static_cast<int>(_saveType));
|
||||
obs_data_set_int(obj, "targetType", static_cast<int>(_targetType));
|
||||
_path.Save(obj, "savePath");
|
||||
obs_data_set_int(obj, "version", _version);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_saveType = static_cast<SaveType>(obs_data_get_int(obj, "saveType"));
|
||||
_targetType =
|
||||
static_cast<TargetType>(obs_data_get_int(obj, "targetType"));
|
||||
_path.Load(obj, "savePath");
|
||||
|
||||
// TODO: Remove fallback for older versions
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
if (!_source.GetSource() && !_scene.GetScene(false)) {
|
||||
_targetType = TargetType::MAIN_OUTPUT;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionScreenshot::GetShortDesc() const
|
||||
{
|
||||
if (_targetType == TargetType::SCENE) {
|
||||
return _scene.ToString();
|
||||
} else {
|
||||
return _source.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static void populateSaveTypeSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.save.default"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.save.custom"));
|
||||
}
|
||||
|
||||
static void populateTargetTypeSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.type.source"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.type.scene"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.OBSVideoOutput"));
|
||||
}
|
||||
|
||||
MacroActionScreenshotEdit::MacroActionScreenshotEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, true, true,
|
||||
true)),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_saveType(new QComboBox()),
|
||||
_targetType(new QComboBox()),
|
||||
_savePath(new FileSelection(FileSelection::Type::WRITE, this))
|
||||
{
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.blackscreenNote"));
|
||||
auto sources = GetVideoSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
populateSaveTypeSelection(_saveType);
|
||||
populateTargetTypeSelection(_targetType);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_saveType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SaveTypeChanged(int)));
|
||||
QWidget::connect(_targetType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TargetTypeChanged(int)));
|
||||
QWidget::connect(_savePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sources}}", _sources},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{saveType}}", _saveType},
|
||||
{"{{targetType}}", _targetType},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.screenshot.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(layout);
|
||||
mainLayout->addWidget(_savePath);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_saveType->setCurrentIndex(static_cast<int>(_entryData->_saveType));
|
||||
_targetType->setCurrentIndex(static_cast<int>(_entryData->_targetType));
|
||||
_savePath->SetPath(_entryData->_path);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SaveTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
}
|
||||
|
||||
_entryData->_saveType =
|
||||
static_cast<MacroActionScreenshot::SaveType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::TargetTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
}
|
||||
|
||||
_entryData->_targetType =
|
||||
static_cast<MacroActionScreenshot::TargetType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::PathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_path = text.toStdString();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = source;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_savePath->setVisible(_entryData->_saveType ==
|
||||
MacroActionScreenshot::SaveType::CUSTOM);
|
||||
_sources->setVisible(_entryData->_targetType ==
|
||||
MacroActionScreenshot::TargetType::SOURCE);
|
||||
_scenes->setVisible(_entryData->_targetType ==
|
||||
MacroActionScreenshot::TargetType::SCENE);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
90
plugins/base/macro-action-screenshot.hpp
Normal file
90
plugins/base/macro-action-screenshot.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "screenshot-helper.hpp"
|
||||
#include "source-selection.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionScreenshot : public MacroAction {
|
||||
public:
|
||||
MacroActionScreenshot(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionScreenshot>(m);
|
||||
}
|
||||
enum class SaveType {
|
||||
OBS_DEFAULT,
|
||||
CUSTOM,
|
||||
};
|
||||
SaveType _saveType = SaveType::OBS_DEFAULT;
|
||||
enum class TargetType {
|
||||
SOURCE,
|
||||
SCENE,
|
||||
MAIN_OUTPUT,
|
||||
};
|
||||
TargetType _targetType = TargetType::SOURCE;
|
||||
SceneSelection _scene;
|
||||
SourceSelection _source;
|
||||
StringVariable _path = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
|
||||
private:
|
||||
void FrontendScreenshot(OBSWeakSource &);
|
||||
void CustomScreenshot(OBSWeakSource &);
|
||||
|
||||
ScreenshotHelper _screenshot;
|
||||
|
||||
const static uint32_t _version;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionScreenshotEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionScreenshotEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionScreenshot> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionScreenshotEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionScreenshot>(
|
||||
action));
|
||||
}
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void SaveTypeChanged(int index);
|
||||
void TargetTypeChanged(int index);
|
||||
void PathChanged(const QString &text);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SourceSelectionWidget *_sources;
|
||||
QComboBox *_saveType;
|
||||
QComboBox *_targetType;
|
||||
FileSelection *_savePath;
|
||||
std::shared_ptr<MacroActionScreenshot> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
432
plugins/base/macro-action-sequence.cpp
Normal file
432
plugins/base/macro-action-sequence.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "macro-action-sequence.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSequence::id = "sequence";
|
||||
|
||||
bool MacroActionSequence::_registered = MacroActionFactory::Register(
|
||||
MacroActionSequence::id,
|
||||
{MacroActionSequence::Create, MacroActionSequenceEdit::Create,
|
||||
"AdvSceneSwitcher.action.sequence"});
|
||||
|
||||
static const std::map<MacroActionSequence::Action, std::string> actionTypes = {
|
||||
{MacroActionSequence::Action::RUN_SEQUENCE,
|
||||
"AdvSceneSwitcher.action.sequence.action.run"},
|
||||
{MacroActionSequence::Action::SET_INDEX,
|
||||
"AdvSceneSwitcher.action.sequence.action.setIndex"},
|
||||
};
|
||||
|
||||
static int getNextUnpausedMacroIdx(std::vector<MacroRef> ¯os, int startIdx)
|
||||
{
|
||||
if (startIdx < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (; (int)macros.size() > startIdx; ++startIdx) {
|
||||
auto macro = macros[startIdx].GetMacro();
|
||||
if (!MacroIsPaused(macro.get())) {
|
||||
return startIdx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
MacroRef MacroActionSequence::GetNextMacro(bool advance)
|
||||
{
|
||||
int idx = _lastIdx;
|
||||
MacroRef res;
|
||||
|
||||
int nextUnpausedIdx = getNextUnpausedMacroIdx(_macros, idx + 1);
|
||||
if (nextUnpausedIdx != -1) {
|
||||
res = _macros[nextUnpausedIdx];
|
||||
idx = nextUnpausedIdx;
|
||||
} else {
|
||||
if (_restart) {
|
||||
idx = getNextUnpausedMacroIdx(_macros, 0);
|
||||
if (idx != -1) {
|
||||
res = _macros[idx];
|
||||
} else {
|
||||
// End of sequence reached, as all available
|
||||
// macros are either paused or the macro list
|
||||
// is empty
|
||||
idx = _macros.size();
|
||||
}
|
||||
} else {
|
||||
// End of sequence reached
|
||||
idx = _macros.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (advance) {
|
||||
_lastIdx = idx;
|
||||
_lastSequenceMacro = res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool MacroActionSequence::RunSequence()
|
||||
{
|
||||
if (_macros.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto macro = GetNextMacro().GetMacro();
|
||||
if (!macro.get()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return RunMacroActions(macro.get());
|
||||
}
|
||||
|
||||
bool MacroActionSequence::SetSequenceIndex() const
|
||||
{
|
||||
auto macro = _macro.GetMacro();
|
||||
if (!macro) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto actions = GetMacroActions(macro.get());
|
||||
if (!actions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &action : *actions) {
|
||||
if (action->GetId() != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sequenceAction =
|
||||
dynamic_cast<MacroActionSequence *>(action.get());
|
||||
if (!sequenceAction) {
|
||||
continue;
|
||||
}
|
||||
// -2 is needed since the _lastIndex starts at -1 and the reset
|
||||
// index starts at 1
|
||||
sequenceAction->_lastIdx = _resetIndex - 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSequence::PerformAction()
|
||||
{
|
||||
if (_action == Action::RUN_SEQUENCE) {
|
||||
return RunSequence();
|
||||
} else {
|
||||
return SetSequenceIndex();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroActionSequence::LogAction() const
|
||||
{
|
||||
if (_action == Action::RUN_SEQUENCE) {
|
||||
vblog(LOG_INFO, "running macro sequence");
|
||||
} else {
|
||||
vblog(LOG_INFO, "set sequences of macro '%s' to index %d",
|
||||
_macro.Name().c_str(), _resetIndex.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSequence::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
SaveMacroList(obj, _macros);
|
||||
obs_data_set_bool(obj, "restart", _restart);
|
||||
_macro.Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_resetIndex.Save(obj, "resetIndex");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSequence::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
LoadMacroList(obj, _macros);
|
||||
_restart = obs_data_get_bool(obj, "restart");
|
||||
_macro.Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_resetIndex.Load(obj, "resetIndex");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSequence::PostLoad()
|
||||
{
|
||||
return MacroAction::PostLoad() && MacroRefAction::PostLoad() &&
|
||||
MultiMacroRefAction::PostLoad();
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSequenceEdit::MacroActionSequenceEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSequence> entryData)
|
||||
: QWidget(parent),
|
||||
_macroList(new MacroList(this, true, true)),
|
||||
_continueFrom(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sequence.continueFrom"))),
|
||||
_restart(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.sequence.restart"))),
|
||||
_statusLine(new QLabel()),
|
||||
_actions(new QComboBox()),
|
||||
_macros(new MacroSelection(this)),
|
||||
_resetIndex(new VariableSpinBox()),
|
||||
_layout(new QHBoxLayout())
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
_resetIndex->setMinimum(1);
|
||||
|
||||
auto line = new QFrame();
|
||||
line->setFrameShape(QFrame::VLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
_macroList->AddControl(line);
|
||||
_macroList->AddControl(_continueFrom);
|
||||
|
||||
QWidget::connect(_macroList, SIGNAL(Added(const std::string &)), this,
|
||||
SLOT(Add(const std::string &)));
|
||||
QWidget::connect(_macroList, SIGNAL(Removed(int)), this,
|
||||
SLOT(Remove(int)));
|
||||
QWidget::connect(_macroList, SIGNAL(MovedUp(int)), this, SLOT(Up(int)));
|
||||
QWidget::connect(_macroList, SIGNAL(MovedDown(int)), this,
|
||||
SLOT(Down(int)));
|
||||
QWidget::connect(_macroList, SIGNAL(Replaced(int, const std::string &)),
|
||||
this, SLOT(Replace(int, const std::string &)));
|
||||
QWidget::connect(_continueFrom, SIGNAL(clicked()), this,
|
||||
SLOT(ContinueFromClicked()));
|
||||
QWidget::connect(_restart, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RestartChanged(int)));
|
||||
QWidget::connect(window(), SIGNAL(MacroRemoved(const QString &)), this,
|
||||
SLOT(MacroRemove(const QString &)));
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(MacroChanged(const QString &)));
|
||||
QWidget::connect(
|
||||
_resetIndex,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(ResetIndexChanged(const NumberVariable<int> &)));
|
||||
|
||||
_layout->addWidget(_actions);
|
||||
_layout->addWidget(_macros);
|
||||
_layout->addWidget(_resetIndex);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_layout);
|
||||
mainLayout->addWidget(_macroList);
|
||||
mainLayout->addWidget(_restart);
|
||||
mainLayout->addWidget(_statusLine);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
|
||||
UpdateStatusLine();
|
||||
connect(&_statusTimer, SIGNAL(timeout()), this,
|
||||
SLOT(UpdateStatusLine()));
|
||||
_statusTimer.start(1000);
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_macroList->SetContent(_entryData->_macros);
|
||||
_restart->setChecked(_entryData->_restart);
|
||||
_resetIndex->SetValue(_entryData->_resetIndex);
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_macros->SetCurrentMacro(_entryData->_macro);
|
||||
SetWidgetVisibility();
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::MacroRemove(const QString &)
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = _entryData->_macros.begin();
|
||||
while (it != _entryData->_macros.end()) {
|
||||
|
||||
if (!it->GetMacro()) {
|
||||
it = _entryData->_macros.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::Add(const std::string &name)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
MacroRef macro(name);
|
||||
_entryData->_macros.push_back(macro);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::Remove(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_macros.erase(std::next(_entryData->_macros.begin(), idx));
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::Up(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
std::swap(_entryData->_macros[idx], _entryData->_macros[idx - 1]);
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::Down(int idx)
|
||||
{
|
||||
auto lock = LockContext();
|
||||
std::swap(_entryData->_macros[idx], _entryData->_macros[idx + 1]);
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::Replace(int idx, const std::string &name)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
MacroRef macro(name);
|
||||
auto lock = LockContext();
|
||||
_entryData->_macros[idx] = macro;
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::ContinueFromClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = _macroList->CurrentRow();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
auto lock = LockContext();
|
||||
_entryData->_lastIdx = idx - 1;
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::RestartChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_restart = state;
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::UpdateStatusLine()
|
||||
{
|
||||
QString lastMacroName =
|
||||
obs_module_text("AdvSceneSwitcher.action.sequence.status.none");
|
||||
QString nextMacroName =
|
||||
obs_module_text("AdvSceneSwitcher.action.sequence.status.none");
|
||||
if (_entryData) {
|
||||
if (auto macro = _entryData->_lastSequenceMacro.GetMacro()) {
|
||||
lastMacroName = QString::fromStdString(
|
||||
GetMacroName(macro.get()));
|
||||
}
|
||||
auto next = _entryData->GetNextMacro(false).GetMacro();
|
||||
if (next) {
|
||||
nextMacroName = QString::fromStdString(
|
||||
GetMacroName(next.get()));
|
||||
}
|
||||
}
|
||||
|
||||
QString format{
|
||||
obs_module_text("AdvSceneSwitcher.action.sequence.status")};
|
||||
_statusLine->setText(format.arg(lastMacroName, nextMacroName));
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionSequence::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::MacroChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_macro = text;
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::ResetIndexChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_resetIndex = value;
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::SetWidgetVisibility()
|
||||
{
|
||||
_layout->removeWidget(_actions);
|
||||
_layout->removeWidget(_macros);
|
||||
_layout->removeWidget(_resetIndex);
|
||||
|
||||
ClearLayout(_layout);
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
_entryData->_action ==
|
||||
MacroActionSequence::Action::RUN_SEQUENCE
|
||||
? "AdvSceneSwitcher.action.sequence.entry.run"
|
||||
: "AdvSceneSwitcher.action.sequence.entry.setIndex"),
|
||||
_layout,
|
||||
{{"{{actions}}", _actions},
|
||||
{"{{macros}}", _macros},
|
||||
{"{{index}}", _resetIndex}});
|
||||
|
||||
_macroList->setVisible(_entryData->_action ==
|
||||
MacroActionSequence::Action::RUN_SEQUENCE);
|
||||
_restart->setVisible(_entryData->_action ==
|
||||
MacroActionSequence::Action::RUN_SEQUENCE);
|
||||
_statusLine->setVisible(_entryData->_action ==
|
||||
MacroActionSequence::Action::RUN_SEQUENCE);
|
||||
_macros->setVisible(_entryData->_action ==
|
||||
MacroActionSequence::Action::SET_INDEX);
|
||||
_resetIndex->setVisible(_entryData->_action ==
|
||||
MacroActionSequence::Action::SET_INDEX);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
98
plugins/base/macro-action-sequence.hpp
Normal file
98
plugins/base/macro-action-sequence.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "macro-list.hpp"
|
||||
#include "macro-selection.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSequence : public MultiMacroRefAction, public MacroRefAction {
|
||||
public:
|
||||
MacroActionSequence(Macro *m)
|
||||
: MacroAction(m), MultiMacroRefAction(m), MacroRefAction(m)
|
||||
{
|
||||
}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
bool PostLoad() override;
|
||||
std::string GetId() const { return id; };
|
||||
MacroRef GetNextMacro(bool advance = true);
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSequence>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
RUN_SEQUENCE,
|
||||
SET_INDEX,
|
||||
};
|
||||
Action _action = Action::RUN_SEQUENCE;
|
||||
bool _restart = true;
|
||||
IntVariable _resetIndex = 1;
|
||||
|
||||
MacroRef _lastSequenceMacro;
|
||||
int _lastIdx = -1;
|
||||
|
||||
private:
|
||||
bool RunSequence();
|
||||
bool SetSequenceIndex() const;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSequenceEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSequenceEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSequence> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSequenceEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSequence>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void MacroRemove(const QString &name);
|
||||
void Add(const std::string &);
|
||||
void Remove(int);
|
||||
void Up(int);
|
||||
void Down(int);
|
||||
void Replace(int, const std::string &);
|
||||
void ContinueFromClicked();
|
||||
void RestartChanged(int state);
|
||||
void UpdateStatusLine();
|
||||
void ActionChanged(int);
|
||||
void MacroChanged(const QString &text);
|
||||
void ResetIndexChanged(const NumberVariable<int> &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
MacroList *_macroList;
|
||||
QPushButton *_continueFrom;
|
||||
QCheckBox *_restart;
|
||||
QLabel *_statusLine;
|
||||
QComboBox *_actions;
|
||||
MacroSelection *_macros;
|
||||
VariableSpinBox *_resetIndex;
|
||||
QHBoxLayout *_layout;
|
||||
QTimer _statusTimer;
|
||||
|
||||
std::shared_ptr<MacroActionSequence> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
661
plugins/base/macro-action-source.cpp
Normal file
661
plugins/base/macro-action-source.cpp
Normal file
@@ -0,0 +1,661 @@
|
||||
#include "macro-action-source.hpp"
|
||||
#include "source-settings-helpers.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
Q_DECLARE_METATYPE(advss::SourceSettingButton);
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSource::id = "source";
|
||||
|
||||
bool MacroActionSource::_registered = MacroActionFactory::Register(
|
||||
MacroActionSource::id,
|
||||
{MacroActionSource::Create, MacroActionSourceEdit::Create,
|
||||
"AdvSceneSwitcher.action.source"});
|
||||
|
||||
const static std::map<MacroActionSource::Action, std::string> actionTypes = {
|
||||
{MacroActionSource::Action::ENABLE,
|
||||
"AdvSceneSwitcher.action.source.type.enable"},
|
||||
{MacroActionSource::Action::DISABLE,
|
||||
"AdvSceneSwitcher.action.source.type.disable"},
|
||||
{MacroActionSource::Action::SETTINGS,
|
||||
"AdvSceneSwitcher.action.source.type.settings"},
|
||||
{MacroActionSource::Action::REFRESH_SETTINGS,
|
||||
"AdvSceneSwitcher.action.source.type.refreshSettings"},
|
||||
{MacroActionSource::Action::SETTINGS_BUTTON,
|
||||
"AdvSceneSwitcher.action.source.type.pressSettingsButton"},
|
||||
{MacroActionSource::Action::DEINTERLACE_MODE,
|
||||
"AdvSceneSwitcher.action.source.type.deinterlaceMode"},
|
||||
{MacroActionSource::Action::DEINTERLACE_FIELD_ORDER,
|
||||
"AdvSceneSwitcher.action.source.type.deinterlaceOrder"},
|
||||
{MacroActionSource::Action::OPEN_INTERACTION_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.openInteractionDialog"},
|
||||
{MacroActionSource::Action::OPEN_FILTER_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.openFilterDialog"},
|
||||
{MacroActionSource::Action::OPEN_PROPERTIES_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.openPropertiesDialog"},
|
||||
};
|
||||
|
||||
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
|
||||
{OBS_DEINTERLACE_MODE_DISABLE,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
|
||||
{OBS_DEINTERLACE_MODE_DISCARD,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
|
||||
{OBS_DEINTERLACE_MODE_RETRO,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.retro"},
|
||||
{OBS_DEINTERLACE_MODE_BLEND,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.blend"},
|
||||
{OBS_DEINTERLACE_MODE_BLEND_2X,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.blend2x"},
|
||||
{OBS_DEINTERLACE_MODE_LINEAR,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.linear"},
|
||||
{OBS_DEINTERLACE_MODE_LINEAR_2X,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.linear2x"},
|
||||
{OBS_DEINTERLACE_MODE_YADIF,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.yadif"},
|
||||
{OBS_DEINTERLACE_MODE_YADIF_2X,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x"},
|
||||
};
|
||||
|
||||
const static std::map<obs_deinterlace_field_order, std::string>
|
||||
deinterlaceFieldOrders = {
|
||||
{OBS_DEINTERLACE_FIELD_ORDER_TOP,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst"},
|
||||
{OBS_DEINTERLACE_FIELD_ORDER_BOTTOM,
|
||||
"AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst"},
|
||||
};
|
||||
|
||||
const static std::map<MacroActionSource::SettingsInputMethod, std::string>
|
||||
inputMethods = {
|
||||
{MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL,
|
||||
"AdvSceneSwitcher.action.source.inputMethod.individualManual"},
|
||||
{MacroActionSource::SettingsInputMethod::INDIVIDUAL_TEMPVAR,
|
||||
"AdvSceneSwitcher.action.source.inputMethod.individualTempvar"},
|
||||
{MacroActionSource::SettingsInputMethod::JSON_STRING,
|
||||
"AdvSceneSwitcher.action.source.inputMethod.json"},
|
||||
};
|
||||
|
||||
static std::vector<SourceSettingButton> getSourceButtons(OBSWeakSource source)
|
||||
{
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
std::vector<SourceSettingButton> buttons;
|
||||
obs_properties_t *sourceProperties = obs_source_properties(s);
|
||||
auto it = obs_properties_first(sourceProperties);
|
||||
do {
|
||||
if (!it || obs_property_get_type(it) != OBS_PROPERTY_BUTTON) {
|
||||
continue;
|
||||
}
|
||||
SourceSettingButton button = {obs_property_name(it),
|
||||
obs_property_description(it)};
|
||||
buttons.emplace_back(button);
|
||||
} while (obs_property_next(&it));
|
||||
obs_source_release(s);
|
||||
obs_properties_destroy(sourceProperties);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
static void pressSourceButton(const SourceSettingButton &button,
|
||||
obs_source_t *source)
|
||||
{
|
||||
obs_properties_t *sourceProperties = obs_source_properties(source);
|
||||
obs_property_t *property =
|
||||
obs_properties_get(sourceProperties, button.id.c_str());
|
||||
if (!obs_property_button_clicked(property, source)) {
|
||||
blog(LOG_WARNING, "Failed to press settings button '%s' for %s",
|
||||
button.id.c_str(), obs_source_get_name(source));
|
||||
}
|
||||
obs_properties_destroy(sourceProperties);
|
||||
}
|
||||
|
||||
static void refreshSourceSettings(obs_source_t *s)
|
||||
{
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_data_t *data = obs_source_get_settings(s);
|
||||
obs_source_update(s, data);
|
||||
obs_data_release(data);
|
||||
|
||||
// Refresh of browser sources based on:
|
||||
// https://github.com/obsproject/obs-websocket/pull/666/files
|
||||
if (strcmp(obs_source_get_id(s), "browser_source") == 0) {
|
||||
obs_properties_t *sourceProperties = obs_source_properties(s);
|
||||
obs_property_t *property =
|
||||
obs_properties_get(sourceProperties, "refreshnocache");
|
||||
obs_property_button_clicked(property, s);
|
||||
obs_properties_destroy(sourceProperties);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isInteractable(obs_source_t *source)
|
||||
{
|
||||
uint32_t flags = obs_source_get_output_flags(source);
|
||||
return (flags & OBS_SOURCE_INTERACTION) != 0;
|
||||
}
|
||||
|
||||
bool MacroActionSource::PerformAction()
|
||||
{
|
||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||
switch (_action) {
|
||||
case Action::ENABLE:
|
||||
obs_source_set_enabled(s, true);
|
||||
break;
|
||||
case Action::DISABLE:
|
||||
obs_source_set_enabled(s, false);
|
||||
break;
|
||||
case Action::SETTINGS:
|
||||
switch (_settingsInputMethod) {
|
||||
case SettingsInputMethod::INDIVIDUAL_MANUAL:
|
||||
SetSourceSetting(s, _setting, _manualSettingValue);
|
||||
break;
|
||||
case SettingsInputMethod::INDIVIDUAL_TEMPVAR: {
|
||||
auto var = _tempVar.GetTempVariable(GetMacro());
|
||||
if (!var) {
|
||||
break;
|
||||
}
|
||||
auto value = var->Value();
|
||||
if (!value) {
|
||||
break;
|
||||
}
|
||||
SetSourceSetting(s, _setting, *value);
|
||||
break;
|
||||
}
|
||||
case SettingsInputMethod::JSON_STRING:
|
||||
SetSourceSettings(s, _settingsString);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Action::REFRESH_SETTINGS:
|
||||
refreshSourceSettings(s);
|
||||
break;
|
||||
case Action::SETTINGS_BUTTON:
|
||||
pressSourceButton(_button, s);
|
||||
break;
|
||||
case Action::DEINTERLACE_MODE:
|
||||
obs_source_set_deinterlace_mode(s, _deinterlaceMode);
|
||||
break;
|
||||
case Action::DEINTERLACE_FIELD_ORDER:
|
||||
obs_source_set_deinterlace_field_order(s, _deinterlaceOrder);
|
||||
break;
|
||||
case Action::OPEN_INTERACTION_DIALOG:
|
||||
if (isInteractable(s)) {
|
||||
obs_frontend_open_source_interaction(s);
|
||||
} else {
|
||||
blog(LOG_INFO,
|
||||
"refusing to open interaction dialog "
|
||||
"for non intractable source \"%s\"",
|
||||
_source.ToString().c_str());
|
||||
}
|
||||
break;
|
||||
case Action::OPEN_FILTER_DIALOG:
|
||||
obs_frontend_open_source_filters(s);
|
||||
break;
|
||||
case Action::OPEN_PROPERTIES_DIALOG:
|
||||
obs_frontend_open_source_properties(s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_source_release(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSource::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\" for Source \"%s\"",
|
||||
it->second.c_str(), _source.ToString(true).c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown source action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSource::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_button.Save(obj);
|
||||
obs_data_set_int(obj, "inputMethod",
|
||||
static_cast<int>(_settingsInputMethod));
|
||||
_setting.Save(obj);
|
||||
_manualSettingValue.Save(obj, "manualSettingValue");
|
||||
_tempVar.Save(obj);
|
||||
_settingsString.Save(obj, "settings");
|
||||
obs_data_set_int(obj, "deinterlaceMode",
|
||||
static_cast<int>(_deinterlaceMode));
|
||||
obs_data_set_int(obj, "deinterlaceOrder",
|
||||
static_cast<int>(_deinterlaceOrder));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSource::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_source.Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_button.Load(obj);
|
||||
// TODO: Remove fallback in future version
|
||||
if (obs_data_has_user_value(obj, "inputMethod")) {
|
||||
_settingsInputMethod = static_cast<SettingsInputMethod>(
|
||||
obs_data_get_int(obj, "inputMethod"));
|
||||
} else {
|
||||
_settingsInputMethod = SettingsInputMethod::JSON_STRING;
|
||||
}
|
||||
_setting.Load(obj);
|
||||
_settingsString.Load(obj, "settings");
|
||||
_manualSettingValue.Load(obj, "manualSettingValue");
|
||||
_tempVar.Load(obj, GetMacro());
|
||||
_deinterlaceMode = static_cast<obs_deinterlace_mode>(
|
||||
obs_data_get_int(obj, "deinterlaceMode"));
|
||||
_deinterlaceOrder = static_cast<obs_deinterlace_field_order>(
|
||||
obs_data_get_int(obj, "deinterlaceOrder"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSource::GetShortDesc() const
|
||||
{
|
||||
_source.ToString();
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto &[actionType, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
if (actionType == MacroActionSource::Action::REFRESH_SETTINGS) {
|
||||
list->setItemData(
|
||||
list->count() - 1,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.source.type.refreshSettings.tooltip"),
|
||||
Qt::ToolTipRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateSourceButtonSelection(QComboBox *list,
|
||||
OBSWeakSource source)
|
||||
{
|
||||
list->clear();
|
||||
auto buttons = getSourceButtons(source);
|
||||
if (buttons.empty()) {
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.source.noSettingsButtons"));
|
||||
}
|
||||
|
||||
for (const auto &button : buttons) {
|
||||
QVariant value;
|
||||
value.setValue(button);
|
||||
list->addItem(QString::fromStdString(button.ToString()), value);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateDeinterlaceModeSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (const auto &[value, name] : deinterlaceModes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateDeinterlaceFieldOrderSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (const auto &[value, name] : deinterlaceFieldOrders) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateSettingsInputMethods(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (const auto &[value, name] : inputMethods) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSourceEdit::MacroActionSourceEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSource> entryData)
|
||||
: QWidget(parent),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_actions(new QComboBox()),
|
||||
_settingsButtons(new QComboBox()),
|
||||
_settingsLayout(new QHBoxLayout()),
|
||||
_settingsInputMethods(new QComboBox(this)),
|
||||
_manualSettingValue(new VariableTextEdit(this, 5, 1, 1)),
|
||||
_tempVars(new TempVariableSelection(this)),
|
||||
_sourceSettings(new SourceSettingSelection(this)),
|
||||
_settingsString(new VariableTextEdit(this)),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.source.getSettings"))),
|
||||
_deinterlaceMode(new QComboBox()),
|
||||
_deinterlaceOrder(new QComboBox()),
|
||||
_warning(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.source.warning")))
|
||||
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
auto sources = GetSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
populateDeinterlaceModeSelection(_deinterlaceMode);
|
||||
populateDeinterlaceFieldOrderSelection(_deinterlaceOrder);
|
||||
populateSettingsInputMethods(_settingsInputMethods);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_settingsButtons, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(ButtonChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settingsString, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsStringChanged()));
|
||||
QWidget::connect(_deinterlaceMode, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(DeinterlaceModeChanged(int)));
|
||||
QWidget::connect(_deinterlaceOrder, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(DeinterlaceOrderChanged(int)));
|
||||
QWidget::connect(_tempVars,
|
||||
SIGNAL(SelectionChanged(const TempVariableRef &)),
|
||||
this, SLOT(SelectionChanged(const TempVariableRef &)));
|
||||
QWidget::connect(_settingsInputMethods,
|
||||
SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SettingsInputMethodChanged(int)));
|
||||
QWidget::connect(_manualSettingValue, SIGNAL(textChanged()), this,
|
||||
SLOT(ManualSettingsValueChanged()));
|
||||
QWidget::connect(_sourceSettings,
|
||||
SIGNAL(SelectionChanged(const SourceSetting &)), this,
|
||||
SLOT(SelectionChanged(const SourceSetting &)));
|
||||
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
entryLayout->setContentsMargins(0, 0, 0, 0);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sources}}", _sources},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{settings}}", _sourceSettings},
|
||||
{"{{settingsInputMethod}}", _settingsInputMethods},
|
||||
{"{{settingValue}}", _manualSettingValue},
|
||||
{"{{tempVar}}", _tempVars},
|
||||
{"{{getSettings}}", _getSettings},
|
||||
{"{{settingsButtons}}", _settingsButtons},
|
||||
{"{{deinterlaceMode}}", _deinterlaceMode},
|
||||
{"{{deinterlaceOrder}}", _deinterlaceOrder},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.source.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
_settingsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.source.entry.settings"),
|
||||
_settingsLayout, widgetPlaceholders);
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addLayout(_settingsLayout);
|
||||
mainLayout->addWidget(_warning);
|
||||
mainLayout->addWidget(_settingsString);
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->setContentsMargins(0, 0, 0, 0);
|
||||
buttonLayout->addWidget(_getSettings);
|
||||
buttonLayout->addStretch();
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
populateSourceButtonSelection(_settingsButtons,
|
||||
_entryData->_source.GetSource());
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_sourceSettings->SetSource(_entryData->_source.GetSource());
|
||||
_sourceSettings->SetSetting(_entryData->_setting);
|
||||
_settingsButtons->setCurrentText(
|
||||
QString::fromStdString(_entryData->_button.ToString()));
|
||||
_settingsString->setPlainText(_entryData->_settingsString);
|
||||
_deinterlaceMode->setCurrentIndex(_deinterlaceMode->findData(
|
||||
static_cast<int>(_entryData->_deinterlaceMode)));
|
||||
_deinterlaceOrder->setCurrentIndex(_deinterlaceOrder->findData(
|
||||
static_cast<int>(_entryData->_deinterlaceOrder)));
|
||||
_settingsInputMethods->setCurrentIndex(_settingsInputMethods->findData(
|
||||
static_cast<int>(_entryData->_settingsInputMethod)));
|
||||
_tempVars->SetVariable(_entryData->_tempVar);
|
||||
_manualSettingValue->setPlainText(_entryData->_manualSettingValue);
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = source;
|
||||
}
|
||||
populateSourceButtonSelection(_settingsButtons,
|
||||
_entryData->_source.GetSource());
|
||||
_sourceSettings->SetSource(_entryData->_source.GetSource());
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionSource::Action>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::ButtonChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_button = qvariant_cast<SourceSettingButton>(
|
||||
_settingsButtons->itemData(idx));
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::GetSettingsClicked()
|
||||
{
|
||||
if (_loading || !_entryData || !_entryData->_source.GetSource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_entryData->_settingsInputMethod) {
|
||||
case MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL:
|
||||
_manualSettingValue->setPlainText(GetSourceSettingValue(
|
||||
_entryData->_source.GetSource(), _entryData->_setting));
|
||||
break;
|
||||
case MacroActionSource::SettingsInputMethod::INDIVIDUAL_TEMPVAR:
|
||||
break;
|
||||
case MacroActionSource::SettingsInputMethod::JSON_STRING:
|
||||
_settingsString->setPlainText(FormatJsonString(
|
||||
GetSourceSettings(_entryData->_source.GetSource())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SettingsStringChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settingsString =
|
||||
_settingsString->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::DeinterlaceModeChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_deinterlaceMode = static_cast<obs_deinterlace_mode>(
|
||||
_deinterlaceMode->itemData(idx).toInt());
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::DeinterlaceOrderChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_deinterlaceOrder =
|
||||
static_cast<obs_deinterlace_field_order>(
|
||||
_deinterlaceOrder->itemData(idx).toInt());
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_tempVar = var;
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settingsInputMethod =
|
||||
static_cast<MacroActionSource::SettingsInputMethod>(
|
||||
_settingsInputMethods->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SelectionChanged(const SourceSetting &setting)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setting = setting;
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::ManualSettingsValueChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_manualSettingValue =
|
||||
_manualSettingValue->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
{
|
||||
SetLayoutVisible(_settingsLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS);
|
||||
_sourceSettings->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING);
|
||||
_settingsString->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING);
|
||||
_getSettings->setVisible(_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS);
|
||||
_tempVars->setVisible(_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
|
||||
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL) {
|
||||
RemoveStretchIfPresent(_settingsLayout);
|
||||
_manualSettingValue->show();
|
||||
} else {
|
||||
AddStretchIfNecessary(_settingsLayout);
|
||||
_manualSettingValue->hide();
|
||||
}
|
||||
|
||||
const bool showWarning =
|
||||
_entryData->_action == MacroActionSource::Action::ENABLE ||
|
||||
_entryData->_action == MacroActionSource::Action::DISABLE;
|
||||
_warning->setVisible(showWarning);
|
||||
_settingsButtons->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS_BUTTON);
|
||||
_deinterlaceMode->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::DEINTERLACE_MODE);
|
||||
_deinterlaceOrder->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
bool SourceSettingButton::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_string(data, "id", id.c_str());
|
||||
obs_data_set_string(data, "description", description.c_str());
|
||||
obs_data_set_obj(obj, "sourceSettingButton", data);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceSettingButton::Load(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, "sourceSettingButton");
|
||||
id = obs_data_get_string(data, "id");
|
||||
description = obs_data_get_string(data, "description");
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SourceSettingButton::ToString() const
|
||||
{
|
||||
if (id.empty()) {
|
||||
return "";
|
||||
}
|
||||
return "[" + id + "] " + description;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
127
plugins/base/macro-action-source.hpp
Normal file
127
plugins/base/macro-action-source.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "source-setting.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct SourceSettingButton {
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string ToString() const;
|
||||
|
||||
std::string id = "";
|
||||
std::string description = "";
|
||||
};
|
||||
|
||||
class MacroActionSource : public MacroAction {
|
||||
public:
|
||||
MacroActionSource(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSource>(m);
|
||||
}
|
||||
|
||||
SourceSelection _source;
|
||||
SourceSettingButton _button;
|
||||
StringVariable _settingsString = "";
|
||||
StringVariable _manualSettingValue = "";
|
||||
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
|
||||
obs_deinterlace_field_order _deinterlaceOrder =
|
||||
OBS_DEINTERLACE_FIELD_ORDER_TOP;
|
||||
TempVariableRef _tempVar;
|
||||
SourceSetting _setting;
|
||||
|
||||
enum class Action {
|
||||
ENABLE,
|
||||
DISABLE,
|
||||
SETTINGS,
|
||||
REFRESH_SETTINGS,
|
||||
SETTINGS_BUTTON,
|
||||
DEINTERLACE_MODE,
|
||||
DEINTERLACE_FIELD_ORDER,
|
||||
OPEN_INTERACTION_DIALOG,
|
||||
OPEN_FILTER_DIALOG,
|
||||
OPEN_PROPERTIES_DIALOG,
|
||||
};
|
||||
Action _action = Action::SETTINGS;
|
||||
|
||||
enum class SettingsInputMethod {
|
||||
INDIVIDUAL_MANUAL,
|
||||
INDIVIDUAL_TEMPVAR,
|
||||
JSON_STRING,
|
||||
};
|
||||
SettingsInputMethod _settingsInputMethod =
|
||||
SettingsInputMethod::INDIVIDUAL_MANUAL;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSourceEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSourceEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSource> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSourceEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSource>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void ActionChanged(int value);
|
||||
void ButtonChanged(int idx);
|
||||
void GetSettingsClicked();
|
||||
void SettingsStringChanged();
|
||||
void DeinterlaceModeChanged(int);
|
||||
void DeinterlaceOrderChanged(int);
|
||||
void SelectionChanged(const TempVariableRef &);
|
||||
void SettingsInputMethodChanged(int);
|
||||
void SelectionChanged(const SourceSetting &);
|
||||
void ManualSettingsValueChanged();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SourceSelectionWidget *_sources;
|
||||
QComboBox *_actions;
|
||||
QComboBox *_settingsButtons;
|
||||
QHBoxLayout *_settingsLayout;
|
||||
QComboBox *_settingsInputMethods;
|
||||
VariableTextEdit *_manualSettingValue;
|
||||
TempVariableSelection *_tempVars;
|
||||
SourceSettingSelection *_sourceSettings;
|
||||
VariableTextEdit *_settingsString;
|
||||
QPushButton *_getSettings;
|
||||
QComboBox *_deinterlaceMode;
|
||||
QComboBox *_deinterlaceOrder;
|
||||
QLabel *_warning;
|
||||
|
||||
std::shared_ptr<MacroActionSource> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
298
plugins/base/macro-action-streaming.cpp
Normal file
298
plugins/base/macro-action-streaming.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "macro-action-streaming.hpp"
|
||||
#include "profile-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <util/config-file.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionStream::id = "streaming";
|
||||
|
||||
bool MacroActionStream::_registered = MacroActionFactory::Register(
|
||||
MacroActionStream::id,
|
||||
{MacroActionStream::Create, MacroActionStreamEdit::Create,
|
||||
"AdvSceneSwitcher.action.streaming"});
|
||||
|
||||
const static std::map<MacroActionStream::Action, std::string> actionTypes = {
|
||||
{MacroActionStream::Action::STOP,
|
||||
"AdvSceneSwitcher.action.streaming.type.stop"},
|
||||
{MacroActionStream::Action::START,
|
||||
"AdvSceneSwitcher.action.streaming.type.start"},
|
||||
{MacroActionStream::Action::KEYFRAME_INTERVAL,
|
||||
"AdvSceneSwitcher.action.streaming.type.keyFrameInterval"},
|
||||
{MacroActionStream::Action::SERVER,
|
||||
"AdvSceneSwitcher.action.streaming.type.server"},
|
||||
{MacroActionStream::Action::STREAM_KEY,
|
||||
"AdvSceneSwitcher.action.streaming.type.streamKey"},
|
||||
{MacroActionStream::Action::USERNAME,
|
||||
"AdvSceneSwitcher.action.streaming.type.username"},
|
||||
{MacroActionStream::Action::PASSWORD,
|
||||
"AdvSceneSwitcher.action.streaming.type.password"},
|
||||
};
|
||||
|
||||
constexpr int streamStartCooldown = 5;
|
||||
std::chrono::high_resolution_clock::time_point MacroActionStream::s_lastAttempt =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
|
||||
void MacroActionStream::SetKeyFrameInterval() const
|
||||
{
|
||||
const auto configPath = GetPathInProfileDir("streamEncoder.json");
|
||||
auto settings =
|
||||
obs_data_create_from_json_file_safe(configPath.c_str(), "bak");
|
||||
if (!settings) {
|
||||
blog(LOG_WARNING, "failed to set key frame interval");
|
||||
return;
|
||||
}
|
||||
obs_data_set_int(settings, "keyint_sec", _keyFrameInterval);
|
||||
obs_data_save_json_safe(settings, configPath.c_str(), "tmp", "bak");
|
||||
obs_data_release(settings);
|
||||
}
|
||||
|
||||
void MacroActionStream::SetStreamSettingsValue(const char *name,
|
||||
const std::string &value,
|
||||
bool enableAuth) const
|
||||
{
|
||||
const auto configPath = GetPathInProfileDir("service.json");
|
||||
auto data =
|
||||
obs_data_create_from_json_file_safe(configPath.c_str(), "bak");
|
||||
if (!data) {
|
||||
blog(LOG_WARNING, "failed to set %s", name);
|
||||
return;
|
||||
}
|
||||
auto settings = obs_data_get_obj(data, "settings");
|
||||
if (!settings) {
|
||||
blog(LOG_WARNING, "failed to set %s", name);
|
||||
obs_data_release(data);
|
||||
return;
|
||||
}
|
||||
obs_data_set_string(settings, name, value.c_str());
|
||||
if (enableAuth) {
|
||||
obs_data_set_bool(settings, "use_auth", true);
|
||||
}
|
||||
obs_data_set_obj(data, "settings", settings);
|
||||
auto service = obs_frontend_get_streaming_service();
|
||||
obs_service_update(service, settings);
|
||||
obs_frontend_save_streaming_service();
|
||||
obs_frontend_set_streaming_service(service);
|
||||
obs_data_release(settings);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
bool MacroActionStream::CooldownDurationReached() const
|
||||
{
|
||||
auto timePassed = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::high_resolution_clock::now() - s_lastAttempt);
|
||||
return timePassed.count() >= streamStartCooldown;
|
||||
}
|
||||
|
||||
bool MacroActionStream::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case Action::STOP:
|
||||
if (obs_frontend_streaming_active()) {
|
||||
obs_frontend_streaming_stop();
|
||||
}
|
||||
break;
|
||||
case Action::START:
|
||||
if (!obs_frontend_streaming_active() &&
|
||||
CooldownDurationReached()) {
|
||||
obs_frontend_streaming_start();
|
||||
s_lastAttempt =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
break;
|
||||
case Action::KEYFRAME_INTERVAL:
|
||||
SetKeyFrameInterval();
|
||||
break;
|
||||
case Action::SERVER:
|
||||
SetStreamSettingsValue("server", _stringValue);
|
||||
break;
|
||||
case Action::STREAM_KEY:
|
||||
SetStreamSettingsValue("key", _stringValue);
|
||||
break;
|
||||
case Action::USERNAME:
|
||||
SetStreamSettingsValue("username", _stringValue, true);
|
||||
break;
|
||||
case Action::PASSWORD:
|
||||
SetStreamSettingsValue("password", _stringValue, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionStream::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown streaming action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionStream::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_keyFrameInterval.Save(obj, "keyFrameInterval");
|
||||
_stringValue.Save(obj, "stringValue");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionStream::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_keyFrameInterval.Load(obj, "keyFrameInterval");
|
||||
_stringValue.Load(obj, "stringValue");
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionStreamEdit::MacroActionStreamEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionStream> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_keyFrameInterval(new VariableSpinBox()),
|
||||
_stringValue(new VariableLineEdit(this)),
|
||||
_showPassword(new QPushButton()),
|
||||
_layout(new QHBoxLayout())
|
||||
{
|
||||
_keyFrameInterval->setMinimum(0);
|
||||
_keyFrameInterval->setMaximum(25);
|
||||
|
||||
_showPassword->setMaximumWidth(22);
|
||||
_showPassword->setFlat(true);
|
||||
_showPassword->setStyleSheet(
|
||||
"QPushButton { background-color: transparent; border: 0px }");
|
||||
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(
|
||||
_keyFrameInterval,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this,
|
||||
SLOT(KeyFrameIntervalChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_stringValue, SIGNAL(editingFinished()), this,
|
||||
SLOT(StringValueChanged()));
|
||||
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
|
||||
SLOT(ShowPassword()));
|
||||
QWidget::connect(_showPassword, SIGNAL(released()), this,
|
||||
SLOT(HidePassword()));
|
||||
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.streaming.entry"),
|
||||
_layout,
|
||||
{{"{{actions}}", _actions},
|
||||
{"{{keyFrameInterval}}", _keyFrameInterval},
|
||||
{"{{stringValue}}", _stringValue},
|
||||
{"{{showPassword}}", _showPassword}});
|
||||
setLayout(_layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_keyFrameInterval->SetValue(_entryData->_keyFrameInterval);
|
||||
_stringValue->setText(_entryData->_stringValue);
|
||||
SetWidgetVisiblity();
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::KeyFrameIntervalChanged(
|
||||
const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_keyFrameInterval = value;
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::StringValueChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_stringValue = _stringValue->text().toStdString();
|
||||
SetWidgetVisiblity();
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::ShowPassword()
|
||||
{
|
||||
SetButtonIcon(_showPassword, ":res/images/visible.svg");
|
||||
_stringValue->setEchoMode(QLineEdit::Normal);
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::HidePassword()
|
||||
{
|
||||
SetButtonIcon(_showPassword, ":res/images/invisible.svg");
|
||||
_stringValue->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::SetWidgetVisiblity()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto action = _entryData->_action;
|
||||
_keyFrameInterval->setVisible(
|
||||
action == MacroActionStream::Action::KEYFRAME_INTERVAL);
|
||||
const bool modifyingStringValue =
|
||||
action == MacroActionStream::Action::SERVER ||
|
||||
action == MacroActionStream::Action::STREAM_KEY ||
|
||||
action == MacroActionStream::Action::USERNAME ||
|
||||
action == MacroActionStream::Action::PASSWORD;
|
||||
_stringValue->setVisible(modifyingStringValue);
|
||||
if (modifyingStringValue) {
|
||||
// Give as much space to string value line edit as posssible
|
||||
RemoveStretchIfPresent(_layout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_layout);
|
||||
}
|
||||
if (action == MacroActionStream::Action::PASSWORD ||
|
||||
action == MacroActionStream::Action::STREAM_KEY) {
|
||||
_stringValue->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
_showPassword->show();
|
||||
HidePassword();
|
||||
} else {
|
||||
_stringValue->setEchoMode(QLineEdit::Normal);
|
||||
_showPassword->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionStreamEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_action =
|
||||
static_cast<MacroActionStream::Action>(value);
|
||||
}
|
||||
_stringValue->setText(QString(""));
|
||||
SetWidgetVisiblity();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
87
plugins/base/macro-action-streaming.hpp
Normal file
87
plugins/base/macro-action-streaming.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <chrono>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionStream : public MacroAction {
|
||||
public:
|
||||
MacroActionStream(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionStream>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
STOP,
|
||||
START,
|
||||
KEYFRAME_INTERVAL,
|
||||
SERVER,
|
||||
STREAM_KEY,
|
||||
USERNAME,
|
||||
PASSWORD,
|
||||
};
|
||||
Action _action = Action::STOP;
|
||||
IntVariable _keyFrameInterval = 0;
|
||||
StringVariable _stringValue;
|
||||
|
||||
private:
|
||||
void SetKeyFrameInterval() const;
|
||||
void SetStreamSettingsValue(const char *name, const std::string &value,
|
||||
bool enableAuth = false) const;
|
||||
bool CooldownDurationReached() const;
|
||||
static std::chrono::high_resolution_clock::time_point s_lastAttempt;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionStreamEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionStreamEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionStream> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionStreamEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionStream>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void KeyFrameIntervalChanged(const NumberVariable<int> &);
|
||||
void StringValueChanged();
|
||||
void ShowPassword();
|
||||
void HidePassword();
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
VariableSpinBox *_keyFrameInterval;
|
||||
VariableLineEdit *_stringValue;
|
||||
QPushButton *_showPassword;
|
||||
std::shared_ptr<MacroActionStream> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisiblity();
|
||||
|
||||
QHBoxLayout *_layout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
185
plugins/base/macro-action-studio-mode.cpp
Normal file
185
plugins/base/macro-action-studio-mode.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "macro-action-studio-mode.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSudioMode::id = "studio_mode";
|
||||
|
||||
bool MacroActionSudioMode::_registered = MacroActionFactory::Register(
|
||||
MacroActionSudioMode::id,
|
||||
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
|
||||
"AdvSceneSwitcher.action.studioMode"});
|
||||
|
||||
const static std::map<MacroActionSudioMode::Action, std::string> actionTypes = {
|
||||
{MacroActionSudioMode::Action::SWAP_SCENE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.swap"},
|
||||
{MacroActionSudioMode::Action::SET_SCENE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.setScene"},
|
||||
{MacroActionSudioMode::Action::ENABLE_STUDIO_MODE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.enable"},
|
||||
{MacroActionSudioMode::Action::DISABLE_STUDIO_MODE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.disable"},
|
||||
};
|
||||
|
||||
// Calling obs_frontend_set_preview_program_mode() directly from a thread that
|
||||
// is not the main OBS UI thread will lead to undefined behaviour, so we have
|
||||
// to use this helper function instead - copied from obs-websocket plugin
|
||||
static void enableStudioMode(bool enable)
|
||||
{
|
||||
if (obs_frontend_preview_program_mode_active() != enable) {
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
[](void *param) {
|
||||
auto studioModeEnabled = (bool *)param;
|
||||
obs_frontend_set_preview_program_mode(
|
||||
*studioModeEnabled);
|
||||
},
|
||||
&enable, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSudioMode::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case Action::SWAP_SCENE:
|
||||
obs_frontend_preview_program_trigger_transition();
|
||||
break;
|
||||
case Action::SET_SCENE: {
|
||||
auto s = obs_weak_source_get_source(_scene.GetScene());
|
||||
obs_frontend_set_current_preview_scene(s);
|
||||
obs_source_release(s);
|
||||
break;
|
||||
}
|
||||
case Action::ENABLE_STUDIO_MODE:
|
||||
enableStudioMode(true);
|
||||
break;
|
||||
case Action::DISABLE_STUDIO_MODE:
|
||||
enableStudioMode(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSudioMode::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\" with scene \"%s\"",
|
||||
it->second.c_str(), _scene.ToString(true).c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown studio mode action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionSudioMode::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_scene.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSudioMode::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_scene.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSudioMode::GetShortDesc() const
|
||||
{
|
||||
if (_action == Action::SET_SCENE) {
|
||||
return _scene.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[id, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSudioModeEdit::MacroActionSudioModeEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSudioMode> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true, true))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{actions}}", _actions},
|
||||
{"{{scenes}}", _scenes},
|
||||
};
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.studioMode.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::SetWidgetVisibility()
|
||||
{
|
||||
_scenes->setVisible(_entryData->_action ==
|
||||
MacroActionSudioMode::Action::SET_SCENE);
|
||||
|
||||
if (_entryData->_action != MacroActionSudioMode::Action::SET_SCENE) {
|
||||
_actions->removeItem(_actions->findData(static_cast<int>(
|
||||
MacroActionSudioMode::Action::SET_SCENE)));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::ActionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionSudioMode::Action>(
|
||||
_actions->itemData(index).toInt());
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
68
plugins/base/macro-action-studio-mode.hpp
Normal file
68
plugins/base/macro-action-studio-mode.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSudioMode : public MacroAction {
|
||||
public:
|
||||
MacroActionSudioMode(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSudioMode>(m);
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
SWAP_SCENE,
|
||||
SET_SCENE, // TODO: Remove in future version as the
|
||||
// functionality moved to the scene switch action
|
||||
ENABLE_STUDIO_MODE,
|
||||
DISABLE_STUDIO_MODE,
|
||||
};
|
||||
Action _action = Action::SWAP_SCENE;
|
||||
SceneSelection _scene;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSudioModeEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSudioModeEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSudioMode> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSudioModeEdit(
|
||||
parent, std::dynamic_pointer_cast<MacroActionSudioMode>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_actions;
|
||||
SceneSelectionWidget *_scenes;
|
||||
std::shared_ptr<MacroActionSudioMode> _entryData;
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
121
plugins/base/macro-action-systray.cpp
Normal file
121
plugins/base/macro-action-systray.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "macro-action-systray.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSystray::id = "systray_notification";
|
||||
|
||||
bool MacroActionSystray::_registered = MacroActionFactory::Register(
|
||||
MacroActionSystray::id,
|
||||
{MacroActionSystray::Create, MacroActionSystrayEdit::Create,
|
||||
"AdvSceneSwitcher.action.systray"});
|
||||
|
||||
bool MacroActionSystray::PerformAction()
|
||||
{
|
||||
if (_lastPath != std::string(_iconPath)) {
|
||||
_lastPath = _iconPath;
|
||||
_icon = QIcon(QString::fromStdString(_iconPath));
|
||||
}
|
||||
DisplayTrayMessage(QString::fromStdString(_title),
|
||||
QString::fromStdString(_message), _icon);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionSystray::LogAction() const
|
||||
{
|
||||
vblog(LOG_INFO, "display systray message \"%s\":\n%s", _title.c_str(),
|
||||
_message.c_str());
|
||||
}
|
||||
|
||||
bool MacroActionSystray::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_message.Save(obj, "message");
|
||||
_title.Save(obj, "title");
|
||||
_iconPath.Save(obj, "icon");
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionSystray::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_message.Load(obj, "message");
|
||||
_title.Load(obj, "title");
|
||||
_iconPath.Load(obj, "icon");
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_title = obs_module_text("AdvSceneSwitcher.pluginName");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MacroActionSystrayEdit::MacroActionSystrayEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSystray> entryData)
|
||||
: QWidget(parent),
|
||||
_message(new VariableLineEdit(this)),
|
||||
_title(new VariableLineEdit(this)),
|
||||
_iconPath(new FileSelection())
|
||||
{
|
||||
_iconPath->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.action.systray.iconHint"));
|
||||
|
||||
QWidget::connect(_message, SIGNAL(editingFinished()), this,
|
||||
SLOT(MessageChanged()));
|
||||
QWidget::connect(_title, SIGNAL(editingFinished()), this,
|
||||
SLOT(TitleChanged()));
|
||||
QWidget::connect(_iconPath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(IconPathChanged(const QString &)));
|
||||
|
||||
auto layout = new QGridLayout();
|
||||
layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.systray.title")),
|
||||
0, 0);
|
||||
layout->addWidget(_title, 0, 1);
|
||||
layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.systray.message")),
|
||||
1, 0);
|
||||
layout->addWidget(_message, 1, 1);
|
||||
layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.systray.icon")),
|
||||
2, 0);
|
||||
layout->addWidget(_iconPath, 2, 1);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
_message->setText(_entryData->_message);
|
||||
_title->setText(_entryData->_title);
|
||||
_iconPath->SetPath(_entryData->_iconPath);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionSystrayEdit::TitleChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_title = _title->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionSystrayEdit::IconPathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_iconPath = text.toStdString();
|
||||
}
|
||||
|
||||
void MacroActionSystrayEdit::MessageChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = _message->text().toStdString();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
65
plugins/base/macro-action-systray.hpp
Normal file
65
plugins/base/macro-action-systray.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable-string.hpp"
|
||||
#include "file-selection.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionSystray : public MacroAction {
|
||||
public:
|
||||
MacroActionSystray(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionSystray>(m);
|
||||
}
|
||||
|
||||
StringVariable _message = "";
|
||||
StringVariable _title = obs_module_text("AdvSceneSwitcher.pluginName");
|
||||
StringVariable _iconPath = "";
|
||||
|
||||
private:
|
||||
QIcon _icon;
|
||||
std::string _lastPath;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionSystrayEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionSystrayEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionSystray> entryData = nullptr);
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionSystrayEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionSystray>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void MessageChanged();
|
||||
void TitleChanged();
|
||||
void IconPathChanged(const QString &text);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionSystray> _entryData;
|
||||
|
||||
private:
|
||||
VariableLineEdit *_message;
|
||||
VariableLineEdit *_title;
|
||||
FileSelection *_iconPath;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
212
plugins/base/macro-action-timer.cpp
Normal file
212
plugins/base/macro-action-timer.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#include "macro-action-timer.hpp"
|
||||
#include "macro-condition-timer.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionTimer::id = "timer";
|
||||
|
||||
bool MacroActionTimer::_registered = MacroActionFactory::Register(
|
||||
MacroActionTimer::id,
|
||||
{MacroActionTimer::Create, MacroActionTimerEdit::Create,
|
||||
"AdvSceneSwitcher.action.timer"});
|
||||
|
||||
const static std::map<TimerAction, std::string> timerActions = {
|
||||
{TimerAction::PAUSE, "AdvSceneSwitcher.action.timer.type.pause"},
|
||||
{TimerAction::CONTINUE, "AdvSceneSwitcher.action.timer.type.continue"},
|
||||
{TimerAction::RESET, "AdvSceneSwitcher.action.timer.type.reset"},
|
||||
{TimerAction::SET_TIME_REMAINING,
|
||||
"AdvSceneSwitcher.action.timer.type.setTimeRemaining"},
|
||||
};
|
||||
|
||||
bool MacroActionTimer::PerformAction()
|
||||
{
|
||||
auto macro = _macro.GetMacro();
|
||||
if (!macro) {
|
||||
return true;
|
||||
}
|
||||
auto conditions = *GetMacroConditions(macro.get());
|
||||
for (auto condition : conditions) {
|
||||
if (condition->GetId() != id) {
|
||||
continue;
|
||||
}
|
||||
auto timerCondition =
|
||||
dynamic_cast<MacroConditionTimer *>(condition.get());
|
||||
if (!timerCondition) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (_actionType) {
|
||||
case TimerAction::PAUSE:
|
||||
timerCondition->Pause();
|
||||
break;
|
||||
case TimerAction::CONTINUE:
|
||||
timerCondition->Continue();
|
||||
break;
|
||||
case TimerAction::RESET:
|
||||
timerCondition->Reset();
|
||||
break;
|
||||
case TimerAction::SET_TIME_REMAINING:
|
||||
timerCondition->_duration.SetTimeRemaining(
|
||||
_duration.Seconds());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionTimer::LogAction() const
|
||||
{
|
||||
auto macro = _macro.GetMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
switch (_actionType) {
|
||||
case TimerAction::PAUSE:
|
||||
vblog(LOG_INFO, "paused timers on \"%s\"",
|
||||
GetMacroName(macro.get()).c_str());
|
||||
break;
|
||||
case TimerAction::CONTINUE:
|
||||
vblog(LOG_INFO, "continued timers on \"%s\"",
|
||||
GetMacroName(macro.get()).c_str());
|
||||
break;
|
||||
case TimerAction::RESET:
|
||||
vblog(LOG_INFO, "reset timers on \"%s\"",
|
||||
GetMacroName(macro.get()).c_str());
|
||||
break;
|
||||
case TimerAction::SET_TIME_REMAINING:
|
||||
vblog(LOG_INFO,
|
||||
"set time remaining of timers on \"%s\" to \"%s\"",
|
||||
GetMacroName(macro.get()).c_str(),
|
||||
_duration.ToString().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionTimer::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_macro.Save(obj);
|
||||
_duration.Save(obj);
|
||||
obs_data_set_int(obj, "actionType", static_cast<int>(_actionType));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionTimer::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_macro.Load(obj);
|
||||
_duration.Load(obj);
|
||||
_actionType =
|
||||
static_cast<TimerAction>(obs_data_get_int(obj, "actionType"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionTimer::GetShortDesc() const
|
||||
{
|
||||
return _macro.Name();
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : timerActions) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionTimerEdit::MacroActionTimerEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionTimer> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_macros = new MacroSelection(parent);
|
||||
_duration = new DurationSelection();
|
||||
_timerAction = new QComboBox();
|
||||
|
||||
populateTypeSelection(_timerAction);
|
||||
|
||||
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(MacroChanged(const QString &)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionTypeChanged(int)));
|
||||
|
||||
_mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{macros}}", _macros},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{timerAction}}", _timerAction},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"),
|
||||
_mainLayout, widgetPlaceholders);
|
||||
setLayout(_mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_macros->SetCurrentMacro(_entryData->_macro);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_timerAction->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_actionType));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::ActionTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_actionType = static_cast<TimerAction>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_duration->setVisible(_entryData->_actionType ==
|
||||
TimerAction::SET_TIME_REMAINING);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::MacroChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_macro = text;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
75
plugins/base/macro-action-timer.hpp
Normal file
75
plugins/base/macro-action-timer.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "macro-selection.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class TimerAction {
|
||||
PAUSE,
|
||||
CONTINUE,
|
||||
RESET,
|
||||
SET_TIME_REMAINING,
|
||||
};
|
||||
|
||||
class MacroActionTimer : public MacroRefAction {
|
||||
public:
|
||||
MacroActionTimer(Macro *m) : MacroAction(m), MacroRefAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionTimer>(m);
|
||||
}
|
||||
Duration _duration;
|
||||
TimerAction _actionType = TimerAction::PAUSE;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionTimerEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionTimerEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionTimer> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionTimerEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionTimer>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void MacroChanged(const QString &text);
|
||||
void DurationChanged(const Duration &value);
|
||||
void ActionTypeChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
MacroSelection *_macros;
|
||||
DurationSelection *_duration;
|
||||
QComboBox *_timerAction;
|
||||
std::shared_ptr<MacroActionTimer> _entryData;
|
||||
|
||||
private:
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
399
plugins/base/macro-action-transition.cpp
Normal file
399
plugins/base/macro-action-transition.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
#include "macro-action-transition.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionTransition::id = "transition";
|
||||
|
||||
bool MacroActionTransition::_registered = MacroActionFactory::Register(
|
||||
MacroActionTransition::id,
|
||||
{MacroActionTransition::Create, MacroActionTransitionEdit::Create,
|
||||
"AdvSceneSwitcher.action.transition"});
|
||||
|
||||
const static std::map<MacroActionTransition::Type, std::string> actionTypes = {
|
||||
{MacroActionTransition::Type::SCENE,
|
||||
"AdvSceneSwitcher.action.transition.type.scene"},
|
||||
{MacroActionTransition::Type::SCENE_OVERRIDE,
|
||||
"AdvSceneSwitcher.action.transition.type.sceneOverride"},
|
||||
{MacroActionTransition::Type::SOURCE_SHOW,
|
||||
"AdvSceneSwitcher.action.transition.type.sourceShow"},
|
||||
{MacroActionTransition::Type::SOURCE_HIDE,
|
||||
"AdvSceneSwitcher.action.transition.type.sourceHide"},
|
||||
};
|
||||
|
||||
void MacroActionTransition::SetSceneTransition()
|
||||
{
|
||||
if (_setTransitionType) {
|
||||
OBSSourceAutoRelease transition =
|
||||
obs_weak_source_get_source(_transition.GetTransition());
|
||||
obs_frontend_set_current_transition(transition);
|
||||
}
|
||||
if (_setDuration) {
|
||||
obs_frontend_set_transition_duration(_duration.Seconds() *
|
||||
1000);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTransition::SetTransitionOverride()
|
||||
{
|
||||
OBSSourceAutoRelease scene =
|
||||
obs_weak_source_get_source(_scene.GetScene());
|
||||
OBSDataAutoRelease data = obs_source_get_private_settings(scene);
|
||||
if (_setTransitionType) {
|
||||
obs_data_set_string(data, "transition",
|
||||
_transition.ToString().c_str());
|
||||
}
|
||||
if (_setDuration) {
|
||||
obs_data_set_int(data, "transition_duration",
|
||||
_duration.Milliseconds());
|
||||
}
|
||||
}
|
||||
|
||||
#if (LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)) && \
|
||||
(LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(28, 0, 0))
|
||||
static void obs_sceneitem_set_transition(obs_sceneitem_t *item, bool show,
|
||||
obs_source_t *transition)
|
||||
{
|
||||
if (show) {
|
||||
obs_sceneitem_set_show_transition(item, transition);
|
||||
} else {
|
||||
obs_sceneitem_set_hide_transition(item, transition);
|
||||
}
|
||||
}
|
||||
|
||||
static void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item,
|
||||
bool show,
|
||||
uint32_t duration_ms)
|
||||
{
|
||||
if (show) {
|
||||
obs_sceneitem_set_show_transition_duration(item, duration_ms);
|
||||
} else {
|
||||
obs_sceneitem_set_hide_transition_duration(item, duration_ms);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void setSceneItemTransition(const OBSSceneItem &item,
|
||||
const OBSSourceAutoRelease &transition,
|
||||
bool show)
|
||||
{
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(transition);
|
||||
if (!transition || !settings) {
|
||||
// Set transition to "None"
|
||||
obs_sceneitem_set_transition(item, show, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot share the transition source between
|
||||
// scene items without introducing strange graphical
|
||||
// artifacts so we have to create new ones here
|
||||
OBSSourceAutoRelease transitionSource = obs_source_create_private(
|
||||
obs_source_get_id(transition), obs_source_get_name(transition),
|
||||
settings);
|
||||
obs_sceneitem_set_transition(item, show, transitionSource);
|
||||
}
|
||||
|
||||
void MacroActionTransition::SetSourceTransition(bool show)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
OBSSourceAutoRelease transition =
|
||||
obs_weak_source_get_source(_transition.GetTransition());
|
||||
const auto items = _source.GetSceneItems(_scene);
|
||||
for (const auto &item : items) {
|
||||
if (_setTransitionType) {
|
||||
setSceneItemTransition(item, transition, show);
|
||||
}
|
||||
if (_setDuration) {
|
||||
obs_sceneitem_set_transition_duration(
|
||||
item, show, _duration.Milliseconds());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MacroActionTransition::PerformAction()
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::SCENE:
|
||||
SetSceneTransition();
|
||||
break;
|
||||
case Type::SCENE_OVERRIDE:
|
||||
SetTransitionOverride();
|
||||
break;
|
||||
case Type::SOURCE_SHOW:
|
||||
SetSourceTransition(true);
|
||||
break;
|
||||
case Type::SOURCE_HIDE:
|
||||
SetSourceTransition(false);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionTransition::LogAction() const
|
||||
{
|
||||
std::string msgBegin;
|
||||
switch (_type) {
|
||||
case Type::SCENE:
|
||||
msgBegin += "set scene transition";
|
||||
break;
|
||||
case Type::SCENE_OVERRIDE:
|
||||
msgBegin += "set scene override transition of " +
|
||||
_scene.ToString(true);
|
||||
break;
|
||||
case Type::SOURCE_SHOW:
|
||||
msgBegin += "set source show transition of " +
|
||||
_source.ToString(true) + " on scene " +
|
||||
_scene.ToString(true);
|
||||
break;
|
||||
case Type::SOURCE_HIDE:
|
||||
msgBegin += "set source hide transition of " +
|
||||
_source.ToString(true) + " on scene " +
|
||||
_scene.ToString(true);
|
||||
break;
|
||||
}
|
||||
if (_setDuration) {
|
||||
vblog(LOG_INFO, "%s duration to %s", msgBegin.c_str(),
|
||||
_duration.ToString().c_str());
|
||||
}
|
||||
if (_setTransitionType) {
|
||||
vblog(LOG_INFO, "%s type to \"%s\"", msgBegin.c_str(),
|
||||
_transition.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionTransition::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "actionType", static_cast<int>(_type));
|
||||
_source.Save(obj);
|
||||
_scene.Save(obj);
|
||||
_duration.Save(obj);
|
||||
_transition.Save(obj);
|
||||
obs_data_set_bool(obj, "setDuration", _setDuration);
|
||||
obs_data_set_bool(obj, "setType", _setTransitionType);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionTransition::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "actionType"));
|
||||
_source.Load(obj);
|
||||
_scene.Load(obj);
|
||||
_duration.Load(obj);
|
||||
_transition.Load(obj);
|
||||
_setDuration = obs_data_get_bool(obj, "setDuration");
|
||||
_setTransitionType = obs_data_get_bool(obj, "setType");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionTransition::GetShortDesc() const
|
||||
{
|
||||
std::string msgBegin;
|
||||
switch (_type) {
|
||||
case Type::SCENE:
|
||||
return _transition.ToString();
|
||||
case Type::SCENE_OVERRIDE:
|
||||
return _scene.ToString() + " - " + _transition.ToString();
|
||||
case Type::SOURCE_SHOW:
|
||||
return _scene.ToString() + " - " + _source.ToString() + " - " +
|
||||
_transition.ToString();
|
||||
case Type::SOURCE_HIDE:
|
||||
return _scene.ToString() + " - " + _source.ToString() + " - " +
|
||||
_transition.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionTransitionEdit::MacroActionTransitionEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionTransition> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox),
|
||||
_sources(new SceneItemSelectionWidget(parent, false)),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, false, true)),
|
||||
_setTransition(new QCheckBox),
|
||||
_setDuration(new QCheckBox),
|
||||
_transitions(new TransitionSelectionWidget(this, false)),
|
||||
_duration(new DurationSelection(this, false)),
|
||||
_transitionLayout(new QHBoxLayout),
|
||||
_durationLayout(new QHBoxLayout)
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_transitions,
|
||||
SIGNAL(TransitionChanged(const TransitionSelection &)),
|
||||
this,
|
||||
SLOT(TransitionChanged(const TransitionSelection &)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_setTransition, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SetTransitionChanged(int)));
|
||||
QWidget::connect(_setDuration, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SetDurationChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{type}}", _actions},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{setTransition}}", _setTransition},
|
||||
{"{{setDuration}}", _setDuration},
|
||||
};
|
||||
QHBoxLayout *typeLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.transition.entry.line1"),
|
||||
typeLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.transition.entry.line2"),
|
||||
_transitionLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.transition.entry.line3"),
|
||||
_durationLayout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(typeLayout);
|
||||
mainLayout->addLayout(_transitionLayout);
|
||||
mainLayout->addLayout(_durationLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem((_entryData->_source));
|
||||
_setDuration->setChecked(_entryData->_setDuration);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_setTransition->setChecked(_entryData->_setTransitionType);
|
||||
_transitions->SetTransition(_entryData->_transition);
|
||||
_transitions->setEnabled(_entryData->_setTransitionType);
|
||||
_duration->setEnabled(_entryData->_setDuration);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_type = static_cast<MacroActionTransition::Type>(value);
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::TransitionChanged(const TransitionSelection &t)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_transition = t;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::SetWidgetVisibility()
|
||||
{
|
||||
_sources->setVisible(
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE ||
|
||||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW);
|
||||
_scenes->setVisible(_entryData->_type !=
|
||||
MacroActionTransition::Type::SCENE);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::SetTransitionChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setTransitionType = state;
|
||||
_transitions->setEnabled(state);
|
||||
if (state) {
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
} else {
|
||||
emit HeaderInfoChanged("");
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTransitionEdit::SetDurationChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setDuration = state;
|
||||
_duration->setEnabled(state);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
97
plugins/base/macro-action-transition.hpp
Normal file
97
plugins/base/macro-action-transition.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "transition-selection.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionTransition : public MacroAction {
|
||||
public:
|
||||
MacroActionTransition(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionTransition>(m);
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
SCENE,
|
||||
SCENE_OVERRIDE,
|
||||
SOURCE_SHOW,
|
||||
SOURCE_HIDE,
|
||||
};
|
||||
|
||||
Type _type = Type::SCENE;
|
||||
SceneItemSelection _source;
|
||||
SceneSelection _scene;
|
||||
bool _setDuration = true;
|
||||
bool _setTransitionType = true;
|
||||
TransitionSelection _transition;
|
||||
Duration _duration;
|
||||
|
||||
private:
|
||||
void SetSceneTransition();
|
||||
void SetTransitionOverride();
|
||||
void SetSourceTransition(bool);
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionTransitionEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionTransitionEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionTransition> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionTransitionEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionTransition>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SetTransitionChanged(int state);
|
||||
void SetDurationChanged(int state);
|
||||
void TransitionChanged(const TransitionSelection &);
|
||||
void DurationChanged(const Duration &seconds);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
SceneSelectionWidget *_scenes;
|
||||
QCheckBox *_setTransition;
|
||||
QCheckBox *_setDuration;
|
||||
TransitionSelectionWidget *_transitions;
|
||||
DurationSelection *_duration;
|
||||
QHBoxLayout *_transitionLayout;
|
||||
QHBoxLayout *_durationLayout;
|
||||
std::shared_ptr<MacroActionTransition> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
114
plugins/base/macro-action-virtual-cam.cpp
Normal file
114
plugins/base/macro-action-virtual-cam.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "macro-action-virtual-cam.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionVCam::id = "virtual_cam";
|
||||
|
||||
bool MacroActionVCam::_registered = MacroActionFactory::Register(
|
||||
MacroActionVCam::id,
|
||||
{MacroActionVCam::Create, MacroActionVCamEdit::Create,
|
||||
"AdvSceneSwitcher.action.virtualCamera"});
|
||||
|
||||
const static std::map<VCamAction, std::string> actionTypes = {
|
||||
{VCamAction::STOP, "AdvSceneSwitcher.action.virtualCamera.type.stop"},
|
||||
{VCamAction::START, "AdvSceneSwitcher.action.virtualCamera.type.start"},
|
||||
};
|
||||
|
||||
bool MacroActionVCam::PerformAction()
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
switch (_action) {
|
||||
case VCamAction::STOP:
|
||||
if (obs_frontend_virtualcam_active()) {
|
||||
obs_frontend_stop_virtualcam();
|
||||
}
|
||||
break;
|
||||
case VCamAction::START:
|
||||
if (!obs_frontend_virtualcam_active()) {
|
||||
obs_frontend_start_virtualcam();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionVCam::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown virtual camera action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionVCam::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionVCam::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<VCamAction>(obs_data_get_int(obj, "action"));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionVCamEdit::MacroActionVCamEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionVCam> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_actions = new QComboBox();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{actions}}", _actions},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.virtualCamera.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionVCamEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
}
|
||||
|
||||
void MacroActionVCamEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<VCamAction>(value);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
62
plugins/base/macro-action-virtual-cam.hpp
Normal file
62
plugins/base/macro-action-virtual-cam.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class VCamAction {
|
||||
STOP,
|
||||
START,
|
||||
};
|
||||
|
||||
class MacroActionVCam : public MacroAction {
|
||||
public:
|
||||
MacroActionVCam(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionVCam>(m);
|
||||
}
|
||||
|
||||
VCamAction _action = VCamAction::STOP;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionVCamEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionVCamEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionVCam> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionVCamEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionVCam>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int value);
|
||||
|
||||
protected:
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionVCam> _entryData;
|
||||
|
||||
private:
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
225
plugins/base/macro-action-wait.cpp
Normal file
225
plugins/base/macro-action-wait.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "macro-action-wait.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "sync-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionWait::id = "wait";
|
||||
|
||||
bool MacroActionWait::_registered = MacroActionFactory::Register(
|
||||
MacroActionWait::id,
|
||||
{MacroActionWait::Create, MacroActionWaitEdit::Create,
|
||||
"AdvSceneSwitcher.action.wait"});
|
||||
|
||||
static const std::map<MacroActionWait::Type, std::string> waitTypes = {
|
||||
{MacroActionWait::Type::FIXED,
|
||||
"AdvSceneSwitcher.action.wait.type.fixed"},
|
||||
{MacroActionWait::Type::RANDOM,
|
||||
"AdvSceneSwitcher.action.wait.type.random"},
|
||||
};
|
||||
|
||||
static std::random_device rd;
|
||||
static std::default_random_engine re(rd());
|
||||
|
||||
static void waitHelper(std::unique_lock<std::mutex> *lock, Macro *macro,
|
||||
std::chrono::high_resolution_clock::time_point &time)
|
||||
{
|
||||
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
|
||||
if (GetMacroWaitCV().wait_until(*lock, time) ==
|
||||
std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionWait::PerformAction()
|
||||
{
|
||||
double sleepDuration;
|
||||
if (_waitType == Type::FIXED) {
|
||||
sleepDuration = _duration.Seconds();
|
||||
} else {
|
||||
double min = (_duration.Seconds() < _duration2.Seconds())
|
||||
? _duration.Seconds()
|
||||
: _duration2.Seconds();
|
||||
double max = (_duration.Seconds() < _duration2.Seconds())
|
||||
? _duration2.Seconds()
|
||||
: _duration.Seconds();
|
||||
std::uniform_real_distribution<double> unif(min, max);
|
||||
sleepDuration = unif(re);
|
||||
}
|
||||
vblog(LOG_INFO, "perform action wait with duration of %f",
|
||||
sleepDuration);
|
||||
|
||||
auto time = std::chrono::high_resolution_clock::now() +
|
||||
std::chrono::milliseconds((int)(sleepDuration * 1000));
|
||||
|
||||
SetMacroAbortWait(false);
|
||||
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||
waitHelper(&lock, GetMacro(), time);
|
||||
|
||||
return !MacroWaitShouldAbort();
|
||||
}
|
||||
|
||||
bool MacroActionWait::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_duration.Save(obj);
|
||||
_duration2.Save(obj, "duration2");
|
||||
obs_data_set_int(obj, "waitType", static_cast<int>(_waitType));
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionWait::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_duration.Load(obj);
|
||||
// TODO: remove this fallback
|
||||
if (obs_data_get_int(obj, "version") == 1) {
|
||||
_duration2.Load(obj, "duration2");
|
||||
} else {
|
||||
_duration2.Load(obj, "seconds2");
|
||||
_duration2.SetUnit(static_cast<Duration::Unit>(
|
||||
obs_data_get_int(obj, "displayUnit2")));
|
||||
}
|
||||
_waitType = static_cast<Type>(obs_data_get_int(obj, "waitType"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionWait::GetShortDesc() const
|
||||
{
|
||||
if (_waitType == Type::FIXED) {
|
||||
return _duration.ToString();
|
||||
}
|
||||
return _duration.ToString() + " - " + _duration2.ToString();
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : waitTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionWaitEdit::MacroActionWaitEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionWait> entryData)
|
||||
: QWidget(parent),
|
||||
_duration(new DurationSelection()),
|
||||
_duration2(new DurationSelection()),
|
||||
_waitType(new QComboBox()),
|
||||
_mainLayout(new QHBoxLayout())
|
||||
{
|
||||
populateTypeSelection(_waitType);
|
||||
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_duration2, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(Duration2Changed(const Duration &)));
|
||||
QWidget::connect(_waitType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
|
||||
setLayout(_mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entryData->_waitType == MacroActionWait::Type::FIXED) {
|
||||
SetupFixedDurationEdit();
|
||||
} else {
|
||||
SetupRandomDurationEdit();
|
||||
}
|
||||
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_duration2->SetDuration(_entryData->_duration2);
|
||||
_waitType->setCurrentIndex(static_cast<int>(_entryData->_waitType));
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::SetupFixedDurationEdit()
|
||||
{
|
||||
_mainLayout->removeWidget(_duration);
|
||||
_mainLayout->removeWidget(_duration2);
|
||||
_mainLayout->removeWidget(_waitType);
|
||||
ClearLayout(_mainLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{duration}}", _duration},
|
||||
{"{{waitType}}", _waitType},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.wait.entry.fixed"),
|
||||
_mainLayout, widgetPlaceholders);
|
||||
_duration2->hide();
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::SetupRandomDurationEdit()
|
||||
{
|
||||
_mainLayout->removeWidget(_duration);
|
||||
_mainLayout->removeWidget(_duration2);
|
||||
_mainLayout->removeWidget(_waitType);
|
||||
ClearLayout(_mainLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{duration}}", _duration},
|
||||
{"{{duration2}}", _duration2},
|
||||
{"{{waitType}}", _waitType},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.wait.entry.random"),
|
||||
_mainLayout, widgetPlaceholders);
|
||||
_duration2->show();
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::TypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
auto type = static_cast<MacroActionWait::Type>(value);
|
||||
|
||||
if (type == MacroActionWait::Type::FIXED) {
|
||||
SetupFixedDurationEdit();
|
||||
} else {
|
||||
SetupRandomDurationEdit();
|
||||
}
|
||||
|
||||
_entryData->_waitType = type;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionWaitEdit::Duration2Changed(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration2 = dur;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
72
plugins/base/macro-action-wait.hpp
Normal file
72
plugins/base/macro-action-wait.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionWait : public MacroAction {
|
||||
public:
|
||||
MacroActionWait(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionWait>(m);
|
||||
}
|
||||
Duration _duration;
|
||||
Duration _duration2;
|
||||
|
||||
enum class Type {
|
||||
FIXED,
|
||||
RANDOM,
|
||||
};
|
||||
Type _waitType = Type::FIXED;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionWaitEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionWaitEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionWait> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
void SetupFixedDurationEdit();
|
||||
void SetupRandomDurationEdit();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionWaitEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionWait>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DurationChanged(const Duration &value);
|
||||
void Duration2Changed(const Duration &value);
|
||||
void TypeChanged(int value);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
DurationSelection *_duration;
|
||||
DurationSelection *_duration2;
|
||||
QComboBox *_waitType;
|
||||
QHBoxLayout *_mainLayout;
|
||||
|
||||
std::shared_ptr<MacroActionWait> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
342
plugins/base/macro-action-websocket.cpp
Normal file
342
plugins/base/macro-action-websocket.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
#include "macro-action-websocket.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionWebsocket::id = "websocket";
|
||||
|
||||
bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
|
||||
MacroActionWebsocket::id,
|
||||
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
|
||||
"AdvSceneSwitcher.action.websocket"});
|
||||
|
||||
const static std::map<MacroActionWebsocket::API, std::string> apiTypes = {
|
||||
{MacroActionWebsocket::API::SCENE_SWITCHER,
|
||||
"AdvSceneSwitcher.action.websocket.api.sceneSwitcher"},
|
||||
{MacroActionWebsocket::API::OBS_WEBSOCKET,
|
||||
"AdvSceneSwitcher.action.websocket.api.obsWebsocket"},
|
||||
{MacroActionWebsocket::API::GENERIC_WEBSOCKET,
|
||||
"AdvSceneSwitcher.action.websocket.api.genericWebsocket"},
|
||||
};
|
||||
|
||||
const static std::map<MacroActionWebsocket::MessageType, std::string>
|
||||
messageTypes = {
|
||||
{MacroActionWebsocket::MessageType::REQUEST,
|
||||
"AdvSceneSwitcher.action.websocket.type.request"},
|
||||
{MacroActionWebsocket::MessageType::EVENT,
|
||||
"AdvSceneSwitcher.action.websocket.type.event"},
|
||||
};
|
||||
|
||||
void MacroActionWebsocket::SendRequest(const std::string &msg)
|
||||
{
|
||||
auto connection = _connection.lock();
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection->SendMsg(msg);
|
||||
}
|
||||
|
||||
bool MacroActionWebsocket::PerformAction()
|
||||
{
|
||||
if (_api != MacroActionWebsocket::API::SCENE_SWITCHER) {
|
||||
SendRequest(_message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_type == MacroActionWebsocket::MessageType::REQUEST) {
|
||||
auto vendorRequest = ConstructVendorRequestMessage(_message);
|
||||
SendRequest(vendorRequest);
|
||||
} else {
|
||||
SendWebsocketEvent(_message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionWebsocket::LogAction() const
|
||||
{
|
||||
if (_api == API::GENERIC_WEBSOCKET) {
|
||||
vblog(LOG_INFO,
|
||||
"sent generic websocket message \"%s\" via \"%s\"",
|
||||
_message.c_str(),
|
||||
GetWeakConnectionName(_connection).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_api == API::OBS_WEBSOCKET) {
|
||||
vblog(LOG_INFO, "sent obs websocket message \"%s\" via \"%s\"",
|
||||
_message.c_str(),
|
||||
GetWeakConnectionName(_connection).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_type) {
|
||||
case MacroActionWebsocket::MessageType::REQUEST:
|
||||
vblog(LOG_INFO, "sent scene switcher message \"%s\" via \"%s\"",
|
||||
_message.c_str(),
|
||||
GetWeakConnectionName(_connection).c_str());
|
||||
break;
|
||||
case MacroActionWebsocket::MessageType::EVENT:
|
||||
vblog(LOG_INFO,
|
||||
"sent scene switcher event \"%s\" to connected clients",
|
||||
_message.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionWebsocket::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "api", static_cast<int>(_api));
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
_message.Save(obj, "message");
|
||||
obs_data_set_string(obj, "connection",
|
||||
GetWeakConnectionName(_connection).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionWebsocket::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_api = static_cast<API>(obs_data_get_int(obj, "api"));
|
||||
_type = static_cast<MessageType>(obs_data_get_int(obj, "type"));
|
||||
_message.Load(obj, "message");
|
||||
_connection =
|
||||
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionWebsocket::GetShortDesc() const
|
||||
{
|
||||
if (_type == MessageType::REQUEST) {
|
||||
return GetWeakConnectionName(_connection);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateAPISelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : apiTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateMessageTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : messageTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionWebsocketEdit::MacroActionWebsocketEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
|
||||
: QWidget(parent),
|
||||
_apiType(new QComboBox(this)),
|
||||
_messageType(new QComboBox(this)),
|
||||
_message(new VariableTextEdit(this)),
|
||||
_connection(new ConnectionSelection(this)),
|
||||
_editLayout(new QHBoxLayout()),
|
||||
_settingsConflict(new QLabel())
|
||||
{
|
||||
populateAPISelection(_apiType);
|
||||
populateMessageTypeSelection(_messageType);
|
||||
_settingsConflict->setWordWrap(true);
|
||||
|
||||
QWidget::connect(_apiType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(APITypeChanged(int)));
|
||||
QWidget::connect(_messageType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MessageTypeChanged(int)));
|
||||
QWidget::connect(_message, SIGNAL(textChanged()), this,
|
||||
SLOT(MessageChanged()));
|
||||
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
|
||||
this,
|
||||
SLOT(ConnectionSelectionChanged(const QString &)));
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_editLayout);
|
||||
mainLayout->addWidget(_message);
|
||||
mainLayout->addWidget(_settingsConflict);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::CheckForSettingsConflict()
|
||||
{
|
||||
auto connection = _entryData->_connection.lock();
|
||||
if (!connection) {
|
||||
_settingsConflict->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entryData->_api == MacroActionWebsocket::API::GENERIC_WEBSOCKET &&
|
||||
connection->IsUsingOBSProtocol()) {
|
||||
_settingsConflict->show();
|
||||
_settingsConflict->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.settingsConflictGeneric"));
|
||||
} else if (_entryData->_api !=
|
||||
MacroActionWebsocket::API::GENERIC_WEBSOCKET &&
|
||||
!connection->IsUsingOBSProtocol()) {
|
||||
_settingsConflict->show();
|
||||
_settingsConflict->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.settingsConflictOBS"));
|
||||
} else {
|
||||
_settingsConflict->hide();
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetWidgetVisibility()
|
||||
{
|
||||
_messageType->setVisible(_entryData->_api ==
|
||||
MacroActionWebsocket::API::SCENE_SWITCHER);
|
||||
switch (_entryData->_api) {
|
||||
case MacroActionWebsocket::API::SCENE_SWITCHER:
|
||||
if (_entryData->_type ==
|
||||
MacroActionWebsocket::MessageType::REQUEST) {
|
||||
SetupRequestEdit();
|
||||
} else {
|
||||
SetupEventEdit();
|
||||
}
|
||||
break;
|
||||
case MacroActionWebsocket::API::OBS_WEBSOCKET:
|
||||
case MacroActionWebsocket::API::GENERIC_WEBSOCKET:
|
||||
SetupGenericEdit();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
CheckForSettingsConflict();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetupRequestEdit()
|
||||
{
|
||||
ClearWidgets();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request"),
|
||||
_editLayout,
|
||||
{{"{{api}}", _apiType},
|
||||
{"{{type}}", _messageType},
|
||||
{"{{connection}}", _connection}});
|
||||
_connection->show();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetupEventEdit()
|
||||
{
|
||||
ClearWidgets();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event"),
|
||||
_editLayout,
|
||||
{{"{{api}}", _apiType},
|
||||
{"{{type}}", _messageType},
|
||||
{"{{connection}}", _connection}});
|
||||
|
||||
// Add widget to layout but hide it
|
||||
_editLayout->addWidget(_connection);
|
||||
_connection->hide();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetupGenericEdit()
|
||||
{
|
||||
ClearWidgets();
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.entry.generic"),
|
||||
_editLayout,
|
||||
{{"{{api}}", _apiType},
|
||||
{"{{type}}", _messageType},
|
||||
{"{{connection}}", _connection}});
|
||||
_connection->show();
|
||||
|
||||
// Add widget to layout but hide it
|
||||
_editLayout->addWidget(_messageType);
|
||||
_messageType->hide();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::ClearWidgets()
|
||||
{
|
||||
_editLayout->removeWidget(_apiType);
|
||||
_editLayout->removeWidget(_messageType);
|
||||
_editLayout->removeWidget(_connection);
|
||||
ClearLayout(_editLayout);
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_apiType->setCurrentIndex(static_cast<int>(_entryData->_api));
|
||||
_messageType->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_message->setPlainText(_entryData->_message);
|
||||
_connection->SetConnection(_entryData->_connection);
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::APITypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_api = static_cast<MacroActionWebsocket::API>(index);
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::MessageTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_type =
|
||||
static_cast<MacroActionWebsocket::MessageType>(index);
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::MessageChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_message = _message->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::ConnectionSelectionChanged(
|
||||
const QString &connection)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_connection = GetWeakConnectionByQString(connection);
|
||||
CheckForSettingsConflict();
|
||||
emit(HeaderInfoChanged(connection));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
95
plugins/base/macro-action-websocket.hpp
Normal file
95
plugins/base/macro-action-websocket.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "connection-manager.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QStringList>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionWebsocket : public MacroAction {
|
||||
public:
|
||||
MacroActionWebsocket(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
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<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionWebsocket>(m);
|
||||
}
|
||||
|
||||
enum class API {
|
||||
SCENE_SWITCHER,
|
||||
OBS_WEBSOCKET,
|
||||
GENERIC_WEBSOCKET,
|
||||
};
|
||||
|
||||
enum class MessageType {
|
||||
REQUEST,
|
||||
EVENT,
|
||||
};
|
||||
|
||||
API _api = API::SCENE_SWITCHER;
|
||||
MessageType _type = MessageType::REQUEST;
|
||||
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
std::weak_ptr<Connection> _connection;
|
||||
|
||||
private:
|
||||
void SendRequest(const std::string &msg);
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionWebsocketEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionWebsocketEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionWebsocket> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionWebsocketEdit(
|
||||
parent, std::dynamic_pointer_cast<MacroActionWebsocket>(
|
||||
action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void APITypeChanged(int);
|
||||
void MessageTypeChanged(int);
|
||||
void MessageChanged();
|
||||
void ConnectionSelectionChanged(const QString &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionWebsocket> _entryData;
|
||||
|
||||
private:
|
||||
void CheckForSettingsConflict();
|
||||
void SetWidgetVisibility();
|
||||
void SetupRequestEdit();
|
||||
void SetupEventEdit();
|
||||
void SetupGenericEdit();
|
||||
void ClearWidgets();
|
||||
|
||||
QComboBox *_apiType;
|
||||
QComboBox *_messageType;
|
||||
VariableTextEdit *_message;
|
||||
ConnectionSelection *_connection;
|
||||
QHBoxLayout *_editLayout;
|
||||
QLabel *_settingsConflict;
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
639
plugins/base/macro-condition-audio.cpp
Normal file
639
plugins/base/macro-condition-audio.cpp
Normal file
@@ -0,0 +1,639 @@
|
||||
#include "macro-condition-audio.hpp"
|
||||
#include "audio-helpers.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
constexpr int64_t nsPerMs = 1000000;
|
||||
const std::string MacroConditionAudio::id = "audio";
|
||||
|
||||
bool MacroConditionAudio::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionAudio::id,
|
||||
{MacroConditionAudio::Create, MacroConditionAudioEdit::Create,
|
||||
"AdvSceneSwitcher.condition.audio"});
|
||||
|
||||
const static std::map<MacroConditionAudio::Type, std::string> checkTypes = {
|
||||
{MacroConditionAudio::Type::OUTPUT_VOLUME,
|
||||
"AdvSceneSwitcher.condition.audio.type.output"},
|
||||
{MacroConditionAudio::Type::CONFIGURED_VOLUME,
|
||||
"AdvSceneSwitcher.condition.audio.type.volume"},
|
||||
{MacroConditionAudio::Type::SYNC_OFFSET,
|
||||
"AdvSceneSwitcher.condition.audio.type.syncOffset"},
|
||||
{MacroConditionAudio::Type::MONITOR,
|
||||
"AdvSceneSwitcher.condition.audio.type.monitor"},
|
||||
{MacroConditionAudio::Type::BALANCE,
|
||||
"AdvSceneSwitcher.condition.audio.type.balance"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionAudio::OutputCondition, std::string>
|
||||
audioOutputConditionTypes = {
|
||||
{MacroConditionAudio::OutputCondition::ABOVE,
|
||||
"AdvSceneSwitcher.condition.audio.state.above"},
|
||||
{MacroConditionAudio::OutputCondition::BELOW,
|
||||
"AdvSceneSwitcher.condition.audio.state.below"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionAudio::VolumeCondition, std::string>
|
||||
audioVolumeConditionTypes = {
|
||||
{MacroConditionAudio::VolumeCondition::ABOVE,
|
||||
"AdvSceneSwitcher.condition.audio.state.above"},
|
||||
{MacroConditionAudio::VolumeCondition::EXACT,
|
||||
"AdvSceneSwitcher.condition.audio.state.exact"},
|
||||
{MacroConditionAudio::VolumeCondition::BELOW,
|
||||
"AdvSceneSwitcher.condition.audio.state.below"},
|
||||
{MacroConditionAudio::VolumeCondition::MUTE,
|
||||
"AdvSceneSwitcher.condition.audio.state.mute"},
|
||||
{MacroConditionAudio::VolumeCondition::UNMUTE,
|
||||
"AdvSceneSwitcher.condition.audio.state.unmute"},
|
||||
};
|
||||
|
||||
void MacroConditionAudio::SetType(const Type &type)
|
||||
{
|
||||
_checkType = type;
|
||||
SetupTempVars();
|
||||
}
|
||||
|
||||
MacroConditionAudio::~MacroConditionAudio()
|
||||
{
|
||||
obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
|
||||
obs_volmeter_destroy(_volmeter);
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckOutputCondition()
|
||||
{
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_audioSource.GetSource());
|
||||
|
||||
// peak will have a value from -60 db to 0 db so we need to scale it
|
||||
double curVolume = ((double)_peak + 60) * 1.7;
|
||||
|
||||
switch (_outputCondition) {
|
||||
case OutputCondition::ABOVE:
|
||||
ret = curVolume > _volume;
|
||||
break;
|
||||
case OutputCondition::BELOW:
|
||||
ret = curVolume < _volume;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetVariableValue(std::to_string(curVolume));
|
||||
SetTempVarValue("output_volume", std::to_string(curVolume));
|
||||
|
||||
// Reset for next check
|
||||
_peak = -std::numeric_limits<float>::infinity();
|
||||
if (_audioSource.GetType() == SourceSelection::Type::VARIABLE) {
|
||||
ResetVolmeter();
|
||||
}
|
||||
|
||||
return ret && source;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckVolumeCondition()
|
||||
{
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_audioSource.GetSource());
|
||||
|
||||
float curVolume = obs_source_get_volume(source);
|
||||
bool muted = obs_source_muted(source);
|
||||
|
||||
switch (_volumeCondition) {
|
||||
case VolumeCondition::ABOVE:
|
||||
ret = curVolume > _volume / 100.f;
|
||||
SetVariableValue(std::to_string(curVolume));
|
||||
break;
|
||||
case VolumeCondition::EXACT:
|
||||
ret = curVolume == _volume / 100.f;
|
||||
SetVariableValue(std::to_string(curVolume));
|
||||
break;
|
||||
case VolumeCondition::BELOW:
|
||||
ret = curVolume < _volume / 100.f;
|
||||
SetVariableValue(std::to_string(curVolume));
|
||||
break;
|
||||
case VolumeCondition::MUTE:
|
||||
ret = muted;
|
||||
SetVariableValue("");
|
||||
break;
|
||||
case VolumeCondition::UNMUTE:
|
||||
ret = !muted;
|
||||
SetVariableValue("");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetTempVarValue("configured_volume", std::to_string(curVolume));
|
||||
SetTempVarValue("muted", (source && muted) ? "true" : "false");
|
||||
|
||||
return ret && source;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckSyncOffset()
|
||||
{
|
||||
if (!_audioSource.GetSource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_audioSource.GetSource());
|
||||
auto curOffset = obs_source_get_sync_offset(source) / nsPerMs;
|
||||
if (_outputCondition == OutputCondition::ABOVE) {
|
||||
ret = curOffset > _syncOffset;
|
||||
} else {
|
||||
ret = curOffset < _syncOffset;
|
||||
}
|
||||
SetVariableValue(std::to_string(curOffset));
|
||||
SetTempVarValue("sync_offset", std::to_string(curOffset));
|
||||
return ret && source;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckMonitor()
|
||||
{
|
||||
if (!_audioSource.GetSource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_audioSource.GetSource());
|
||||
ret = obs_source_get_monitoring_type(source) == _monitorType;
|
||||
SetVariableValue("");
|
||||
SetTempVarValue("monitor",
|
||||
std::to_string(obs_source_get_monitoring_type(source)));
|
||||
return ret && source;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckBalance()
|
||||
{
|
||||
if (!_audioSource.GetSource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_audioSource.GetSource());
|
||||
auto curBalance = obs_source_get_balance_value(source);
|
||||
if (_outputCondition == OutputCondition::ABOVE) {
|
||||
ret = curBalance > _balance;
|
||||
} else {
|
||||
ret = curBalance < _balance;
|
||||
}
|
||||
SetVariableValue(std::to_string(curBalance));
|
||||
SetTempVarValue("balance", std::to_string(curBalance));
|
||||
return ret && source;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::CheckCondition()
|
||||
{
|
||||
bool ret = false;
|
||||
switch (_checkType) {
|
||||
case Type::OUTPUT_VOLUME:
|
||||
ret = CheckOutputCondition();
|
||||
break;
|
||||
case Type::CONFIGURED_VOLUME:
|
||||
ret = CheckVolumeCondition();
|
||||
break;
|
||||
case Type::SYNC_OFFSET:
|
||||
ret = CheckSyncOffset();
|
||||
break;
|
||||
case Type::MONITOR:
|
||||
ret = CheckMonitor();
|
||||
break;
|
||||
case Type::BALANCE:
|
||||
ret = CheckBalance();
|
||||
break;
|
||||
}
|
||||
|
||||
if (GetVariableValue().empty()) {
|
||||
SetVariableValue(ret ? "true" : "false");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_audioSource.Save(obj, "audioSource");
|
||||
obs_data_set_int(obj, "monitor", _monitorType);
|
||||
_volume.Save(obj, "volume");
|
||||
_syncOffset.Save(obj, "syncOffset");
|
||||
_balance.Save(obj, "balance");
|
||||
obs_data_set_int(obj, "checkType", static_cast<int>(_checkType));
|
||||
obs_data_set_int(obj, "outputCondition",
|
||||
static_cast<int>(_outputCondition));
|
||||
obs_data_set_int(obj, "volumeCondition",
|
||||
static_cast<int>(_volumeCondition));
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry,
|
||||
obs_weak_source *source)
|
||||
{
|
||||
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
|
||||
obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
|
||||
entry);
|
||||
OBSSourceAutoRelease audioSource = obs_weak_source_get_source(source);
|
||||
if (!obs_volmeter_attach_source(volmeter, audioSource)) {
|
||||
const char *name = obs_source_get_name(audioSource);
|
||||
blog(LOG_WARNING, "failed to attach volmeter to source %s",
|
||||
name);
|
||||
}
|
||||
|
||||
return volmeter;
|
||||
}
|
||||
|
||||
bool MacroConditionAudio::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_audioSource.Load(obj, "audioSource");
|
||||
_monitorType = static_cast<obs_monitoring_type>(
|
||||
obs_data_get_int(obj, "monitor"));
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_volume = obs_data_get_int(obj, "volume");
|
||||
_syncOffset = obs_data_get_int(obj, "syncOffset");
|
||||
_balance = obs_data_get_double(obj, "balance");
|
||||
} else {
|
||||
_volume.Load(obj, "volume");
|
||||
_syncOffset.Load(obj, "syncOffset");
|
||||
_balance.Load(obj, "balance");
|
||||
}
|
||||
_checkType = static_cast<Type>(obs_data_get_int(obj, "checkType"));
|
||||
_outputCondition = static_cast<OutputCondition>(
|
||||
obs_data_get_int(obj, "outputCondition"));
|
||||
_volumeCondition = static_cast<VolumeCondition>(
|
||||
obs_data_get_int(obj, "volumeCondition"));
|
||||
_volmeter = AddVolmeterToSource(this, _audioSource.GetSource());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionAudio::GetShortDesc() const
|
||||
{
|
||||
return _audioSource.ToString();
|
||||
}
|
||||
|
||||
void MacroConditionAudio::SetVolumeLevel(void *data, const float *,
|
||||
const float peak[MAX_AUDIO_CHANNELS],
|
||||
const float *)
|
||||
{
|
||||
auto c = static_cast<MacroConditionAudio *>(data);
|
||||
const auto macro = c->GetMacro();
|
||||
if (MacroIsPaused(macro)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
|
||||
if (peak[i] > c->_peak) {
|
||||
c->_peak = peak[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionAudio::ResetVolmeter()
|
||||
{
|
||||
obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
|
||||
obs_volmeter_destroy(_volmeter);
|
||||
|
||||
_volmeter = AddVolmeterToSource(this, _audioSource.GetSource());
|
||||
}
|
||||
|
||||
void MacroConditionAudio::SetupTempVars()
|
||||
{
|
||||
MacroCondition::SetupTempVars();
|
||||
switch (_checkType) {
|
||||
case Type::OUTPUT_VOLUME:
|
||||
AddTempvar(
|
||||
"output_volume",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.output_volume"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.output_volume.description"));
|
||||
break;
|
||||
case Type::CONFIGURED_VOLUME:
|
||||
AddTempvar(
|
||||
"configured_volume",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.configured_volume"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.configured_volume.description"));
|
||||
AddTempvar("muted",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.muted"));
|
||||
break;
|
||||
case Type::SYNC_OFFSET:
|
||||
AddTempvar(
|
||||
"sync_offset",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.sync_offset"));
|
||||
break;
|
||||
case Type::MONITOR:
|
||||
AddTempvar("monitor",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.monitor"));
|
||||
break;
|
||||
case Type::BALANCE:
|
||||
AddTempvar("balance",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.audio.balance"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateCheckTypes(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (const auto &[type, name] : checkTypes) {
|
||||
if (type == MacroConditionAudio::Type::MONITOR) {
|
||||
if (obs_audio_monitoring_available()) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(type));
|
||||
}
|
||||
} else {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateOutputConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : audioOutputConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateVolumeConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : audioVolumeConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionAudioEdit::MacroConditionAudioEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionAudio> entryData)
|
||||
: QWidget(parent),
|
||||
_checkTypes(new QComboBox()),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_condition(new QComboBox()),
|
||||
_volume(new VariableSpinBox()),
|
||||
_syncOffset(new VariableSpinBox()),
|
||||
_monitorTypes(new QComboBox),
|
||||
_balance(new SliderSpinBox(0., 1., ""))
|
||||
{
|
||||
_volume->setSuffix("%");
|
||||
_volume->setMaximum(100);
|
||||
_volume->setMinimum(0);
|
||||
|
||||
_syncOffset->setMinimum(-950);
|
||||
_syncOffset->setMaximum(20000);
|
||||
_syncOffset->setSuffix("ms");
|
||||
|
||||
auto sources = GetAudioSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(CheckTypeChanged(int)));
|
||||
QWidget::connect(
|
||||
_volume,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this,
|
||||
SLOT(VolumeThresholdChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_syncOffset,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(SyncOffsetChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_monitorTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(MonitorTypeChanged(int)));
|
||||
QWidget::connect(
|
||||
_balance,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(BalanceChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
|
||||
populateCheckTypes(_checkTypes);
|
||||
PopulateMonitorTypeSelection(_monitorTypes);
|
||||
|
||||
QHBoxLayout *switchLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{checkType}}", _checkTypes},
|
||||
{"{{audioSources}}", _sources},
|
||||
{"{{volume}}", _volume},
|
||||
{"{{syncOffset}}", _syncOffset},
|
||||
{"{{monitorTypes}}", _monitorTypes},
|
||||
{"{{balance}}", _balance},
|
||||
{"{{condition}}", _condition},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.audio.entry"),
|
||||
switchLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(switchLayout);
|
||||
mainLayout->addWidget(_balance);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::UpdateVolmeterSource()
|
||||
{
|
||||
delete _volMeter;
|
||||
OBSSourceAutoRelease soruce = obs_weak_source_get_source(
|
||||
_entryData->_audioSource.GetSource());
|
||||
_volMeter = new VolControl(soruce.Get());
|
||||
|
||||
QLayout *layout = this->layout();
|
||||
layout->addWidget(_volMeter);
|
||||
|
||||
QWidget::connect(_volMeter->GetSlider(), SIGNAL(valueChanged(int)),
|
||||
_volume, SLOT(SetFixedValue(int)));
|
||||
QWidget::connect(_volume, SIGNAL(FixedValueChanged(int)),
|
||||
_volMeter->GetSlider(), SLOT(setValue(int)));
|
||||
|
||||
// Slider will default to 0 so set it manually once
|
||||
_volMeter->GetSlider()->setValue(_entryData->_volume);
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_audioSource = source;
|
||||
_entryData->ResetVolmeter();
|
||||
}
|
||||
UpdateVolmeterSource();
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::VolumeThresholdChanged(
|
||||
const NumberVariable<int> &vol)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_volume = vol;
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::SyncOffsetChanged(const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_syncOffset = value;
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::MonitorTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_monitorType = static_cast<obs_monitoring_type>(value);
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::BalanceChanged(const NumberVariable<double> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_balance = value;
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
|
||||
_entryData->_outputCondition =
|
||||
static_cast<MacroConditionAudio::OutputCondition>(cond);
|
||||
} else {
|
||||
_entryData->_volumeCondition =
|
||||
static_cast<MacroConditionAudio::VolumeCondition>(cond);
|
||||
}
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::CheckTypeChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetType(static_cast<MacroConditionAudio::Type>(
|
||||
_checkTypes->itemData(idx).toInt()));
|
||||
|
||||
const QSignalBlocker b(_condition);
|
||||
if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
|
||||
populateOutputConditionSelection(_condition);
|
||||
} else if (_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::CONFIGURED_VOLUME) {
|
||||
populateVolumeConditionSelection(_condition);
|
||||
}
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sources->SetSource(_entryData->_audioSource);
|
||||
_volume->SetValue(_entryData->_volume);
|
||||
_syncOffset->SetValue(_entryData->_syncOffset);
|
||||
_monitorTypes->setCurrentIndex(_entryData->_monitorType);
|
||||
_balance->SetDoubleValue(_entryData->_balance);
|
||||
_checkTypes->setCurrentIndex(
|
||||
_checkTypes->findData(static_cast<int>(_entryData->GetType())));
|
||||
|
||||
if (_entryData->GetType() == MacroConditionAudio::Type::OUTPUT_VOLUME ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::SYNC_OFFSET) {
|
||||
populateOutputConditionSelection(_condition);
|
||||
_condition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_outputCondition));
|
||||
} else if (_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::CONFIGURED_VOLUME) {
|
||||
populateVolumeConditionSelection(_condition);
|
||||
_condition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_volumeCondition));
|
||||
}
|
||||
|
||||
UpdateVolmeterSource();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionAudioEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_volume->setVisible(
|
||||
_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::OUTPUT_VOLUME ||
|
||||
(_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::CONFIGURED_VOLUME &&
|
||||
(_entryData->_volumeCondition ==
|
||||
MacroConditionAudio::VolumeCondition::ABOVE ||
|
||||
_entryData->_volumeCondition ==
|
||||
MacroConditionAudio::VolumeCondition::EXACT ||
|
||||
_entryData->_volumeCondition ==
|
||||
MacroConditionAudio::VolumeCondition::BELOW)));
|
||||
_condition->setVisible(
|
||||
_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::OUTPUT_VOLUME ||
|
||||
_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::CONFIGURED_VOLUME ||
|
||||
_entryData->GetType() == MacroConditionAudio::Type::BALANCE ||
|
||||
_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::SYNC_OFFSET);
|
||||
_syncOffset->setVisible(_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::SYNC_OFFSET);
|
||||
_monitorTypes->setVisible(_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::MONITOR);
|
||||
_balance->setVisible(_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::BALANCE);
|
||||
_volMeter->setVisible(_entryData->GetType() ==
|
||||
MacroConditionAudio::Type::OUTPUT_VOLUME);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
125
plugins/base/macro-condition-audio.hpp
Normal file
125
plugins/base/macro-condition-audio.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "volume-control.hpp"
|
||||
#include "slider-spinbox.hpp"
|
||||
#include "source-selection.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <chrono>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionAudio : public MacroCondition {
|
||||
public:
|
||||
MacroConditionAudio(Macro *m) : MacroCondition(m, true) {}
|
||||
~MacroConditionAudio();
|
||||
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<MacroConditionAudio>(m);
|
||||
}
|
||||
static void SetVolumeLevel(void *data,
|
||||
const float magnitude[MAX_AUDIO_CHANNELS],
|
||||
const float peak[MAX_AUDIO_CHANNELS],
|
||||
const float inputPeak[MAX_AUDIO_CHANNELS]);
|
||||
void ResetVolmeter();
|
||||
|
||||
enum class Type {
|
||||
OUTPUT_VOLUME,
|
||||
CONFIGURED_VOLUME,
|
||||
SYNC_OFFSET,
|
||||
MONITOR,
|
||||
BALANCE,
|
||||
};
|
||||
void SetType(const Type &);
|
||||
Type GetType() const { return _checkType; }
|
||||
|
||||
enum class OutputCondition {
|
||||
ABOVE,
|
||||
BELOW,
|
||||
};
|
||||
|
||||
enum class VolumeCondition {
|
||||
ABOVE,
|
||||
EXACT,
|
||||
BELOW,
|
||||
MUTE,
|
||||
UNMUTE,
|
||||
};
|
||||
|
||||
SourceSelection _audioSource;
|
||||
NumberVariable<int> _volume = 0;
|
||||
NumberVariable<int> _syncOffset = 0;
|
||||
obs_monitoring_type _monitorType = OBS_MONITORING_TYPE_NONE;
|
||||
NumberVariable<double> _balance = 0.5;
|
||||
OutputCondition _outputCondition = OutputCondition::ABOVE;
|
||||
VolumeCondition _volumeCondition = VolumeCondition::ABOVE;
|
||||
obs_volmeter_t *_volmeter = nullptr;
|
||||
|
||||
private:
|
||||
bool CheckOutputCondition();
|
||||
bool CheckVolumeCondition();
|
||||
bool CheckSyncOffset();
|
||||
bool CheckMonitor();
|
||||
bool CheckBalance();
|
||||
void SetupTempVars();
|
||||
|
||||
Type _checkType = Type::OUTPUT_VOLUME;
|
||||
float _peak = -std::numeric_limits<float>::infinity();
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionAudioEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionAudioEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionAudio> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionAudioEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionAudio>(cond));
|
||||
}
|
||||
void UpdateVolmeterSource();
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void VolumeThresholdChanged(const NumberVariable<int> &vol);
|
||||
void ConditionChanged(int cond);
|
||||
void CheckTypeChanged(int cond);
|
||||
void SyncOffsetChanged(const NumberVariable<int> &value);
|
||||
void MonitorTypeChanged(int value);
|
||||
void BalanceChanged(const NumberVariable<double> &value);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_checkTypes;
|
||||
SourceSelectionWidget *_sources;
|
||||
QComboBox *_condition;
|
||||
VariableSpinBox *_volume;
|
||||
VariableSpinBox *_syncOffset;
|
||||
QComboBox *_monitorTypes;
|
||||
SliderSpinBox *_balance;
|
||||
VolControl *_volMeter = nullptr;
|
||||
std::shared_ptr<MacroConditionAudio> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
388
plugins/base/macro-condition-cursor.cpp
Normal file
388
plugins/base/macro-condition-cursor.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "macro-condition-cursor.hpp"
|
||||
#include "cursor-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QScreen>
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionCursor::id = "cursor";
|
||||
|
||||
bool MacroConditionCursor::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionCursor::id,
|
||||
{MacroConditionCursor::Create, MacroConditionCursorEdit::Create,
|
||||
"AdvSceneSwitcher.condition.cursor"});
|
||||
|
||||
const static std::map<MacroConditionCursor::Condition, std::string>
|
||||
cursorConditionTypes = {
|
||||
{MacroConditionCursor::Condition::REGION,
|
||||
"AdvSceneSwitcher.condition.cursor.type.region"},
|
||||
{MacroConditionCursor::Condition::MOVING,
|
||||
"AdvSceneSwitcher.condition.cursor.type.moving"},
|
||||
#ifdef _WIN32 // Not implemented for MacOS and Linux
|
||||
{MacroConditionCursor::Condition::CLICK,
|
||||
"AdvSceneSwitcher.condition.cursor.type.click"},
|
||||
#endif
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionCursor::Button, std::string> buttonTypes = {
|
||||
{MacroConditionCursor::Button::LEFT,
|
||||
"AdvSceneSwitcher.condition.cursor.button.left"},
|
||||
{MacroConditionCursor::Button::MIDDLE,
|
||||
"AdvSceneSwitcher.condition.cursor.button.middle"},
|
||||
{MacroConditionCursor::Button::RIGHT,
|
||||
"AdvSceneSwitcher.condition.cursor.button.right"},
|
||||
};
|
||||
|
||||
bool MacroConditionCursor::CheckClick()
|
||||
{
|
||||
switch (_button) {
|
||||
case MacroConditionCursor::Button::LEFT:
|
||||
return _lastCheckTime < GetLastMouseLeftClickTime();
|
||||
case MacroConditionCursor::Button::MIDDLE:
|
||||
return _lastCheckTime < GetLastMouseMiddleClickTime();
|
||||
case MacroConditionCursor::Button::RIGHT:
|
||||
return _lastCheckTime < GetLastMouseRightClickTime();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionCursor::CheckCondition()
|
||||
{
|
||||
bool ret = false;
|
||||
std::pair<int, int> cursorPos = GetCursorPos();
|
||||
switch (_condition) {
|
||||
case Condition::REGION:
|
||||
ret = cursorPos.first >= _minX && cursorPos.second >= _minY &&
|
||||
cursorPos.first <= _maxX && cursorPos.second <= _maxY;
|
||||
SetVariableValue(std::to_string(cursorPos.first) + " " +
|
||||
std::to_string(cursorPos.second));
|
||||
break;
|
||||
case Condition::MOVING:
|
||||
ret = cursorPos.first != _lastCursorPosition.first ||
|
||||
cursorPos.second != _lastCursorPosition.second;
|
||||
break;
|
||||
case Condition::CLICK:
|
||||
ret = CheckClick();
|
||||
break;
|
||||
}
|
||||
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
||||
_lastCursorPosition = cursorPos;
|
||||
|
||||
if (GetVariableValue().empty()) {
|
||||
SetVariableValue(ret ? "true" : "false");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionCursor::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_int(obj, "button", static_cast<int>(_button));
|
||||
_minX.Save(obj, "minX");
|
||||
_minY.Save(obj, "minY");
|
||||
_maxX.Save(obj, "maxX");
|
||||
_maxY.Save(obj, "maxY");
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionCursor::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
_button = static_cast<Button>(obs_data_get_int(obj, "button"));
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_minX = obs_data_get_int(obj, "minX");
|
||||
_minY = obs_data_get_int(obj, "minY");
|
||||
_maxX = obs_data_get_int(obj, "maxX");
|
||||
_maxY = obs_data_get_int(obj, "maxY");
|
||||
} else {
|
||||
_minX.Load(obj, "minX");
|
||||
_minY.Load(obj, "minY");
|
||||
_maxX.Load(obj, "maxX");
|
||||
_maxY.Load(obj, "maxY");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[condition, name] : cursorConditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(condition));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateButtonSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[button, name] : buttonTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(button));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionCursorEdit::MacroConditionCursorEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionCursor> entryData)
|
||||
: QWidget(parent),
|
||||
_minX(new VariableSpinBox()),
|
||||
_minY(new VariableSpinBox()),
|
||||
_maxX(new VariableSpinBox()),
|
||||
_maxY(new VariableSpinBox()),
|
||||
_conditions(new QComboBox()),
|
||||
_buttons(new QComboBox()),
|
||||
_frameToggle(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.cursor.showFrame"))),
|
||||
_xPos(new QLabel("-")),
|
||||
_yPos(new QLabel("-")),
|
||||
_curentPosLayout(new QHBoxLayout())
|
||||
{
|
||||
_frame.setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
populateButtonSelection(_buttons);
|
||||
|
||||
_minX->setPrefix("Min X: ");
|
||||
_minY->setPrefix("Min Y: ");
|
||||
_maxX->setPrefix("Max X: ");
|
||||
_maxY->setPrefix("Max Y: ");
|
||||
|
||||
_minX->setMinimum(-1000000);
|
||||
_minY->setMinimum(-1000000);
|
||||
_maxX->setMinimum(-1000000);
|
||||
_maxY->setMinimum(-1000000);
|
||||
|
||||
_minX->setMaximum(1000000);
|
||||
_minY->setMaximum(1000000);
|
||||
_maxX->setMaximum(1000000);
|
||||
_maxY->setMaximum(1000000);
|
||||
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_buttons, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ButtonChanged(int)));
|
||||
QWidget::connect(
|
||||
_minX,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(MinXChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_minY,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(MinYChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_maxX,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(MaxXChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(
|
||||
_maxY,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(MaxYChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_frameToggle, SIGNAL(clicked()), this,
|
||||
SLOT(ToggleFrame()));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{conditions}}", _conditions},
|
||||
{"{{buttons}}", _buttons},
|
||||
{"{{minX}}", _minX},
|
||||
{"{{minY}}", _minY},
|
||||
{"{{maxX}}", _maxX},
|
||||
{"{{maxY}}", _maxY},
|
||||
{"{{xPos}}", _xPos},
|
||||
{"{{yPos}}", _yPos},
|
||||
{"{{toggleFrameButton}}", _frameToggle},
|
||||
};
|
||||
QHBoxLayout *line1 = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.cursor.entry.line1"),
|
||||
line1, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.cursor.entry.line2"),
|
||||
_curentPosLayout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(line1);
|
||||
mainLayout->addLayout(_curentPosLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
connect(&_timer, &QTimer::timeout, this,
|
||||
&MacroConditionCursorEdit::UpdateCursorPos);
|
||||
_timer.start(1000);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition = static_cast<MacroConditionCursor::Condition>(
|
||||
_conditions->itemData(index).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::ButtonChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_button = static_cast<MacroConditionCursor::Button>(
|
||||
_buttons->itemData(index).toInt());
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::MinXChanged(const NumberVariable<int> &pos)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_minX = pos;
|
||||
SetupFrame();
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::MinYChanged(const NumberVariable<int> &pos)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_minY = pos;
|
||||
SetupFrame();
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::MaxXChanged(const NumberVariable<int> &pos)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_maxX = pos;
|
||||
SetupFrame();
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::MaxYChanged(const NumberVariable<int> &pos)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_maxY = pos;
|
||||
SetupFrame();
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::UpdateCursorPos()
|
||||
{
|
||||
std::pair<int, int> position = GetCursorPos();
|
||||
_xPos->setText(QString::number(position.first));
|
||||
_yPos->setText(QString::number(position.second));
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::ToggleFrame()
|
||||
{
|
||||
SetupFrame();
|
||||
if (_frame.isVisible()) {
|
||||
_frameToggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.cursor.showFrame"));
|
||||
_frame.hide();
|
||||
} else {
|
||||
_frameToggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.cursor.hideFrame"));
|
||||
_frame.show();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::SetWidgetVisibility()
|
||||
{
|
||||
const bool isRegionCondition = _entryData->_condition ==
|
||||
MacroConditionCursor::Condition::REGION;
|
||||
_minX->setVisible(isRegionCondition);
|
||||
_minY->setVisible(isRegionCondition);
|
||||
_maxX->setVisible(isRegionCondition);
|
||||
_maxY->setVisible(isRegionCondition);
|
||||
_frameToggle->setVisible(isRegionCondition);
|
||||
SetLayoutVisible(_curentPosLayout, isRegionCondition);
|
||||
if (_frame.isVisible()) {
|
||||
ToggleFrame();
|
||||
}
|
||||
|
||||
_buttons->setVisible(_entryData->_condition ==
|
||||
MacroConditionCursor::Condition::CLICK);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
static QRect getScreenUnionRect()
|
||||
{
|
||||
QPoint screenTopLeft(0, 0);
|
||||
QPoint screenBottomRight(0, 0);
|
||||
auto screens = QGuiApplication::screens();
|
||||
for (const auto &screen : screens) {
|
||||
const auto geo = screen->geometry();
|
||||
if (geo.topLeft().x() < screenTopLeft.x()) {
|
||||
screenTopLeft.setX(geo.topLeft().x());
|
||||
}
|
||||
if (geo.topLeft().y() < screenTopLeft.y()) {
|
||||
screenTopLeft.setY(geo.topLeft().y());
|
||||
}
|
||||
if (geo.bottomRight().x() > screenBottomRight.x()) {
|
||||
screenBottomRight.setX(geo.bottomRight().x());
|
||||
}
|
||||
if (geo.bottomRight().y() > screenBottomRight.y()) {
|
||||
screenBottomRight.setY(geo.bottomRight().y());
|
||||
}
|
||||
}
|
||||
return QRect(screenTopLeft, screenBottomRight);
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::SetupFrame()
|
||||
{
|
||||
_frame.setFrameStyle(QFrame::Box | QFrame::Plain);
|
||||
_frame.setWindowFlags(Qt::FramelessWindowHint | Qt::Tool |
|
||||
Qt::WindowTransparentForInput |
|
||||
Qt::WindowDoesNotAcceptFocus |
|
||||
Qt::WindowStaysOnTopHint);
|
||||
_frame.setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRect rect(_entryData->_minX, _entryData->_minY,
|
||||
_entryData->_maxX - _entryData->_minX,
|
||||
_entryData->_maxY - _entryData->_minY);
|
||||
|
||||
if (rect.size().height() <= 0 || rect.size().width() <= 0) {
|
||||
_frame.setGeometry(0, 0, 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame.setGeometry(getScreenUnionRect().intersected(rect));
|
||||
}
|
||||
|
||||
void MacroConditionCursorEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_buttons->setCurrentIndex(static_cast<int>(_entryData->_button));
|
||||
_minX->SetValue(_entryData->_minX);
|
||||
_minY->SetValue(_entryData->_minY);
|
||||
_maxX->SetValue(_entryData->_maxX);
|
||||
_maxY->SetValue(_entryData->_maxY);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
97
plugins/base/macro-condition-cursor.hpp
Normal file
97
plugins/base/macro-condition-cursor.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "striped-frame.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionCursor : public MacroCondition {
|
||||
public:
|
||||
MacroConditionCursor(Macro *m) : MacroCondition(m, true) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionCursor>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
REGION,
|
||||
MOVING,
|
||||
CLICK,
|
||||
};
|
||||
|
||||
enum class Button {
|
||||
LEFT,
|
||||
MIDDLE,
|
||||
RIGHT,
|
||||
};
|
||||
|
||||
Condition _condition = Condition::REGION;
|
||||
Button _button = Button::LEFT;
|
||||
NumberVariable<int> _minX = 0, _minY = 0, _maxX = 0, _maxY = 0;
|
||||
|
||||
private:
|
||||
bool CheckClick();
|
||||
std::chrono::high_resolution_clock::time_point _lastCheckTime{};
|
||||
std::pair<int, int> _lastCursorPosition;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionCursorEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionCursorEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionCursor> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionCursorEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionCursor>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int idx);
|
||||
void ButtonChanged(int idx);
|
||||
void MinXChanged(const NumberVariable<int> &pos);
|
||||
void MinYChanged(const NumberVariable<int> &pos);
|
||||
void MaxXChanged(const NumberVariable<int> &pos);
|
||||
void MaxYChanged(const NumberVariable<int> &pos);
|
||||
void UpdateCursorPos();
|
||||
void ToggleFrame();
|
||||
|
||||
protected:
|
||||
VariableSpinBox *_minX;
|
||||
VariableSpinBox *_minY;
|
||||
VariableSpinBox *_maxX;
|
||||
VariableSpinBox *_maxY;
|
||||
QComboBox *_conditions;
|
||||
QComboBox *_buttons;
|
||||
QPushButton *_frameToggle;
|
||||
QLabel *_xPos;
|
||||
QLabel *_yPos;
|
||||
QHBoxLayout *_curentPosLayout;
|
||||
std::shared_ptr<MacroConditionCursor> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
void SetupFrame();
|
||||
QTimer _timer;
|
||||
StripedFrame _frame;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
794
plugins/base/macro-condition-date.cpp
Normal file
794
plugins/base/macro-condition-date.cpp
Normal file
@@ -0,0 +1,794 @@
|
||||
#include "macro-condition-date.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QCalendarWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionDate::id = "date";
|
||||
constexpr const char dateFormat[] = "yyyy MM dd hh mm ss";
|
||||
|
||||
bool MacroConditionDate::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionDate::id,
|
||||
{MacroConditionDate::Create, MacroConditionDateEdit::Create,
|
||||
"AdvSceneSwitcher.condition.date", false});
|
||||
|
||||
const static std::map<MacroConditionDate::Condition, std::string>
|
||||
dateConditionTypes = {
|
||||
{MacroConditionDate::Condition::AT,
|
||||
"AdvSceneSwitcher.condition.date.state.at"},
|
||||
{MacroConditionDate::Condition::AFTER,
|
||||
"AdvSceneSwitcher.condition.date.state.after"},
|
||||
{MacroConditionDate::Condition::BEFORE,
|
||||
"AdvSceneSwitcher.condition.date.state.before"},
|
||||
{MacroConditionDate::Condition::BETWEEN,
|
||||
"AdvSceneSwitcher.condition.date.state.between"},
|
||||
{MacroConditionDate::Condition::PATTERN,
|
||||
"AdvSceneSwitcher.condition.date.state.pattern"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionDate::Condition, std::string>
|
||||
weekConditionTypes = {
|
||||
{MacroConditionDate::Condition::AT,
|
||||
"AdvSceneSwitcher.condition.date.state.at"},
|
||||
{MacroConditionDate::Condition::AFTER,
|
||||
"AdvSceneSwitcher.condition.date.state.after"},
|
||||
{MacroConditionDate::Condition::BEFORE,
|
||||
"AdvSceneSwitcher.condition.date.state.before"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionDate::Day, std::string> dayOfWeekNames = {
|
||||
{MacroConditionDate::Day::ANY,
|
||||
"AdvSceneSwitcher.condition.date.anyDay"},
|
||||
{MacroConditionDate::Day::MONDAY,
|
||||
"AdvSceneSwitcher.condition.date.monday"},
|
||||
{MacroConditionDate::Day::TUESDAY,
|
||||
"AdvSceneSwitcher.condition.date.tuesday"},
|
||||
{MacroConditionDate::Day::WEDNESDAY,
|
||||
"AdvSceneSwitcher.condition.date.wednesday"},
|
||||
{MacroConditionDate::Day::THURSDAY,
|
||||
"AdvSceneSwitcher.condition.date.thursday"},
|
||||
{MacroConditionDate::Day::FRIDAY,
|
||||
"AdvSceneSwitcher.condition.date.friday"},
|
||||
{MacroConditionDate::Day::SATURDAY,
|
||||
"AdvSceneSwitcher.condition.date.saturday"},
|
||||
{MacroConditionDate::Day::SUNDAY,
|
||||
"AdvSceneSwitcher.condition.date.sunday"},
|
||||
};
|
||||
|
||||
bool MacroConditionDate::CheckDayOfWeek(int64_t msSinceLastCheck)
|
||||
{
|
||||
QDateTime cur = QDateTime::currentDateTime();
|
||||
SetVariables(cur);
|
||||
|
||||
if (_dayOfWeek != Day::ANY &&
|
||||
cur.date().dayOfWeek() != static_cast<int>(_dayOfWeek)) {
|
||||
return false;
|
||||
}
|
||||
if (_ignoreTime) {
|
||||
return true;
|
||||
}
|
||||
_dateTime.setDate(cur.date());
|
||||
|
||||
switch (_condition) {
|
||||
case MacroConditionDate::Condition::AT:
|
||||
return _dateTime <= cur &&
|
||||
_dateTime >= cur.addMSecs(-msSinceLastCheck);
|
||||
case MacroConditionDate::Condition::AFTER:
|
||||
return cur >= _dateTime;
|
||||
case MacroConditionDate::Condition::BEFORE:
|
||||
return cur <= _dateTime;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckBetween(const QDateTime &now)
|
||||
{
|
||||
// In the case of ignoring the date component treat the "left" value as
|
||||
// the start of the time range and the "right" value as the end
|
||||
if (_ignoreDate) {
|
||||
if (_dateTime2 >= _dateTime) {
|
||||
return now >= _dateTime && now <= _dateTime2;
|
||||
}
|
||||
// Assume that the end / start value should be shifted by 24h
|
||||
// as otherwise the condition would never be true
|
||||
return (now >= _dateTime && now <= _dateTime2.addDays(1)) ||
|
||||
(now >= _dateTime.addDays(-1) && now <= _dateTime2);
|
||||
}
|
||||
|
||||
if (_dateTime2 >= _dateTime) {
|
||||
return now >= _dateTime && now <= _dateTime2;
|
||||
}
|
||||
return now >= _dateTime2 && now <= _dateTime;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckPattern(QDateTime now,
|
||||
int64_t secondsSinceLastCheck)
|
||||
{
|
||||
auto regex = QRegularExpression(QRegularExpression::anchoredPattern(
|
||||
QString::fromStdString(_pattern)));
|
||||
if (!regex.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Silently ignore cases in which the scene switcher was not able to
|
||||
// run for more than 60s
|
||||
if (secondsSinceLastCheck > 60) {
|
||||
secondsSinceLastCheck = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= secondsSinceLastCheck; ++i) {
|
||||
auto match = regex.match(now.toString(dateFormat));
|
||||
if (match.hasMatch()) {
|
||||
return true;
|
||||
}
|
||||
now = now.addSecs(-1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
|
||||
{
|
||||
bool match = false;
|
||||
QDateTime cur = QDateTime::currentDateTime();
|
||||
SetVariables(cur);
|
||||
|
||||
if (_ignoreDate) {
|
||||
_dateTime.setDate(cur.date());
|
||||
_dateTime2.setDate(cur.date());
|
||||
}
|
||||
if (_ignoreTime) {
|
||||
_dateTime.setTime(cur.time());
|
||||
_dateTime2.setTime(cur.time());
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case MacroConditionDate::Condition::AT:
|
||||
match = _dateTime <= cur &&
|
||||
_dateTime >= cur.addMSecs(-msSinceLastCheck);
|
||||
break;
|
||||
case MacroConditionDate::Condition::AFTER:
|
||||
match = cur >= _dateTime;
|
||||
break;
|
||||
case MacroConditionDate::Condition::BEFORE:
|
||||
match = cur <= _dateTime;
|
||||
break;
|
||||
case MacroConditionDate::Condition::BETWEEN:
|
||||
match = CheckBetween(cur);
|
||||
break;
|
||||
case MacroConditionDate::Condition::PATTERN:
|
||||
match = CheckPattern(cur, msSinceLastCheck / 1000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (match && _repeat) {
|
||||
_dateTime = _dateTime.addSecs(_duration.Seconds());
|
||||
_dateTime2 = _dateTime2.addSecs(_duration.Seconds());
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckCondition()
|
||||
{
|
||||
auto m = GetMacro();
|
||||
if (!m) {
|
||||
return false;
|
||||
}
|
||||
auto msSinceLastCheck = MillisecondsSinceMacroConditionCheck(m);
|
||||
if (_dayOfWeekCheck) {
|
||||
return CheckDayOfWeek(msSinceLastCheck);
|
||||
}
|
||||
return CheckRegularDate(msSinceLastCheck);
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "dayOfWeek", static_cast<int>(_dayOfWeek));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
const auto &dateToSave = _updateOnRepeat ? _dateTime : _origDateTime;
|
||||
const auto &dateToSave2 = _updateOnRepeat ? _dateTime2 : _origDateTime2;
|
||||
obs_data_set_string(obj, "dateTime",
|
||||
dateToSave.toString().toStdString().c_str());
|
||||
obs_data_set_string(obj, "dateTime2",
|
||||
dateToSave2.toString().toStdString().c_str());
|
||||
obs_data_set_bool(obj, "ignoreDate", _ignoreDate);
|
||||
obs_data_set_bool(obj, "ignoreTime", _ignoreTime);
|
||||
obs_data_set_bool(obj, "repeat", _repeat);
|
||||
obs_data_set_bool(obj, "updateOnRepeat", _updateOnRepeat);
|
||||
_duration.Save(obj);
|
||||
obs_data_set_bool(obj, "dayOfWeekCheck", _dayOfWeekCheck);
|
||||
obs_data_set_string(obj, "pattern", _pattern.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_dayOfWeek = static_cast<Day>(obs_data_get_int(obj, "dayOfWeek"));
|
||||
_condition = static_cast<MacroConditionDate::Condition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_dateTime = QDateTime::fromString(
|
||||
QString::fromStdString(obs_data_get_string(obj, "dateTime")));
|
||||
_origDateTime = _dateTime;
|
||||
_dateTime2 = QDateTime::fromString(
|
||||
QString::fromStdString(obs_data_get_string(obj, "dateTime2")));
|
||||
_origDateTime2 = _dateTime2;
|
||||
_ignoreDate = obs_data_get_bool(obj, "ignoreDate");
|
||||
_ignoreTime = obs_data_get_bool(obj, "ignoreTime");
|
||||
_repeat = obs_data_get_bool(obj, "repeat");
|
||||
_updateOnRepeat = obs_data_get_bool(obj, "updateOnRepeat");
|
||||
_duration.Load(obj);
|
||||
_dayOfWeekCheck = obs_data_get_bool(obj, "dayOfWeekCheck");
|
||||
_pattern = obs_data_get_string(obj, "pattern");
|
||||
|
||||
// The following code is used to avoid issues with old save files in
|
||||
// which the simple date check did not support setting _condition
|
||||
if (_dayOfWeekCheck &&
|
||||
_condition == MacroConditionDate::Condition::BETWEEN) {
|
||||
_condition = MacroConditionDate::Condition::AT;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionDate::GetShortDesc() const
|
||||
{
|
||||
if (_dayOfWeekCheck) {
|
||||
auto it = dayOfWeekNames.find(_dayOfWeek);
|
||||
if (it == dayOfWeekNames.end()) {
|
||||
return "";
|
||||
}
|
||||
std::string dayName = obs_module_text(it->second.c_str());
|
||||
if (_ignoreTime) {
|
||||
return dayName;
|
||||
}
|
||||
return dayName + " " +
|
||||
_dateTime.time().toString().toStdString();
|
||||
} else {
|
||||
if (_condition == Condition::PATTERN) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!_ignoreDate && !_ignoreTime) {
|
||||
return GetDateTime1().toString().toStdString();
|
||||
} else if (_ignoreDate && !_ignoreTime) {
|
||||
return GetDateTime1().time().toString().toStdString();
|
||||
} else if (!_ignoreDate && _ignoreTime) {
|
||||
return GetDateTime1().date().toString().toStdString();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetDate1(const QDate &date)
|
||||
{
|
||||
_dateTime.setDate(date);
|
||||
_origDateTime.setDate(date);
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetDate2(const QDate &date)
|
||||
{
|
||||
_dateTime2.setDate(date);
|
||||
_origDateTime2.setDate(date);
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetTime1(const QTime &time)
|
||||
{
|
||||
_dateTime.setTime(time);
|
||||
_origDateTime.setTime(time);
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetTime2(const QTime &time)
|
||||
{
|
||||
_dateTime2.setTime(time);
|
||||
_origDateTime2.setTime(time);
|
||||
}
|
||||
|
||||
QDateTime MacroConditionDate::GetDateTime1() const
|
||||
{
|
||||
return _repeat && _updateOnRepeat ? _dateTime : _origDateTime;
|
||||
}
|
||||
|
||||
QDateTime MacroConditionDate::GetDateTime2() const
|
||||
{
|
||||
return _repeat && _updateOnRepeat ? _dateTime2 : _origDateTime2;
|
||||
}
|
||||
|
||||
QDateTime MacroConditionDate::GetNextMatchDateTime() const
|
||||
{
|
||||
return _dateTime;
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetVariables(const QDateTime &date)
|
||||
{
|
||||
SetVariableValue(date.toString().toStdString());
|
||||
|
||||
SetTempVarValue("year", std::to_string(date.date().year()));
|
||||
SetTempVarValue("month", std::to_string(date.date().month()));
|
||||
SetTempVarValue("day", std::to_string(date.date().day()));
|
||||
SetTempVarValue("hour", std::to_string(date.time().hour()));
|
||||
SetTempVarValue("minute", std::to_string(date.time().minute()));
|
||||
SetTempVarValue("second", std::to_string(date.time().second()));
|
||||
SetTempVarValue("dayOfWeek", std::to_string(date.date().dayOfWeek()));
|
||||
}
|
||||
|
||||
void MacroConditionDate::SetupTempVars()
|
||||
{
|
||||
MacroCondition::SetupTempVars();
|
||||
AddTempvar("year",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.year"));
|
||||
AddTempvar("month",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.month"));
|
||||
AddTempvar("day", obs_module_text("AdvSceneSwitcher.tempVar.date.day"));
|
||||
AddTempvar("hour",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.hour"));
|
||||
AddTempvar("minute",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.minute"));
|
||||
AddTempvar("second",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.second"));
|
||||
AddTempvar("dayOfWeek",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.date.dayOfWeek"));
|
||||
}
|
||||
|
||||
static inline void populateDaySelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : dayOfWeekNames) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : dateConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateWeekConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : weekConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
|
||||
: QWidget(parent),
|
||||
_weekCondition(new QComboBox()),
|
||||
_dayOfWeek(new QComboBox()),
|
||||
_ignoreWeekTime(new QCheckBox()),
|
||||
_weekTime(new QTimeEdit()),
|
||||
_condition(new QComboBox()),
|
||||
_date(new QDateEdit()),
|
||||
_time(new QTimeEdit()),
|
||||
_separator(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.separator"))),
|
||||
_date2(new QDateEdit()),
|
||||
_time2(new QTimeEdit()),
|
||||
_ignoreDate(new QCheckBox()),
|
||||
_ignoreTime(new QCheckBox()),
|
||||
_repeat(new QCheckBox()),
|
||||
_nextMatchDate(new QLabel()),
|
||||
_updateOnRepeat(new QCheckBox()),
|
||||
_duration(new DurationSelection()),
|
||||
_pattern(new QLineEdit()),
|
||||
_currentDate(new QLabel()),
|
||||
_advancedSettingsTooggle(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showAdvancedSettings"))),
|
||||
_simpleLayout(new QHBoxLayout()),
|
||||
_advancedLayout(new QHBoxLayout()),
|
||||
_repeatLayout(new QVBoxLayout()),
|
||||
_repeatUpdateLayout(new QHBoxLayout()),
|
||||
_patternLayout(new QHBoxLayout())
|
||||
{
|
||||
_ignoreWeekTime->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
|
||||
_weekTime->setDisplayFormat("hh:mm:ss");
|
||||
_date->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date->setCalendarPopup(true);
|
||||
auto cal = _date->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time->setDisplayFormat("hh:mm:ss");
|
||||
_date2->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date2->setCalendarPopup(true);
|
||||
cal = _date2->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time2->setDisplayFormat("hh:mm:ss");
|
||||
_ignoreDate->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreDate"));
|
||||
_ignoreTime->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
|
||||
|
||||
QWidget::connect(_weekCondition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_dayOfWeek, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(DayOfWeekChanged(int)));
|
||||
QWidget::connect(_ignoreWeekTime, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreTimeChanged(int)));
|
||||
QWidget::connect(_weekTime, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(TimeChanged(const QTime &)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_date, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(DateChanged(const QDate &)));
|
||||
QWidget::connect(_date2, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(Date2Changed(const QDate &)));
|
||||
QWidget::connect(_time, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(TimeChanged(const QTime &)));
|
||||
QWidget::connect(_time2, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(Time2Changed(const QTime &)));
|
||||
QWidget::connect(_ignoreDate, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreDateChanged(int)));
|
||||
QWidget::connect(_ignoreTime, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreTimeChanged(int)));
|
||||
QWidget::connect(_repeat, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RepeatChanged(int)));
|
||||
QWidget::connect(_updateOnRepeat, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UpdateOnRepeatChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_advancedSettingsTooggle, SIGNAL(clicked()), this,
|
||||
SLOT(AdvancedSettingsToggleClicked()));
|
||||
QWidget::connect(_pattern, SIGNAL(editingFinished()), this,
|
||||
SLOT(PatternChanged()));
|
||||
|
||||
populateDaySelection(_dayOfWeek);
|
||||
populateConditionSelection(_condition);
|
||||
populateWeekConditionSelection(_weekCondition);
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{weekCondition}}", _weekCondition},
|
||||
{"{{dayOfWeek}}", _dayOfWeek},
|
||||
{"{{ignoreWeekTime}}", _ignoreWeekTime},
|
||||
{"{{weekTime}}", _weekTime},
|
||||
{"{{condition}}", _condition},
|
||||
{"{{date}}", _date},
|
||||
{"{{time}}", _time},
|
||||
{"{{separator}}", _separator},
|
||||
{"{{date2}}", _date2},
|
||||
{"{{time2}}", _time2},
|
||||
{"{{ignoreDate}}", _ignoreDate},
|
||||
{"{{ignoreTime}}", _ignoreTime},
|
||||
{"{{repeat}}", _repeat},
|
||||
{"{{updateOnRepeat}}", _updateOnRepeat},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{pattern}}", _pattern},
|
||||
{"{{currentDate}}", _currentDate},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.simple"),
|
||||
_simpleLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.advanced"),
|
||||
_advancedLayout, widgetPlaceholders);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.updateOnRepeat"),
|
||||
_repeatUpdateLayout, widgetPlaceholders);
|
||||
auto repeatLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.repeat"),
|
||||
repeatLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.pattern"),
|
||||
_patternLayout, widgetPlaceholders);
|
||||
_repeatLayout->addLayout(repeatLayout);
|
||||
_repeatLayout->addWidget(_nextMatchDate);
|
||||
_repeatLayout->addLayout(_repeatUpdateLayout);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_simpleLayout);
|
||||
mainLayout->addLayout(_advancedLayout);
|
||||
mainLayout->addLayout(_patternLayout);
|
||||
mainLayout->addLayout(_repeatLayout);
|
||||
auto *_advancedToggleLayout = new QHBoxLayout;
|
||||
_advancedToggleLayout->addWidget(_advancedSettingsTooggle);
|
||||
_advancedToggleLayout->addStretch();
|
||||
mainLayout->addLayout(_advancedToggleLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
QWidget::connect(&_timer, SIGNAL(timeout()), this,
|
||||
SLOT(ShowNextMatch()));
|
||||
QWidget::connect(&_timer, SIGNAL(timeout()), this,
|
||||
SLOT(UpdateCurrentTime()));
|
||||
_timer.start(1000);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DayOfWeekChanged(int day)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_dayOfWeek = static_cast<MacroConditionDate::Day>(day);
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionDate::Condition>(cond);
|
||||
SetWidgetStatus();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DateChanged(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetDate1(date);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::TimeChanged(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetTime1(time);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Date2Changed(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetDate2(date);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Time2Changed(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetTime2(time);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreDateChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_ignoreDate = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreTimeChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_ignoreTime = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::RepeatChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_repeat = state;
|
||||
_duration->setDisabled(!state);
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::UpdateOnRepeatChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_updateOnRepeat = state;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::AdvancedSettingsToggleClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
|
||||
}
|
||||
|
||||
switch (_entryData->_condition) {
|
||||
case MacroConditionDate::Condition::BETWEEN:
|
||||
case MacroConditionDate::Condition::PATTERN:
|
||||
_condition->setCurrentIndex(0);
|
||||
_weekCondition->setCurrentIndex(0);
|
||||
break;
|
||||
case MacroConditionDate::Condition::AT:
|
||||
case MacroConditionDate::Condition::AFTER:
|
||||
case MacroConditionDate::Condition::BEFORE:
|
||||
_weekCondition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_condition));
|
||||
_condition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_condition));
|
||||
break;
|
||||
}
|
||||
|
||||
SetWidgetStatus();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ShowNextMatch()
|
||||
{
|
||||
if (!_entryData || _entryData->_dayOfWeekCheck ||
|
||||
!_entryData->_repeat) {
|
||||
return;
|
||||
}
|
||||
QString format(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.nextMatchDate"));
|
||||
_nextMatchDate->setText(
|
||||
format.arg(_entryData->GetNextMatchDateTime().toString()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::PatternChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
_entryData->_pattern = _pattern->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::UpdateCurrentTime()
|
||||
{
|
||||
QDateTime dateTime = dateTime.currentDateTime();
|
||||
_currentDate->setText(dateTime.toString(dateFormat));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_weekCondition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_condition));
|
||||
_dayOfWeek->setCurrentIndex(
|
||||
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
|
||||
_ignoreWeekTime->setChecked(!_entryData->_ignoreTime);
|
||||
_weekTime->setTime(_entryData->GetDateTime1().time());
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_date->setDate(_entryData->GetDateTime1().date());
|
||||
_time->setTime(_entryData->GetDateTime1().time());
|
||||
_date2->setDate(_entryData->GetDateTime2().date());
|
||||
_time2->setTime(_entryData->GetDateTime2().time());
|
||||
_ignoreDate->setChecked(!_entryData->_ignoreDate);
|
||||
_ignoreTime->setChecked(!_entryData->_ignoreTime);
|
||||
_repeat->setChecked(_entryData->_repeat);
|
||||
_updateOnRepeat->setChecked(_entryData->_updateOnRepeat);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_duration->setDisabled(!_entryData->_repeat);
|
||||
_pattern->setText(QString::fromStdString(_entryData->_pattern));
|
||||
ShowNextMatch();
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetupSimpleView()
|
||||
{
|
||||
SetLayoutVisible(_simpleLayout, true);
|
||||
SetLayoutVisible(_advancedLayout, false);
|
||||
SetLayoutVisible(_patternLayout, false);
|
||||
SetLayoutVisible(_repeatLayout, false);
|
||||
SetLayoutVisible(_repeatUpdateLayout, false);
|
||||
_weekTime->setDisabled(_entryData->_ignoreTime);
|
||||
_weekCondition->setDisabled(_entryData->_ignoreTime);
|
||||
const QSignalBlocker b(_weekTime);
|
||||
_weekTime->setTime(_entryData->GetDateTime1().time());
|
||||
_advancedSettingsTooggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showAdvancedSettings"));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetupAdvancedView()
|
||||
{
|
||||
SetLayoutVisible(_simpleLayout, false);
|
||||
SetLayoutVisible(_advancedLayout, true);
|
||||
SetLayoutVisible(_patternLayout, false);
|
||||
SetLayoutVisible(_repeatLayout, true);
|
||||
SetLayoutVisible(_repeatUpdateLayout, _entryData->_repeat);
|
||||
_nextMatchDate->setVisible(_entryData->_repeat);
|
||||
_date->setDisabled(_entryData->_ignoreDate);
|
||||
_date2->setDisabled(_entryData->_ignoreDate);
|
||||
_time->setDisabled(_entryData->_ignoreTime);
|
||||
_time2->setDisabled(_entryData->_ignoreTime);
|
||||
ShowFirstDateSelection(true);
|
||||
ShowSecondDateSelection(_entryData->_condition ==
|
||||
MacroConditionDate::Condition::BETWEEN);
|
||||
_advancedSettingsTooggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showSimpleSettings"));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetupPatternView()
|
||||
{
|
||||
SetLayoutVisible(_simpleLayout, false);
|
||||
SetLayoutVisible(_advancedLayout, true);
|
||||
SetLayoutVisible(_patternLayout, true);
|
||||
SetLayoutVisible(_repeatLayout, false);
|
||||
SetLayoutVisible(_repeatUpdateLayout, false);
|
||||
ShowFirstDateSelection(false);
|
||||
ShowSecondDateSelection(false);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetWidgetStatus()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entryData->_dayOfWeekCheck) {
|
||||
SetupSimpleView();
|
||||
} else if (_entryData->_condition ==
|
||||
MacroConditionDate::Condition::PATTERN) {
|
||||
SetupPatternView();
|
||||
} else {
|
||||
SetupAdvancedView();
|
||||
}
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ShowFirstDateSelection(bool visible)
|
||||
{
|
||||
_ignoreDate->setVisible(visible);
|
||||
_date->setVisible(visible);
|
||||
_ignoreTime->setVisible(visible);
|
||||
_time->setVisible(visible);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ShowSecondDateSelection(bool visible)
|
||||
{
|
||||
_separator->setVisible(visible);
|
||||
_date2->setVisible(visible);
|
||||
_time2->setVisible(visible);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
156
plugins/base/macro-condition-date.hpp
Normal file
156
plugins/base/macro-condition-date.hpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QComboBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionDate : public MacroCondition {
|
||||
public:
|
||||
MacroConditionDate(Macro *m) : MacroCondition(m, true) {}
|
||||
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<MacroConditionDate>(m);
|
||||
}
|
||||
|
||||
void SetDate1(const QDate &date);
|
||||
void SetDate2(const QDate &date);
|
||||
void SetTime1(const QTime &time);
|
||||
void SetTime2(const QTime &time);
|
||||
QDateTime GetDateTime1() const;
|
||||
QDateTime GetDateTime2() const;
|
||||
QDateTime GetNextMatchDateTime() const;
|
||||
|
||||
enum class Day {
|
||||
ANY = 0,
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY,
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
AT,
|
||||
AFTER,
|
||||
BEFORE,
|
||||
BETWEEN,
|
||||
PATTERN,
|
||||
};
|
||||
|
||||
Day _dayOfWeek = Day::ANY;
|
||||
bool _ignoreDate = false;
|
||||
bool _ignoreTime = false;
|
||||
bool _repeat = false;
|
||||
bool _updateOnRepeat = true;
|
||||
Duration _duration;
|
||||
Condition _condition = Condition::AT;
|
||||
bool _dayOfWeekCheck = true;
|
||||
std::string _pattern = ".... .. .. .. .. ..";
|
||||
|
||||
private:
|
||||
bool CheckDayOfWeek(int64_t);
|
||||
bool CheckRegularDate(int64_t);
|
||||
bool CheckBetween(const QDateTime &now);
|
||||
bool CheckPattern(QDateTime now, int64_t secondsSinceLastCheck);
|
||||
void SetVariables(const QDateTime &date);
|
||||
void SetupTempVars();
|
||||
|
||||
QDateTime _dateTime = QDateTime::currentDateTime();
|
||||
QDateTime _dateTime2 = QDateTime::currentDateTime();
|
||||
// Used depending whether or not _updateOnRepeat is set
|
||||
QDateTime _origDateTime = QDateTime::currentDateTime();
|
||||
QDateTime _origDateTime2 = QDateTime::currentDateTime();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionDateEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionDateEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionDate> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionDateEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionDate>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DayOfWeekChanged(int day);
|
||||
void ConditionChanged(int cond);
|
||||
void DateChanged(const QDate &date);
|
||||
void TimeChanged(const QTime &time);
|
||||
void Date2Changed(const QDate &date);
|
||||
void Time2Changed(const QTime &time);
|
||||
void IgnoreDateChanged(int state);
|
||||
void IgnoreTimeChanged(int state);
|
||||
void RepeatChanged(int state);
|
||||
void UpdateOnRepeatChanged(int state);
|
||||
void DurationChanged(const Duration &seconds);
|
||||
void AdvancedSettingsToggleClicked();
|
||||
void ShowNextMatch();
|
||||
void UpdateCurrentTime();
|
||||
void PatternChanged();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_weekCondition;
|
||||
QComboBox *_dayOfWeek;
|
||||
QCheckBox *_ignoreWeekTime;
|
||||
QTimeEdit *_weekTime;
|
||||
|
||||
QComboBox *_condition;
|
||||
QDateEdit *_date;
|
||||
QTimeEdit *_time;
|
||||
QLabel *_separator;
|
||||
QDateEdit *_date2;
|
||||
QTimeEdit *_time2;
|
||||
QCheckBox *_ignoreDate;
|
||||
QCheckBox *_ignoreTime;
|
||||
QCheckBox *_repeat;
|
||||
QLabel *_nextMatchDate;
|
||||
QCheckBox *_updateOnRepeat;
|
||||
DurationSelection *_duration;
|
||||
QLineEdit *_pattern;
|
||||
QLabel *_currentDate;
|
||||
|
||||
QPushButton *_advancedSettingsTooggle;
|
||||
QHBoxLayout *_simpleLayout;
|
||||
QHBoxLayout *_advancedLayout;
|
||||
QVBoxLayout *_repeatLayout;
|
||||
QHBoxLayout *_repeatUpdateLayout;
|
||||
QHBoxLayout *_patternLayout;
|
||||
|
||||
std::shared_ptr<MacroConditionDate> _entryData;
|
||||
|
||||
private:
|
||||
void SetupSimpleView();
|
||||
void SetupAdvancedView();
|
||||
void SetupPatternView();
|
||||
void SetWidgetStatus();
|
||||
void ShowFirstDateSelection(bool visible);
|
||||
void ShowSecondDateSelection(bool visible);
|
||||
QTimer _timer;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
201
plugins/base/macro-condition-display.cpp
Normal file
201
plugins/base/macro-condition-display.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "macro-condition-display.hpp"
|
||||
#include "monitor-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionDisplay::id = "display";
|
||||
|
||||
bool MacroConditionDisplay::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionDisplay::id,
|
||||
{MacroConditionDisplay::Create, MacroConditionDisplayEdit::Create,
|
||||
"AdvSceneSwitcher.condition.display"});
|
||||
|
||||
const static std::map<MacroConditionDisplay::Condition, std::string>
|
||||
conditionTypes = {
|
||||
{MacroConditionDisplay::Condition::DISPLAY_NAME,
|
||||
"AdvSceneSwitcher.condition.display.type.displayName"},
|
||||
{MacroConditionDisplay::Condition::DISPLAY_COUNT,
|
||||
"AdvSceneSwitcher.condition.display.type.displayCount"},
|
||||
};
|
||||
|
||||
static bool matchDisplayNames(const std::string &name, const RegexConfig ®ex)
|
||||
{
|
||||
auto monitors = GetMonitorNames();
|
||||
if (regex.Enabled()) {
|
||||
auto expr = regex.GetRegularExpression(name);
|
||||
if (!expr.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &m : monitors) {
|
||||
auto match = expr.match(m);
|
||||
if (match.hasMatch()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto &m : monitors) {
|
||||
if (m.toStdString() == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionDisplay::CheckCondition()
|
||||
{
|
||||
switch (_condition) {
|
||||
case advss::MacroConditionDisplay::Condition::DISPLAY_NAME:
|
||||
return matchDisplayNames(_displayName, _regexConf);
|
||||
case advss::MacroConditionDisplay::Condition::DISPLAY_COUNT:
|
||||
return _displayCount == QGuiApplication::screens().count();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionDisplay::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
_displayName.Save(obj, "displayName");
|
||||
_regexConf.Save(obj);
|
||||
_displayCount.Save(obj, "displayCount");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionDisplay::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
_displayName.Load(obj, "displayName");
|
||||
_regexConf.Load(obj);
|
||||
_displayCount.Load(obj, "displayCount");
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : conditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionDisplayEdit::MacroConditionDisplayEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionDisplay> entryData)
|
||||
: QWidget(parent),
|
||||
_conditions(new QComboBox()),
|
||||
_displays(new QComboBox()),
|
||||
_regex(new RegexConfigWidget()),
|
||||
_displayCount(new VariableSpinBox(this))
|
||||
{
|
||||
populateConditionSelection(_conditions);
|
||||
_displays->addItems(GetMonitorNames());
|
||||
_displays->setEditable(true);
|
||||
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_displays, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(DisplayNameChanged(const QString &)));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(
|
||||
_displayCount,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(DisplayCountChanged(const NumberVariable<int> &)));
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{conditions}}", _conditions},
|
||||
{"{{displays}}", _displays},
|
||||
{"{{regex}}", _regex},
|
||||
{"{{displayCount}}", _displayCount},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.display.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::ConditionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionDisplay::Condition>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::DisplayNameChanged(const QString &display)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_displayName = display.toStdString();
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regexConf = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::DisplayCountChanged(
|
||||
const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_displayCount = value;
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_displays->setCurrentText(QString::fromStdString(
|
||||
_entryData->_displayName.UnresolvedValue()));
|
||||
_regex->SetRegexConfig(_entryData->_regexConf);
|
||||
_displayCount->SetValue(_entryData->_displayCount);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionDisplayEdit::SetWidgetVisibility()
|
||||
{
|
||||
_displays->setVisible(_entryData->_condition ==
|
||||
MacroConditionDisplay::Condition::DISPLAY_NAME);
|
||||
_regex->setVisible(_entryData->_condition ==
|
||||
MacroConditionDisplay::Condition::DISPLAY_NAME);
|
||||
_displayCount->setVisible(
|
||||
_entryData->_condition ==
|
||||
MacroConditionDisplay::Condition::DISPLAY_COUNT);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
73
plugins/base/macro-condition-display.hpp
Normal file
73
plugins/base/macro-condition-display.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionDisplay : public MacroCondition {
|
||||
public:
|
||||
MacroConditionDisplay(Macro *m) : MacroCondition(m, true) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionDisplay>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
DISPLAY_NAME,
|
||||
DISPLAY_COUNT,
|
||||
};
|
||||
Condition _condition = Condition::DISPLAY_NAME;
|
||||
StringVariable _displayName;
|
||||
RegexConfig _regexConf;
|
||||
IntVariable _displayCount;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionDisplayEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionDisplayEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionDisplay> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionDisplayEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionDisplay>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int);
|
||||
void DisplayNameChanged(const QString &);
|
||||
void RegexChanged(const RegexConfig &conf);
|
||||
void DisplayCountChanged(const NumberVariable<int> &);
|
||||
|
||||
protected:
|
||||
QComboBox *_conditions;
|
||||
QComboBox *_displays;
|
||||
RegexConfigWidget *_regex;
|
||||
VariableSpinBox *_displayCount;
|
||||
std::shared_ptr<MacroConditionDisplay> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
424
plugins/base/macro-condition-file.cpp
Normal file
424
plugins/base/macro-condition-file.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
#include "macro-condition-file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "curl-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <QTextStream>
|
||||
#include <QFileDialog>
|
||||
#include <regex>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionFile::id = "file";
|
||||
|
||||
bool MacroConditionFile::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionFile::id,
|
||||
{MacroConditionFile::Create, MacroConditionFileEdit::Create,
|
||||
"AdvSceneSwitcher.condition.file"});
|
||||
|
||||
static std::hash<std::string> strHash;
|
||||
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
|
||||
void *userp)
|
||||
{
|
||||
((std::string *)userp)->append((char *)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static std::string getRemoteData(std::string &url)
|
||||
{
|
||||
std::string readBuffer;
|
||||
CurlHelper::SetOpt(CURLOPT_URL, url.c_str());
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &readBuffer);
|
||||
// Set timeout to at least one second
|
||||
int timeout = GetIntervalValue() / 1000;
|
||||
if (timeout == 0) {
|
||||
timeout = 1;
|
||||
}
|
||||
CurlHelper::SetOpt(CURLOPT_TIMEOUT, 1);
|
||||
CurlHelper::Perform();
|
||||
return readBuffer;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::MatchFileContent(QString &filedata)
|
||||
{
|
||||
if (_onlyMatchIfChanged) {
|
||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
||||
if (newHash == _lastHash) {
|
||||
return false;
|
||||
}
|
||||
_lastHash = newHash;
|
||||
}
|
||||
|
||||
if (_regex.Enabled()) {
|
||||
return _regex.Matches(filedata, QString::fromStdString(_text));
|
||||
}
|
||||
|
||||
QString text = QString::fromStdString(_text);
|
||||
return CompareIgnoringLineEnding(text, filedata);
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckRemoteFileContent()
|
||||
{
|
||||
std::string path = _file;
|
||||
std::string data = getRemoteData(path);
|
||||
SetVariableValue(data);
|
||||
QString qdata = QString::fromStdString(data);
|
||||
return MatchFileContent(qdata);
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckLocalFileContent()
|
||||
{
|
||||
QFile file(QString::fromStdString(_file));
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_useTime) {
|
||||
QDateTime newLastMod = QFileInfo(file).lastModified();
|
||||
if (_lastMod == newLastMod) {
|
||||
return false;
|
||||
}
|
||||
_lastMod = newLastMod;
|
||||
}
|
||||
|
||||
QString filedata = QTextStream(&file).readAll();
|
||||
SetVariableValue(filedata.toStdString());
|
||||
bool match = MatchFileContent(filedata);
|
||||
|
||||
file.close();
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckChangeContent()
|
||||
{
|
||||
QString filedata;
|
||||
switch (_fileType) {
|
||||
case FileType::LOCAL: {
|
||||
std::string path = _file;
|
||||
QFile file(QString::fromStdString(path));
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
filedata = QTextStream(&file).readAll();
|
||||
file.close();
|
||||
} break;
|
||||
case FileType::REMOTE: {
|
||||
std::string path = _file;
|
||||
std::string data = getRemoteData(path);
|
||||
QString filedata = QString::fromStdString(data);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
||||
const bool contentChanged = newHash != _lastHash;
|
||||
_lastHash = newHash;
|
||||
return contentChanged;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckChangeDate()
|
||||
{
|
||||
if (_fileType == FileType::REMOTE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(QString::fromStdString(_file));
|
||||
QDateTime newLastMod = QFileInfo(file).lastModified();
|
||||
SetVariableValue(newLastMod.toString().toStdString());
|
||||
const bool dateChanged = _lastMod != newLastMod;
|
||||
_lastMod = newLastMod;
|
||||
return dateChanged;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckCondition()
|
||||
{
|
||||
bool ret = false;
|
||||
switch (_condition) {
|
||||
case MacroConditionFile::ConditionType::MATCH:
|
||||
if (_fileType == FileType::REMOTE) {
|
||||
ret = CheckRemoteFileContent();
|
||||
break;
|
||||
}
|
||||
ret = CheckLocalFileContent();
|
||||
break;
|
||||
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
|
||||
ret = CheckChangeContent();
|
||||
break;
|
||||
case MacroConditionFile::ConditionType::DATE_CHANGE:
|
||||
ret = CheckChangeDate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (GetVariableValue().empty()) {
|
||||
SetVariableValue(ret ? "true" : "false");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_regex.Save(obj);
|
||||
_file.Save(obj, "file");
|
||||
_text.Save(obj, "text");
|
||||
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_bool(obj, "useTime", _useTime);
|
||||
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_regex.Load(obj);
|
||||
// TODO: remove in future version
|
||||
if (obs_data_has_user_value(obj, "useRegex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "useRegex"));
|
||||
}
|
||||
_file.Load(obj, "file");
|
||||
_text.Load(obj, "text");
|
||||
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
|
||||
_condition =
|
||||
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
|
||||
_useTime = obs_data_get_bool(obj, "useTime");
|
||||
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionFile::GetShortDesc() const
|
||||
{
|
||||
return _file.UnresolvedValue();
|
||||
}
|
||||
|
||||
static void populateFileTypes(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.condition.file.local"));
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.remote"));
|
||||
}
|
||||
|
||||
static void populateConditions(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.type.match"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.file.type.contentChange"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.file.type.dateChange"));
|
||||
}
|
||||
|
||||
MacroConditionFileEdit::MacroConditionFileEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
|
||||
: QWidget(parent),
|
||||
_fileTypes(new QComboBox()),
|
||||
_conditions(new QComboBox()),
|
||||
_filePath(new FileSelection()),
|
||||
_matchText(new VariableTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent)),
|
||||
_checkModificationDate(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
|
||||
_checkFileContent(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
|
||||
{
|
||||
populateFileTypes(_fileTypes);
|
||||
populateConditions(_conditions);
|
||||
|
||||
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(FileTypeChanged(int)));
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
|
||||
SLOT(MatchTextChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(CheckModificationDateChanged(int)));
|
||||
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(OnlyMatchIfChangedChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{fileType}}", _fileTypes},
|
||||
{"{{conditions}}", _conditions},
|
||||
{"{{filePath}}", _filePath},
|
||||
{"{{matchText}}", _matchText},
|
||||
{"{{useRegex}}", _regex},
|
||||
{"{{checkModificationDate}}", _checkModificationDate},
|
||||
{"{{checkFileContent}}", _checkFileContent},
|
||||
};
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
QHBoxLayout *line1Layout = new QHBoxLayout;
|
||||
QHBoxLayout *line2Layout = new QHBoxLayout;
|
||||
QHBoxLayout *line3Layout = new QHBoxLayout;
|
||||
line1Layout->setContentsMargins(0, 0, 0, 0);
|
||||
line2Layout->setContentsMargins(0, 0, 0, 0);
|
||||
line3Layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line1"),
|
||||
line1Layout, widgetPlaceholders);
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line2"),
|
||||
line2Layout, widgetPlaceholders, false);
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line3"),
|
||||
line3Layout, widgetPlaceholders);
|
||||
mainLayout->addLayout(line1Layout);
|
||||
mainLayout->addLayout(line2Layout);
|
||||
mainLayout->addLayout(line3Layout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_filePath->SetPath(_entryData->_file);
|
||||
_matchText->setPlainText(_entryData->_text);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_checkModificationDate->setChecked(_entryData->_useTime);
|
||||
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
|
||||
|
||||
// TODO: Remove in future version
|
||||
if (!_entryData->_useTime) {
|
||||
_checkModificationDate->hide();
|
||||
}
|
||||
if (!_entryData->_onlyMatchIfChanged) {
|
||||
_checkFileContent->hide();
|
||||
}
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::FileTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
MacroConditionFile::FileType type =
|
||||
static_cast<MacroConditionFile::FileType>(index);
|
||||
|
||||
if (type == MacroConditionFile::FileType::LOCAL) {
|
||||
_filePath->Button()->setDisabled(false);
|
||||
_checkModificationDate->setDisabled(false);
|
||||
} else {
|
||||
_filePath->Button()->setDisabled(true);
|
||||
_checkModificationDate->setDisabled(true);
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_fileType = type;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionFile::ConditionType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::PathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_file = text.toUtf8().constData();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::MatchTextChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_text = _matchText->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regex = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_useTime = state;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_onlyMatchIfChanged = state;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_matchText->setVisible(_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_regex->setVisible(_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_checkModificationDate->setVisible(
|
||||
_entryData->_useTime &&
|
||||
_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_checkFileContent->setVisible(
|
||||
_entryData->_onlyMatchIfChanged &&
|
||||
_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
106
plugins/base/macro-condition-file.hpp
Normal file
106
plugins/base/macro-condition-file.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionFile : public MacroCondition {
|
||||
public:
|
||||
MacroConditionFile(Macro *m) : MacroCondition(m, true) {}
|
||||
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<MacroConditionFile>(m);
|
||||
}
|
||||
|
||||
enum class FileType {
|
||||
LOCAL,
|
||||
REMOTE,
|
||||
};
|
||||
|
||||
enum class ConditionType {
|
||||
MATCH,
|
||||
CONTENT_CHANGE,
|
||||
DATE_CHANGE,
|
||||
};
|
||||
|
||||
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
FileType _fileType = FileType::LOCAL;
|
||||
ConditionType _condition = ConditionType::MATCH;
|
||||
RegexConfig _regex;
|
||||
|
||||
// TODO: Remove in future version
|
||||
bool _useTime = false;
|
||||
bool _onlyMatchIfChanged = false;
|
||||
|
||||
private:
|
||||
bool MatchFileContent(QString &filedata);
|
||||
bool CheckRemoteFileContent();
|
||||
bool CheckLocalFileContent();
|
||||
bool CheckChangeContent();
|
||||
bool CheckChangeDate();
|
||||
|
||||
QDateTime _lastMod;
|
||||
size_t _lastHash = 0;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionFileEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionFileEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionFile> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionFileEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionFile>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void FileTypeChanged(int index);
|
||||
void ConditionChanged(int index);
|
||||
void PathChanged(const QString &text);
|
||||
void MatchTextChanged();
|
||||
void RegexChanged(const RegexConfig &);
|
||||
void CheckModificationDateChanged(int state);
|
||||
void OnlyMatchIfChangedChanged(int state);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_fileTypes;
|
||||
QComboBox *_conditions;
|
||||
FileSelection *_filePath;
|
||||
VariableTextEdit *_matchText;
|
||||
RegexConfigWidget *_regex;
|
||||
QCheckBox *_checkModificationDate;
|
||||
QCheckBox *_checkFileContent;
|
||||
std::shared_ptr<MacroConditionFile> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
361
plugins/base/macro-condition-filter.cpp
Normal file
361
plugins/base/macro-condition-filter.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
#include "macro-condition-filter.hpp"
|
||||
#include "source-settings-helpers.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "text-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionFilter::id = "filter";
|
||||
|
||||
bool MacroConditionFilter::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionFilter::id,
|
||||
{MacroConditionFilter::Create, MacroConditionFilterEdit::Create,
|
||||
"AdvSceneSwitcher.condition.filter"});
|
||||
|
||||
const static std::map<MacroConditionFilter::Condition, std::string>
|
||||
filterConditionTypes = {
|
||||
{MacroConditionFilter::Condition::ENABLED,
|
||||
"AdvSceneSwitcher.condition.filter.type.active"},
|
||||
{MacroConditionFilter::Condition::DISABLED,
|
||||
"AdvSceneSwitcher.condition.filter.type.showing"},
|
||||
{MacroConditionFilter::Condition::SETTINGS_MATCH,
|
||||
"AdvSceneSwitcher.condition.filter.type.settingsMatch"},
|
||||
{MacroConditionFilter::Condition::SETTINGS_CHANGED,
|
||||
"AdvSceneSwitcher.condition.filter.type.settingsChanged"},
|
||||
{MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH,
|
||||
"AdvSceneSwitcher.condition.filter.type.individualSettingMatches"},
|
||||
{MacroConditionFilter::Condition::INDIVIDUAL_SETTING_CHANGED,
|
||||
"AdvSceneSwitcher.condition.filter.type.individualSettingChanged"},
|
||||
};
|
||||
|
||||
bool MacroConditionFilter::CheckConditionHelper(const OBSWeakSource &filter)
|
||||
{
|
||||
bool ret = false;
|
||||
OBSSourceAutoRelease filterSource = obs_weak_source_get_source(filter);
|
||||
if (!filterSource.Get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ENABLED:
|
||||
ret = obs_source_enabled(filterSource);
|
||||
break;
|
||||
case Condition::DISABLED:
|
||||
ret = !obs_source_enabled(filterSource);
|
||||
break;
|
||||
case Condition::SETTINGS_MATCH:
|
||||
ret = CompareSourceSettings(filter, _settings, _regex);
|
||||
if (IsReferencedInVars()) {
|
||||
SetVariableValue(GetSourceSettings(filter));
|
||||
}
|
||||
break;
|
||||
case Condition::SETTINGS_CHANGED: {
|
||||
std::string settings = GetSourceSettings(filter);
|
||||
ret = !_currentSettings.empty() && settings != _currentSettings;
|
||||
_currentSettings = settings;
|
||||
SetVariableValue(settings);
|
||||
break;
|
||||
}
|
||||
case Condition::INDIVIDUAL_SETTING_MATCH: {
|
||||
std::string value = GetSourceSettingValue(filter, _setting);
|
||||
ret = _regex.Enabled() ? _regex.Matches(value, _settings)
|
||||
: value == std::string(_settings);
|
||||
SetVariableValue(value);
|
||||
break;
|
||||
}
|
||||
case Condition::INDIVIDUAL_SETTING_CHANGED: {
|
||||
std::string value = GetSourceSettingValue(filter, _setting);
|
||||
ret = _currentSettingsValue != value;
|
||||
_currentSettingsValue = value;
|
||||
SetVariableValue(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionFilter::CheckCondition()
|
||||
{
|
||||
auto filters = _filter.GetFilters(_source);
|
||||
if (filters.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
for (const auto &filter : filters) {
|
||||
ret = ret && CheckConditionHelper(filter);
|
||||
}
|
||||
|
||||
if (GetVariableValue().empty()) {
|
||||
SetVariableValue(ret ? "true" : "false");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionFilter::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_source.Save(obj);
|
||||
_filter.Save(obj, "filter");
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
_settings.Save(obj, "settings");
|
||||
_regex.Save(obj);
|
||||
_setting.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionFilter::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_source.Load(obj);
|
||||
_filter.Load(obj, _source, "filter");
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
_settings.Load(obj, "settings");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "regex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "regex"));
|
||||
}
|
||||
_setting.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionFilter::GetShortDesc() const
|
||||
{
|
||||
if (!_filter.ToString().empty() && !_source.ToString().empty()) {
|
||||
return _source.ToString() + " - " + _filter.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : filterConditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionFilterEdit::MacroConditionFilterEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData)
|
||||
: QWidget(parent),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_filters(new FilterSelectionWidget(this, _sources, true)),
|
||||
_conditions(new QComboBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.getSettings"))),
|
||||
_settings(new VariableTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent)),
|
||||
_settingSelection(new SourceSettingSelection())
|
||||
{
|
||||
populateConditionSelection(_conditions);
|
||||
auto sources = GetSourcesWithFilterNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_filters,
|
||||
SIGNAL(FilterChanged(const FilterSelection &)), this,
|
||||
SLOT(FilterChanged(const FilterSelection &)));
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_settingSelection,
|
||||
SIGNAL(SelectionChanged(const SourceSetting &)), this,
|
||||
SLOT(SettingSelectionChanged(const SourceSetting &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sources}}", _sources},
|
||||
{"{{filters}}", _filters},
|
||||
{"{{conditions}}", _conditions},
|
||||
{"{{settings}}", _settings},
|
||||
{"{{getSettings}}", _getSettings},
|
||||
{"{{regex}}", _regex},
|
||||
{"{{settingSelection}}", _settingSelection},
|
||||
|
||||
};
|
||||
auto line1Layout = new QHBoxLayout;
|
||||
line1Layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.entry.line1"),
|
||||
line1Layout, widgetPlaceholders);
|
||||
auto line2Layout = new QHBoxLayout;
|
||||
line2Layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.entry.line2"),
|
||||
line2Layout, widgetPlaceholders, false);
|
||||
auto line3Layout = new QHBoxLayout;
|
||||
line3Layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.entry.line3"),
|
||||
line3Layout, widgetPlaceholders);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(line1Layout);
|
||||
mainLayout->addLayout(line2Layout);
|
||||
mainLayout->addLayout(line3Layout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = source;
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::FilterChanged(const FilterSelection &filter)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_filter = filter;
|
||||
}
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_settingSelection->SetSource(filters.empty() ? nullptr : filters.at(0));
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionFilter::Condition>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::GetSettingsClicked()
|
||||
{
|
||||
if (_loading || !_entryData ||
|
||||
_entryData->_filter.GetFilters(_entryData->_source).empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
if (filters.empty()) {
|
||||
_settings->setPlainText(QString(""));
|
||||
return;
|
||||
}
|
||||
|
||||
QString value;
|
||||
if (_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS_MATCH) {
|
||||
value = FormatJsonString(GetSourceSettings(filters.at(0)));
|
||||
} else {
|
||||
value = QString::fromStdString(GetSourceSettingValue(
|
||||
filters.at(0), _entryData->_setting));
|
||||
}
|
||||
|
||||
if (_entryData->_regex.Enabled()) {
|
||||
value = EscapeForRegex(value);
|
||||
}
|
||||
_settings->setPlainText(value);
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::SettingsChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::SettingSelectionChanged(
|
||||
const SourceSetting &setting)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_setting = setting;
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::SetWidgetVisibility()
|
||||
{
|
||||
const bool showSettingsControls =
|
||||
_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS_MATCH ||
|
||||
_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH;
|
||||
_settings->setVisible(showSettingsControls);
|
||||
_getSettings->setVisible(showSettingsControls);
|
||||
_regex->setVisible(showSettingsControls);
|
||||
_settingSelection->setVisible(
|
||||
_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::INDIVIDUAL_SETTING_MATCH);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_filters->SetFilter(_entryData->_source, _entryData->_filter);
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_settings->setPlainText(_entryData->_settings);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
const auto filters =
|
||||
_entryData->_filter.GetFilters(_entryData->_source);
|
||||
_settingSelection->SetSource(filters.empty() ? nullptr : filters.at(0));
|
||||
_settingSelection->SetSetting(_entryData->_setting);
|
||||
SetWidgetVisibility();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
96
plugins/base/macro-condition-filter.hpp
Normal file
96
plugins/base/macro-condition-filter.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "filter-selection.hpp"
|
||||
#include "source-setting.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionFilter : public MacroCondition {
|
||||
public:
|
||||
MacroConditionFilter(Macro *m) : MacroCondition(m, true) {}
|
||||
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<MacroConditionFilter>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
SETTINGS_MATCH,
|
||||
SETTINGS_CHANGED,
|
||||
INDIVIDUAL_SETTING_MATCH,
|
||||
INDIVIDUAL_SETTING_CHANGED,
|
||||
};
|
||||
|
||||
SourceSelection _source;
|
||||
FilterSelection _filter;
|
||||
Condition _condition = Condition::ENABLED;
|
||||
StringVariable _settings = "";
|
||||
RegexConfig _regex;
|
||||
SourceSetting _setting;
|
||||
|
||||
private:
|
||||
bool CheckConditionHelper(const OBSWeakSource &);
|
||||
|
||||
std::string _currentSettings;
|
||||
std::string _currentSettingsValue;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionFilterEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionFilterEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionFilter> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionFilterEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionFilter>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void FilterChanged(const FilterSelection &);
|
||||
void ConditionChanged(int cond);
|
||||
void GetSettingsClicked();
|
||||
void SettingsChanged();
|
||||
void RegexChanged(const RegexConfig &);
|
||||
void SettingSelectionChanged(const SourceSetting &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
SourceSelectionWidget *_sources;
|
||||
FilterSelectionWidget *_filters;
|
||||
QComboBox *_conditions;
|
||||
QPushButton *_getSettings;
|
||||
VariableTextEdit *_settings;
|
||||
RegexConfigWidget *_regex;
|
||||
SourceSettingSelection *_settingSelection;
|
||||
|
||||
std::shared_ptr<MacroConditionFilter> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
114
plugins/base/macro-condition-hotkey.cpp
Normal file
114
plugins/base/macro-condition-hotkey.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "macro-condition-hotkey.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionHotkey::id = "hotkey";
|
||||
|
||||
bool MacroConditionHotkey::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionHotkey::id,
|
||||
{MacroConditionHotkey::Create, MacroConditionHotkeyEdit::Create,
|
||||
"AdvSceneSwitcher.condition.hotkey"});
|
||||
|
||||
static uint32_t count = 1;
|
||||
|
||||
MacroConditionHotkey::MacroConditionHotkey(Macro *m) : MacroCondition(m)
|
||||
{
|
||||
auto name = obs_module_text("AdvSceneSwitcher.condition.hotkey.name") +
|
||||
std::string(" ") + std::to_string(count);
|
||||
_hotkey = Hotkey::GetHotkey(name, true);
|
||||
count++;
|
||||
}
|
||||
|
||||
bool MacroConditionHotkey::CheckCondition()
|
||||
{
|
||||
bool ret = _hotkey->GetPressed() ||
|
||||
_hotkey->GetLastPressed() > _lastCheck;
|
||||
_lastCheck = std::chrono::high_resolution_clock::now();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionHotkey::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_hotkey->Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionHotkey::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
if (!_hotkey->Load(obj)) {
|
||||
auto description = obs_data_get_string(obj, "desc");
|
||||
_hotkey = Hotkey::GetHotkey(description);
|
||||
vblog(LOG_WARNING,
|
||||
"hotkey name conflict for \"%s\" - using previous key bind",
|
||||
description);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MacroConditionHotkeyEdit::MacroConditionHotkeyEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionHotkey> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_name = new QLineEdit();
|
||||
QLabel *line1 = new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.hotkey.entry.line1"));
|
||||
QLabel *hint = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.condition.hotkey.tip"));
|
||||
|
||||
QWidget::connect(_name, SIGNAL(editingFinished()), this,
|
||||
SLOT(NameChanged()));
|
||||
|
||||
QHBoxLayout *switchLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{name}}", _name},
|
||||
};
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.hotkey.entry.line2"),
|
||||
switchLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(line1);
|
||||
mainLayout->addLayout(switchLayout);
|
||||
mainLayout->addWidget(hint);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionHotkeyEdit::NameChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
const auto name = _name->text().toStdString();
|
||||
// In case a hotkey is used by multiple conditions create a new hotkey
|
||||
// with the new description or get an existing hotkey matching this
|
||||
// description.
|
||||
// If the hotkey is only used by this single condition instance try to
|
||||
// update the description.
|
||||
// If updating the description runs into a conflict with an existing
|
||||
// hotkey use its settings instead.
|
||||
if (_entryData->_hotkey.use_count() > 1 ||
|
||||
!_entryData->_hotkey->UpdateDescription(name)) {
|
||||
_entryData->_hotkey = Hotkey::GetHotkey(name);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionHotkeyEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_name->setText(
|
||||
QString::fromStdString(_entryData->_hotkey->GetDescription()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
58
plugins/base/macro-condition-hotkey.hpp
Normal file
58
plugins/base/macro-condition-hotkey.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "hotkey-helpers.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLineEdit>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionHotkey : public MacroCondition {
|
||||
public:
|
||||
MacroConditionHotkey(Macro *m);
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionHotkey>(m);
|
||||
}
|
||||
|
||||
std::shared_ptr<Hotkey> _hotkey;
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point _lastCheck{};
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionHotkeyEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionHotkeyEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionHotkey> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionHotkeyEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionHotkey>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void NameChanged();
|
||||
|
||||
protected:
|
||||
QLineEdit *_name;
|
||||
std::shared_ptr<MacroConditionHotkey> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
76
plugins/base/macro-condition-idle.cpp
Normal file
76
plugins/base/macro-condition-idle.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "macro-condition-idle.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionIdle::id = "idle";
|
||||
|
||||
bool MacroConditionIdle::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionIdle::id,
|
||||
{MacroConditionIdle::Create, MacroConditionIdleEdit::Create,
|
||||
"AdvSceneSwitcher.condition.idle", false});
|
||||
|
||||
bool MacroConditionIdle::CheckCondition()
|
||||
{
|
||||
auto seconds = SecondsSinceLastInput();
|
||||
SetVariableValue(std::to_string(seconds));
|
||||
return seconds >= _duration.Seconds();
|
||||
}
|
||||
|
||||
bool MacroConditionIdle::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_duration.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionIdle::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_duration.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
MacroConditionIdleEdit::MacroConditionIdleEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionIdle> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_duration = new DurationSelection();
|
||||
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{duration}}", _duration},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.idle.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionIdleEdit::DurationChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = dur;
|
||||
}
|
||||
|
||||
void MacroConditionIdleEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
56
plugins/base/macro-condition-idle.hpp
Normal file
56
plugins/base/macro-condition-idle.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionIdle : public MacroCondition {
|
||||
public:
|
||||
MacroConditionIdle(Macro *m) : MacroCondition(m, true) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionIdle>(m);
|
||||
}
|
||||
|
||||
Duration _duration;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionIdleEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionIdleEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionIdle> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionIdleEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionIdle>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DurationChanged(const Duration &seconds);
|
||||
|
||||
protected:
|
||||
DurationSelection *_duration;
|
||||
std::shared_ptr<MacroConditionIdle> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
596
plugins/base/macro-condition-media.cpp
Normal file
596
plugins/base/macro-condition-media.cpp
Normal file
@@ -0,0 +1,596 @@
|
||||
#include "macro-condition-media.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "scene-switch-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionMedia::id = "media";
|
||||
|
||||
bool MacroConditionMedia::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionMedia::id,
|
||||
{MacroConditionMedia::Create, MacroConditionMediaEdit::Create,
|
||||
"AdvSceneSwitcher.condition.media"});
|
||||
|
||||
static const std::map<MacroConditionMedia::Time, std::string>
|
||||
mediaTimeRestrictions = {
|
||||
{MacroConditionMedia::Time::TIME_RESTRICTION_NONE,
|
||||
"AdvSceneSwitcher.mediaTab.timeRestriction.none"},
|
||||
{MacroConditionMedia::Time::TIME_RESTRICTION_SHORTER,
|
||||
"AdvSceneSwitcher.mediaTab.timeRestriction.shorter"},
|
||||
{MacroConditionMedia::Time::TIME_RESTRICTION_LONGER,
|
||||
"AdvSceneSwitcher.mediaTab.timeRestriction.longer"},
|
||||
{MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_SHORTER,
|
||||
"AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter"},
|
||||
{MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_LONGER,
|
||||
"AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger"},
|
||||
};
|
||||
|
||||
static const std::map<MacroConditionMedia::State, std::string> mediaStates = {
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_NONE,
|
||||
"AdvSceneSwitcher.mediaTab.states.none"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_PLAYING,
|
||||
"AdvSceneSwitcher.mediaTab.states.playing"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_OPENING,
|
||||
"AdvSceneSwitcher.mediaTab.states.opening"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_BUFFERING,
|
||||
"AdvSceneSwitcher.mediaTab.states.buffering"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_PAUSED,
|
||||
"AdvSceneSwitcher.mediaTab.states.paused"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_STOPPED,
|
||||
"AdvSceneSwitcher.mediaTab.states.stopped"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED,
|
||||
"AdvSceneSwitcher.mediaTab.states.ended"},
|
||||
{MacroConditionMedia::State::OBS_MEDIA_STATE_ERROR,
|
||||
"AdvSceneSwitcher.mediaTab.states.error"},
|
||||
{MacroConditionMedia::State::PLAYLIST_ENDED,
|
||||
"AdvSceneSwitcher.mediaTab.states.playlistEnd"},
|
||||
{MacroConditionMedia::State::ANY,
|
||||
"AdvSceneSwitcher.mediaTab.states.any"},
|
||||
};
|
||||
|
||||
MacroConditionMedia::~MacroConditionMedia()
|
||||
{
|
||||
OBSSourceAutoRelease mediasource =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckTime()
|
||||
{
|
||||
OBSSourceAutoRelease s =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
auto duration = obs_source_media_get_duration(s);
|
||||
auto currentTime = obs_source_media_get_time(s);
|
||||
|
||||
bool match = false;
|
||||
|
||||
switch (_restriction) {
|
||||
case Time::TIME_RESTRICTION_NONE:
|
||||
match = true;
|
||||
break;
|
||||
case Time::TIME_RESTRICTION_SHORTER:
|
||||
match = currentTime < _time.Milliseconds();
|
||||
break;
|
||||
case Time::TIME_RESTRICTION_LONGER:
|
||||
match = currentTime > _time.Milliseconds();
|
||||
break;
|
||||
case Time::TIME_RESTRICTION_REMAINING_SHORTER:
|
||||
match = duration > currentTime &&
|
||||
duration - currentTime < _time.Milliseconds();
|
||||
break;
|
||||
case Time::TIME_RESTRICTION_REMAINING_LONGER:
|
||||
match = duration > currentTime &&
|
||||
duration - currentTime > _time.Milliseconds();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckState()
|
||||
{
|
||||
OBSSourceAutoRelease s =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
obs_media_state currentState = obs_source_media_get_state(s);
|
||||
|
||||
bool match = false;
|
||||
// To be able to compare to obs_media_state more easily
|
||||
int expectedState = static_cast<int>(_state);
|
||||
|
||||
switch (_state) {
|
||||
case State::OBS_MEDIA_STATE_STOPPED:
|
||||
match = _stopped || currentState == OBS_MEDIA_STATE_STOPPED;
|
||||
break;
|
||||
case State::OBS_MEDIA_STATE_ENDED:
|
||||
match = _ended || currentState == OBS_MEDIA_STATE_ENDED;
|
||||
break;
|
||||
case State::PLAYLIST_ENDED:
|
||||
match = CheckPlaylistEnd(currentState);
|
||||
break;
|
||||
case State::ANY:
|
||||
match = true;
|
||||
break;
|
||||
case State::OBS_MEDIA_STATE_NONE:
|
||||
case State::OBS_MEDIA_STATE_PLAYING:
|
||||
case State::OBS_MEDIA_STATE_OPENING:
|
||||
case State::OBS_MEDIA_STATE_BUFFERING:
|
||||
case State::OBS_MEDIA_STATE_PAUSED:
|
||||
case State::OBS_MEDIA_STATE_ERROR:
|
||||
match = currentState == expectedState;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckPlaylistEnd(const obs_media_state currentState)
|
||||
{
|
||||
bool consecutiveEndedStates = false;
|
||||
if (_next || currentState != OBS_MEDIA_STATE_ENDED) {
|
||||
_previousStateEnded = false;
|
||||
}
|
||||
if (currentState == OBS_MEDIA_STATE_ENDED && _previousStateEnded) {
|
||||
consecutiveEndedStates = true;
|
||||
}
|
||||
_previousStateEnded = _ended || currentState == OBS_MEDIA_STATE_ENDED;
|
||||
return consecutiveEndedStates;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckMediaMatch()
|
||||
{
|
||||
if (!_source.GetSource()) {
|
||||
return false;
|
||||
}
|
||||
bool matched = CheckState() && CheckTime();
|
||||
|
||||
// reset for next check
|
||||
_stopped = false;
|
||||
_ended = false;
|
||||
_next = false;
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
void MacroConditionMedia::HandleSceneChange()
|
||||
{
|
||||
UpdateMediaSourcesOfSceneList();
|
||||
_lastConfigureScene = GetCurrentScene();
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckCondition()
|
||||
{
|
||||
bool match = false;
|
||||
switch (_sourceType) {
|
||||
case Type::ANY:
|
||||
for (auto &source : _sourceGroup) {
|
||||
match = match || source.CheckCondition();
|
||||
}
|
||||
break;
|
||||
case Type::ALL: {
|
||||
bool res = true;
|
||||
for (auto &source : _sourceGroup) {
|
||||
res = res && source.CheckCondition();
|
||||
}
|
||||
match = res;
|
||||
break;
|
||||
}
|
||||
case Type::SOURCE:
|
||||
match = CheckMediaMatch();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_lastConfigureScene != GetCurrentScene()) {
|
||||
HandleSceneChange();
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_source.Save(obj);
|
||||
_scene.Save(obj);
|
||||
obs_data_set_int(obj, "sourceType", static_cast<int>(_sourceType));
|
||||
obs_data_set_int(obj, "state", static_cast<int>(_state));
|
||||
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
|
||||
_time.Save(obj);
|
||||
obs_data_set_int(obj, "version", 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
{
|
||||
auto *sources = reinterpret_cast<std::vector<OBSWeakSource> *>(ptr);
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, enumSceneItem, ptr);
|
||||
}
|
||||
|
||||
auto source = obs_sceneitem_get_source(item);
|
||||
if (IsMediaSource(source)) {
|
||||
OBSWeakSourceAutoRelease weak =
|
||||
obs_source_get_weak_source(source);
|
||||
sources->emplace_back(weak);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroConditionMedia::UpdateMediaSourcesOfSceneList()
|
||||
{
|
||||
_sourceGroup.clear();
|
||||
if (!_scene.GetScene(false)) {
|
||||
return;
|
||||
}
|
||||
std::vector<OBSWeakSource> mediaSources;
|
||||
OBSSourceAutoRelease s =
|
||||
obs_weak_source_get_source(_scene.GetScene(false));
|
||||
auto scene = obs_scene_from_source(s);
|
||||
obs_scene_enum_items(scene, enumSceneItem, &mediaSources);
|
||||
_sourceGroup.reserve(mediaSources.size());
|
||||
|
||||
for (auto &source : mediaSources) {
|
||||
MacroConditionMedia cond(*this);
|
||||
cond._sourceType = Type::SOURCE;
|
||||
cond._source.SetSource(source);
|
||||
_sourceGroup.push_back(cond);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_source.Load(obj);
|
||||
_scene.Load(obj);
|
||||
_sourceType = static_cast<Type>(obs_data_get_int(obj, "sourceType"));
|
||||
_state = static_cast<MacroConditionMedia::State>(
|
||||
obs_data_get_int(obj, "state"));
|
||||
_restriction = static_cast<MacroConditionMedia::Time>(
|
||||
obs_data_get_int(obj, "restriction"));
|
||||
_time.Load(obj);
|
||||
|
||||
if (_sourceType == Type::SOURCE) {
|
||||
OBSSourceAutoRelease mediasource =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
signal_handler_t *sh =
|
||||
obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_connect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_connect(sh, "media_next", MediaNext, this);
|
||||
}
|
||||
|
||||
UpdateMediaSourcesOfSceneList();
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
if (_state ==
|
||||
MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED) {
|
||||
_state = MacroConditionMedia::State::PLAYLIST_ENDED;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionMedia::GetShortDesc() const
|
||||
{
|
||||
switch (_sourceType) {
|
||||
case Type::SOURCE:
|
||||
return _source.ToString();
|
||||
case Type::ANY:
|
||||
if (_scene.GetScene(false)) {
|
||||
return obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.anyOnScene") +
|
||||
std::string(" ") + _scene.ToString();
|
||||
}
|
||||
break;
|
||||
case Type::ALL:
|
||||
if (_scene.GetScene(false)) {
|
||||
return obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.allOnScene") +
|
||||
std::string(" ") + _scene.ToString();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void MacroConditionMedia::ClearSignalHandler()
|
||||
{
|
||||
OBSSourceAutoRelease mediasource =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
}
|
||||
|
||||
void MacroConditionMedia::ResetSignalHandler()
|
||||
{
|
||||
OBSSourceAutoRelease mediasource =
|
||||
obs_weak_source_get_source(_source.GetSource());
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_connect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_connect(sh, "media_next", MediaNext, this);
|
||||
}
|
||||
|
||||
void MacroConditionMedia::MediaStopped(void *data, calldata_t *)
|
||||
{
|
||||
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
|
||||
const auto macro = media->GetMacro();
|
||||
if (MacroIsPaused(macro)) {
|
||||
return;
|
||||
}
|
||||
media->_stopped = true;
|
||||
}
|
||||
|
||||
void MacroConditionMedia::MediaEnded(void *data, calldata_t *)
|
||||
{
|
||||
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
|
||||
const auto macro = media->GetMacro();
|
||||
if (MacroIsPaused(macro)) {
|
||||
return;
|
||||
}
|
||||
media->_ended = true;
|
||||
}
|
||||
|
||||
void MacroConditionMedia::MediaNext(void *data, calldata_t *)
|
||||
{
|
||||
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
|
||||
const auto macro = media->GetMacro();
|
||||
if (MacroIsPaused(macro)) {
|
||||
return;
|
||||
}
|
||||
media->_next = true;
|
||||
}
|
||||
|
||||
static void populateMediaTimes(QComboBox *list)
|
||||
{
|
||||
for (auto entry : mediaTimeRestrictions) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static void populateMediaStates(QComboBox *list)
|
||||
{
|
||||
for (auto entry : mediaStates) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static void populateSourceTypes(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.media.source"),
|
||||
static_cast<int>(MacroConditionMedia::Type::SOURCE));
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.media.anyOnScene"),
|
||||
static_cast<int>(MacroConditionMedia::Type::ANY));
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.media.allOnScene"),
|
||||
static_cast<int>(MacroConditionMedia::Type::ALL));
|
||||
}
|
||||
|
||||
MacroConditionMediaEdit::MacroConditionMediaEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
|
||||
: QWidget(parent),
|
||||
_sourceTypes(new QComboBox()),
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true, true,
|
||||
true)),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_states(new QComboBox()),
|
||||
_timeRestrictions(new QComboBox()),
|
||||
_time(new DurationSelection())
|
||||
{
|
||||
_states->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.inconsistencyInfo"));
|
||||
|
||||
auto sources = GetMediaSourceNames();
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
|
||||
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SourceTypeChanged(int)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_states, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(StateChanged(int)));
|
||||
QWidget::connect(_timeRestrictions, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(TimeRestrictionChanged(int)));
|
||||
QWidget::connect(_time, SIGNAL(DurationChanged(const Duration &)), this,
|
||||
SLOT(TimeChanged(const Duration &)));
|
||||
|
||||
populateSourceTypes(_sourceTypes);
|
||||
populateMediaStates(_states);
|
||||
populateMediaTimes(_timeRestrictions);
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sourceTypes}}", _sourceTypes},
|
||||
{"{{mediaSources}}", _sources},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{states}}", _states},
|
||||
{"{{timeRestrictions}}", _timeRestrictions},
|
||||
{"{{time}}", _time},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.media.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::SourceTypeChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sourceType = static_cast<MacroConditionMedia::Type>(
|
||||
_sourceTypes->itemData(idx).toInt());
|
||||
|
||||
if (_entryData->_sourceType == MacroConditionMedia::Type::SOURCE) {
|
||||
_entryData->_sourceGroup.clear();
|
||||
}
|
||||
|
||||
_entryData->ResetSignalHandler();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sourceGroup.clear();
|
||||
_entryData->_sourceType = MacroConditionMedia::Type::SOURCE;
|
||||
_entryData->ClearSignalHandler();
|
||||
_entryData->_source = source;
|
||||
_entryData->ResetSignalHandler();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
_entryData->UpdateMediaSourcesOfSceneList();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
MacroConditionMedia::State getMediaStateFromIdx(int idx)
|
||||
{
|
||||
if (idx < static_cast<int>(
|
||||
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE)) {
|
||||
return static_cast<MacroConditionMedia::State>(idx);
|
||||
} else {
|
||||
return static_cast<MacroConditionMedia::State>(
|
||||
idx -
|
||||
static_cast<int>(
|
||||
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) +
|
||||
custom_media_states_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::StateChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_state = getMediaStateFromIdx(index);
|
||||
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
|
||||
_entryData->UpdateMediaSourcesOfSceneList();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::TimeRestrictionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<MacroConditionMedia::Time>(index) ==
|
||||
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
|
||||
_time->setDisabled(true);
|
||||
} else {
|
||||
_time->setDisabled(false);
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_restriction =
|
||||
static_cast<MacroConditionMedia::Time>(index);
|
||||
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
|
||||
_entryData->UpdateMediaSourcesOfSceneList();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::TimeChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_time = dur;
|
||||
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
|
||||
_entryData->UpdateMediaSourcesOfSceneList();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::SetWidgetVisibility()
|
||||
{
|
||||
_sources->setVisible(_entryData->_sourceType ==
|
||||
MacroConditionMedia::Type::SOURCE);
|
||||
_scenes->setVisible(_entryData->_sourceType !=
|
||||
MacroConditionMedia::Type::SOURCE);
|
||||
}
|
||||
|
||||
static int getIdxFromMediaState(MacroConditionMedia::State state)
|
||||
{
|
||||
if (state < MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) {
|
||||
return static_cast<int>(state);
|
||||
} else {
|
||||
return static_cast<int>(state) - custom_media_states_offset +
|
||||
static_cast<int>(
|
||||
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionMediaEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sourceTypes->setCurrentIndex(_sourceTypes->findData(
|
||||
static_cast<int>(_entryData->_sourceType)));
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_states->setCurrentIndex(getIdxFromMediaState(_entryData->_state));
|
||||
_timeRestrictions->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_restriction));
|
||||
_time->SetDuration(_entryData->_time);
|
||||
if (_entryData->_restriction ==
|
||||
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
|
||||
_time->setDisabled(true);
|
||||
}
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
136
plugins/base/macro-condition-media.hpp
Normal file
136
plugins/base/macro-condition-media.hpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "source-selection.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
constexpr auto custom_media_states_offset = 100;
|
||||
|
||||
class MacroConditionMedia : public MacroCondition {
|
||||
public:
|
||||
MacroConditionMedia(Macro *m) : MacroCondition(m) {}
|
||||
~MacroConditionMedia();
|
||||
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<MacroConditionMedia>(m);
|
||||
}
|
||||
void ClearSignalHandler();
|
||||
void ResetSignalHandler();
|
||||
void UpdateMediaSourcesOfSceneList();
|
||||
static void MediaStopped(void *data, calldata_t *);
|
||||
static void MediaEnded(void *data, calldata_t *);
|
||||
static void MediaNext(void *data, calldata_t *);
|
||||
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
ANY,
|
||||
ALL,
|
||||
};
|
||||
Type _sourceType = Type::SOURCE;
|
||||
|
||||
enum class State {
|
||||
// OBS's internal states
|
||||
OBS_MEDIA_STATE_NONE,
|
||||
OBS_MEDIA_STATE_PLAYING,
|
||||
OBS_MEDIA_STATE_OPENING,
|
||||
OBS_MEDIA_STATE_BUFFERING,
|
||||
OBS_MEDIA_STATE_PAUSED,
|
||||
OBS_MEDIA_STATE_STOPPED,
|
||||
OBS_MEDIA_STATE_ENDED,
|
||||
OBS_MEDIA_STATE_ERROR,
|
||||
// Just a marker
|
||||
LAST_OBS_MEDIA_STATE,
|
||||
// states added for use in the plugin
|
||||
PLAYLIST_ENDED = custom_media_states_offset,
|
||||
ANY,
|
||||
};
|
||||
State _state = State::OBS_MEDIA_STATE_NONE;
|
||||
|
||||
enum class Time {
|
||||
TIME_RESTRICTION_NONE,
|
||||
TIME_RESTRICTION_SHORTER,
|
||||
TIME_RESTRICTION_LONGER,
|
||||
TIME_RESTRICTION_REMAINING_SHORTER,
|
||||
TIME_RESTRICTION_REMAINING_LONGER,
|
||||
};
|
||||
Time _restriction = Time::TIME_RESTRICTION_NONE;
|
||||
|
||||
SceneSelection _scene;
|
||||
SourceSelection _source;
|
||||
OBSWeakSource _rawSource = nullptr;
|
||||
std::vector<MacroConditionMedia> _sourceGroup;
|
||||
Duration _time;
|
||||
|
||||
private:
|
||||
bool CheckTime();
|
||||
bool CheckState();
|
||||
bool CheckPlaylistEnd(const obs_media_state);
|
||||
bool CheckMediaMatch();
|
||||
void HandleSceneChange();
|
||||
|
||||
bool _stopped = false;
|
||||
bool _ended = false;
|
||||
bool _next = false;
|
||||
|
||||
// Workaround to enable use of "ended" to specify end of VLC playlist
|
||||
bool _previousStateEnded = false;
|
||||
// Used to keep track of scene changes
|
||||
OBSWeakSource _lastConfigureScene;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionMediaEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionMediaEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionMedia> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionMediaEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionMedia>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SourceTypeChanged(int);
|
||||
void SourceChanged(const SourceSelection &);
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void StateChanged(int index);
|
||||
void TimeRestrictionChanged(int index);
|
||||
void TimeChanged(const Duration &seconds);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_sourceTypes;
|
||||
SceneSelectionWidget *_scenes;
|
||||
SourceSelectionWidget *_sources;
|
||||
QComboBox *_states;
|
||||
QComboBox *_timeRestrictions;
|
||||
DurationSelection *_time;
|
||||
std::shared_ptr<MacroConditionMedia> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
592
plugins/base/macro-condition-obs-stats.cpp
Normal file
592
plugins/base/macro-condition-obs-stats.cpp
Normal file
@@ -0,0 +1,592 @@
|
||||
#include "macro-condition-obs-stats.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionStats::id = "obs_stats";
|
||||
|
||||
bool MacroConditionStats::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionStats::id,
|
||||
{MacroConditionStats::Create, MacroConditionStatsEdit::Create,
|
||||
"AdvSceneSwitcher.condition.stats"});
|
||||
|
||||
const static std::map<MacroConditionStats::Type, std::string> statsTypes = {
|
||||
{MacroConditionStats::Type::FPS,
|
||||
"AdvSceneSwitcher.condition.stats.type.fps"},
|
||||
{MacroConditionStats::Type::CPU_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.CPUUsage"},
|
||||
{MacroConditionStats::Type::DISK_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable"},
|
||||
{MacroConditionStats::Type::MEM_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.memoryUsage"},
|
||||
{MacroConditionStats::Type::AVG_FRAMETIME,
|
||||
"AdvSceneSwitcher.condition.stats.type.averageTimeToRender"},
|
||||
{MacroConditionStats::Type::RENDER_LAG,
|
||||
"AdvSceneSwitcher.condition.stats.type.missedFrames"},
|
||||
{MacroConditionStats::Type::ENCODE_LAG,
|
||||
"AdvSceneSwitcher.condition.stats.type.skippedFrames"},
|
||||
{MacroConditionStats::Type::STREAM_DROPPED_FRAMES,
|
||||
"AdvSceneSwitcher.condition.stats.type.droppedFrames.stream"},
|
||||
{MacroConditionStats::Type::STREAM_BITRATE,
|
||||
"AdvSceneSwitcher.condition.stats.type.bitrate.stream"},
|
||||
{MacroConditionStats::Type::STREAM_MB_SENT,
|
||||
"AdvSceneSwitcher.condition.stats.type.megabytesSent.stream"},
|
||||
{MacroConditionStats::Type::RECORDING_DROPPED_FRAMES,
|
||||
"AdvSceneSwitcher.condition.stats.type.droppedFrames.recording"},
|
||||
{MacroConditionStats::Type::RECORDING_BITRATE,
|
||||
"AdvSceneSwitcher.condition.stats.type.bitrate.recording"},
|
||||
{MacroConditionStats::Type::RECORDING_MB_SENT,
|
||||
"AdvSceneSwitcher.condition.stats.type.megabytesSent.recording"},
|
||||
};
|
||||
|
||||
const static std::map<MacroConditionStats::Condition, std::string>
|
||||
statsConditionTypes = {
|
||||
{MacroConditionStats::Condition::ABOVE,
|
||||
"AdvSceneSwitcher.condition.stats.condition.above"},
|
||||
{MacroConditionStats::Condition::EQUALS,
|
||||
"AdvSceneSwitcher.condition.stats.condition.equals"},
|
||||
{MacroConditionStats::Condition::BELOW,
|
||||
"AdvSceneSwitcher.condition.stats.condition.below"},
|
||||
};
|
||||
|
||||
MacroConditionStats::MacroConditionStats(Macro *m)
|
||||
: MacroCondition(m), _cpu_info(os_cpu_usage_info_start())
|
||||
{
|
||||
}
|
||||
|
||||
MacroConditionStats::~MacroConditionStats()
|
||||
{
|
||||
os_cpu_usage_info_destroy(_cpu_info);
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckFPS()
|
||||
{
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return obs_get_active_fps() > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(obs_get_active_fps(), _value, 0.01);
|
||||
case Condition::BELOW:
|
||||
return obs_get_active_fps() < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckCPU()
|
||||
{
|
||||
double usage = os_cpu_usage_info_query(_cpu_info);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return usage > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(usage, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return usage < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckMemory()
|
||||
{
|
||||
auto rss =
|
||||
(long double)os_get_proc_resident_size() / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return rss > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(rss, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return rss < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckAvgFrametime()
|
||||
{
|
||||
auto num = (long double)obs_get_average_frame_time_ns() / 1000000.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return num > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(num, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRenderLag()
|
||||
{
|
||||
uint32_t total_rendered = obs_get_total_frames();
|
||||
uint32_t total_lagged = obs_get_lagged_frames();
|
||||
|
||||
if (total_rendered < _first_rendered || total_lagged < _first_lagged) {
|
||||
_first_rendered = total_rendered;
|
||||
_first_lagged = total_lagged;
|
||||
}
|
||||
total_rendered -= _first_rendered;
|
||||
total_lagged -= _first_lagged;
|
||||
|
||||
auto num = total_rendered ? (long double)total_lagged /
|
||||
(long double)total_rendered
|
||||
: 0.0l;
|
||||
num *= 100.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return num > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(num, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckEncodeLag()
|
||||
{
|
||||
video_t *video = obs_get_video();
|
||||
uint32_t total_encoded = video_output_get_total_frames(video);
|
||||
uint32_t total_skipped = video_output_get_skipped_frames(video);
|
||||
|
||||
if (total_encoded < _first_encoded || total_skipped < _first_encoded) {
|
||||
_first_encoded = total_encoded;
|
||||
_first_skipped = total_skipped;
|
||||
}
|
||||
total_encoded -= _first_encoded;
|
||||
total_skipped -= _first_skipped;
|
||||
|
||||
auto num = total_encoded ? (long double)total_skipped /
|
||||
(long double)total_encoded
|
||||
: 0.0l;
|
||||
num *= 100.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return num > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(num, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroConditionStats::OutputInfo::Update(obs_output_t *output)
|
||||
{
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
uint64_t curTime = os_gettime_ns();
|
||||
uint64_t bytesSent = totalBytes;
|
||||
|
||||
if (bytesSent < lastBytesSent)
|
||||
bytesSent = 0;
|
||||
if (bytesSent == 0)
|
||||
lastBytesSent = 0;
|
||||
|
||||
uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
|
||||
long double timePassed =
|
||||
(long double)(curTime - lastBytesSentTime) / 1000000000.0l;
|
||||
kbps = (long double)bitsBetween / timePassed / 1000.0l;
|
||||
|
||||
if (timePassed < 0.01l)
|
||||
kbps = 0.0l;
|
||||
|
||||
int total = output ? obs_output_get_total_frames(output) : 0;
|
||||
int dropped = output ? obs_output_get_frames_dropped(output) : 0;
|
||||
|
||||
if (total < first_total || dropped < first_dropped) {
|
||||
first_total = 0;
|
||||
first_dropped = 0;
|
||||
}
|
||||
|
||||
total -= first_total;
|
||||
dropped -= first_dropped;
|
||||
|
||||
dropped_relative =
|
||||
total ? (long double)dropped / (long double)total * 100.0l
|
||||
: 0.0l;
|
||||
|
||||
lastBytesSent = bytesSent;
|
||||
lastBytesSentTime = curTime;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamDroppedFrames()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
_streamInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return _streamInfo.dropped_relative > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(_streamInfo.dropped_relative, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return _streamInfo.dropped_relative < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamBitrate()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
_streamInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return _streamInfo.kbps > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(_streamInfo.kbps, _value, 1.0);
|
||||
case Condition::BELOW:
|
||||
return _streamInfo.kbps < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamMBSent()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
obs_output_release(output);
|
||||
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return num > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(num, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingDroppedFrames()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
_recordingInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return _recordingInfo.dropped_relative > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(_recordingInfo.dropped_relative, _value,
|
||||
0.1);
|
||||
case Condition::BELOW:
|
||||
return _recordingInfo.dropped_relative < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingBitrate()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
_recordingInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return _recordingInfo.kbps > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(_recordingInfo.kbps, _value, 1.0);
|
||||
case Condition::BELOW:
|
||||
return _recordingInfo.kbps < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingMBSent()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
obs_output_release(output);
|
||||
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
return num > _value;
|
||||
case Condition::EQUALS:
|
||||
return DoubleEquals(num, _value, 0.1);
|
||||
case Condition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckCondition()
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::FPS:
|
||||
return CheckFPS();
|
||||
case Type::CPU_USAGE:
|
||||
return CheckCPU();
|
||||
case Type::DISK_USAGE:
|
||||
// TODO: not implemented
|
||||
break;
|
||||
case Type::MEM_USAGE:
|
||||
return CheckMemory();
|
||||
case Type::AVG_FRAMETIME:
|
||||
return CheckAvgFrametime();
|
||||
case Type::RENDER_LAG:
|
||||
return CheckRenderLag();
|
||||
case Type::ENCODE_LAG:
|
||||
return CheckEncodeLag();
|
||||
case Type::STREAM_DROPPED_FRAMES:
|
||||
return CheckStreamDroppedFrames();
|
||||
case Type::STREAM_BITRATE:
|
||||
return CheckStreamBitrate();
|
||||
case Type::STREAM_MB_SENT:
|
||||
return CheckStreamMBSent();
|
||||
case Type::RECORDING_DROPPED_FRAMES:
|
||||
return CheckRecordingDroppedFrames();
|
||||
case Type::RECORDING_BITRATE:
|
||||
return CheckRecordingBitrate();
|
||||
case Type::RECORDING_MB_SENT:
|
||||
return CheckRecordingMBSent();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_value.Save(obj, "value");
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::Load(obs_data_t *obj)
|
||||
{
|
||||
|
||||
MacroCondition::Load(obj);
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_value = obs_data_get_double(obj, "value");
|
||||
} else {
|
||||
_value.Load(obj, "value");
|
||||
}
|
||||
_type = static_cast<MacroConditionStats::Type>(
|
||||
obs_data_get_int(obj, "type"));
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionStats::GetShortDesc() const
|
||||
{
|
||||
auto it = statsTypes.find(_type);
|
||||
if (it != statsTypes.end()) {
|
||||
return obs_module_text(it->second.c_str());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateStatsTypes(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : statsTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
// TODO: not implemented
|
||||
if (entry.first == MacroConditionStats::Type::DISK_USAGE) {
|
||||
qobject_cast<QListView *>(list->view())
|
||||
->setRowHidden(list->count() - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : statsConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionStatsEdit::MacroConditionStatsEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionStats> entryData)
|
||||
: QWidget(parent),
|
||||
_stats(new QComboBox()),
|
||||
_condition(new QComboBox()),
|
||||
_value(new VariableDoubleSpinBox())
|
||||
{
|
||||
_value->setMaximum(999999999999);
|
||||
|
||||
populateStatsTypes(_stats);
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.stats.dockHint"));
|
||||
|
||||
QWidget::connect(
|
||||
_value,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(ValueChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_stats, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(StatsTypeChanged(int)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{value}}", _value},
|
||||
{"{{stats}}", _stats},
|
||||
{"{{condition}}", _condition},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.stats.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::ValueChanged(const NumberVariable<double> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_value = value;
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::StatsTypeChanged(int type)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_type =
|
||||
static_cast<MacroConditionStats::Type>(type);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
_value->SetFixedValue(0);
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionStats::Condition>(cond);
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_value->SetValue(_entryData->_value);
|
||||
_stats->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_entryData->_type) {
|
||||
case MacroConditionStats::Type::FPS:
|
||||
_value->setMaximum(1000);
|
||||
_value->setSuffix("");
|
||||
break;
|
||||
case MacroConditionStats::Type::CPU_USAGE:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case MacroConditionStats::Type::DISK_USAGE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case MacroConditionStats::Type::MEM_USAGE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case MacroConditionStats::Type::AVG_FRAMETIME:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("ms");
|
||||
break;
|
||||
case MacroConditionStats::Type::RENDER_LAG:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case MacroConditionStats::Type::ENCODE_LAG:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case MacroConditionStats::Type::STREAM_DROPPED_FRAMES:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case MacroConditionStats::Type::STREAM_BITRATE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("kb/s");
|
||||
break;
|
||||
case MacroConditionStats::Type::STREAM_MB_SENT:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case MacroConditionStats::Type::RECORDING_DROPPED_FRAMES:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case MacroConditionStats::Type::RECORDING_BITRATE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("kb/s");
|
||||
break;
|
||||
case MacroConditionStats::Type::RECORDING_MB_SENT:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
123
plugins/base/macro-condition-obs-stats.hpp
Normal file
123
plugins/base/macro-condition-obs-stats.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <util/platform.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionStats : public MacroCondition {
|
||||
public:
|
||||
MacroConditionStats(Macro *m);
|
||||
~MacroConditionStats();
|
||||
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<MacroConditionStats>(m);
|
||||
}
|
||||
|
||||
NumberVariable<double> _value = 0.0;
|
||||
|
||||
enum class Type {
|
||||
FPS,
|
||||
CPU_USAGE,
|
||||
DISK_USAGE, // not implemented
|
||||
MEM_USAGE,
|
||||
AVG_FRAMETIME,
|
||||
RENDER_LAG,
|
||||
ENCODE_LAG,
|
||||
STREAM_DROPPED_FRAMES,
|
||||
STREAM_BITRATE,
|
||||
STREAM_MB_SENT,
|
||||
RECORDING_DROPPED_FRAMES, // not sure if this makes sense
|
||||
RECORDING_BITRATE,
|
||||
RECORDING_MB_SENT,
|
||||
};
|
||||
Type _type = Type::FPS;
|
||||
|
||||
enum class Condition {
|
||||
ABOVE,
|
||||
EQUALS,
|
||||
BELOW,
|
||||
};
|
||||
Condition _condition = Condition::ABOVE;
|
||||
|
||||
private:
|
||||
bool CheckFPS();
|
||||
bool CheckCPU();
|
||||
bool CheckMemory();
|
||||
bool CheckAvgFrametime();
|
||||
bool CheckRenderLag();
|
||||
bool CheckEncodeLag();
|
||||
bool CheckStreamDroppedFrames();
|
||||
bool CheckStreamBitrate();
|
||||
bool CheckStreamMBSent();
|
||||
bool CheckRecordingDroppedFrames();
|
||||
bool CheckRecordingBitrate();
|
||||
bool CheckRecordingMBSent();
|
||||
|
||||
os_cpu_usage_info_t *_cpu_info = nullptr;
|
||||
uint32_t _first_encoded = 0xFFFFFFFF;
|
||||
uint32_t _first_skipped = 0xFFFFFFFF;
|
||||
uint32_t _first_rendered = 0xFFFFFFFF;
|
||||
uint32_t _first_lagged = 0xFFFFFFFF;
|
||||
struct OutputInfo {
|
||||
void Update(obs_output_t *output);
|
||||
|
||||
uint64_t lastBytesSent = 0;
|
||||
uint64_t lastBytesSentTime = 0;
|
||||
int first_total = 0;
|
||||
int first_dropped = 0;
|
||||
double dropped_relative = 0.0;
|
||||
long double kbps = 0.0l;
|
||||
};
|
||||
OutputInfo _streamInfo;
|
||||
OutputInfo _recordingInfo;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionStatsEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionStatsEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionStats> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionStatsEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionStats>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ValueChanged(const NumberVariable<double> &value);
|
||||
void StatsTypeChanged(int type);
|
||||
void ConditionChanged(int cond);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_stats;
|
||||
QComboBox *_condition;
|
||||
VariableDoubleSpinBox *_value;
|
||||
std::shared_ptr<MacroConditionStats> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
186
plugins/base/macro-condition-plugin-state.cpp
Normal file
186
plugins/base/macro-condition-plugin-state.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "macro-condition-plugin-state.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const uint32_t MacroConditionPluginState::_version = 1;
|
||||
|
||||
const std::string MacroConditionPluginState::id = "plugin_state";
|
||||
|
||||
bool MacroConditionPluginState::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionPluginState::id,
|
||||
{MacroConditionPluginState::Create,
|
||||
MacroConditionPluginStateEdit::Create,
|
||||
"AdvSceneSwitcher.condition.pluginState"});
|
||||
|
||||
const static std::map<MacroConditionPluginState::Condition, std::string>
|
||||
pluginStateConditionTypes = {
|
||||
{MacroConditionPluginState::Condition::PLUGIN_START,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.start"},
|
||||
{MacroConditionPluginState::Condition::PLUGIN_RESTART,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.restart"},
|
||||
{MacroConditionPluginState::Condition::PLUGIN_RUNNING,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.running"},
|
||||
{MacroConditionPluginState::Condition::OBS_SHUTDOWN,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.shutdown"},
|
||||
{MacroConditionPluginState::Condition::SCENE_COLLECTION_CHANGE,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.sceneCollection"},
|
||||
{MacroConditionPluginState::Condition::PLUGIN_SCENE_CHANGE,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.sceneSwitched"},
|
||||
};
|
||||
|
||||
MacroConditionPluginState::~MacroConditionPluginState()
|
||||
{
|
||||
if (_condition == Condition::OBS_SHUTDOWN) {
|
||||
GetShutdownConditionCount()--;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroConditionPluginState::CheckCondition()
|
||||
{
|
||||
switch (_condition) {
|
||||
case Condition::PLUGIN_SCENE_CHANGE:
|
||||
return MacroSwitchedScene();
|
||||
case Condition::PLUGIN_RUNNING:
|
||||
return true;
|
||||
case Condition::OBS_SHUTDOWN:
|
||||
return OBSIsShuttingDown();
|
||||
case Condition::PLUGIN_START:
|
||||
return IsFirstInterval();
|
||||
case Condition::PLUGIN_RESTART:
|
||||
return IsFirstIntervalAfterStop();
|
||||
case Condition::SCENE_COLLECTION_CHANGE:
|
||||
if (_firstCheckAfterSceneCollectionChange) {
|
||||
_firstCheckAfterSceneCollectionChange = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionPluginState::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_int(obj, "version", _version);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionPluginState::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
|
||||
// TODO: Remove fallback for older version
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
enum class PluginStateCondition {
|
||||
SCENE_SWITCHED,
|
||||
RUNNING,
|
||||
SHUTDOWN,
|
||||
};
|
||||
auto oldValue = static_cast<PluginStateCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
switch (oldValue) {
|
||||
case PluginStateCondition::SCENE_SWITCHED:
|
||||
SetCondition(Condition::PLUGIN_SCENE_CHANGE);
|
||||
break;
|
||||
case PluginStateCondition::RUNNING:
|
||||
SetCondition(Condition::PLUGIN_RUNNING);
|
||||
break;
|
||||
case PluginStateCondition::SHUTDOWN:
|
||||
SetCondition(Condition::OBS_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
SetCondition(static_cast<Condition>(
|
||||
obs_data_get_int(obj, "condition")));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroConditionPluginState::SetCondition(Condition newValue)
|
||||
{
|
||||
if (_condition == Condition::OBS_SHUTDOWN) {
|
||||
GetShutdownConditionCount()--;
|
||||
}
|
||||
if (newValue == Condition::OBS_SHUTDOWN) {
|
||||
GetShutdownConditionCount()++;
|
||||
}
|
||||
_condition = newValue;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[action, name] : pluginStateConditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(action));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionPluginStateEdit::MacroConditionPluginStateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionPluginState> entryData)
|
||||
: QWidget(parent),
|
||||
_condition(new QComboBox()),
|
||||
_shutdownLimitation(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.pluginState.state.shutdown.limitation")))
|
||||
{
|
||||
_shutdownLimitation->setWordWrap(true);
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{condition}}", _condition},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.pluginState.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
entryLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_shutdownLimitation);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionPluginStateEdit::ConditionChanged(int idx)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetCondition(
|
||||
static_cast<MacroConditionPluginState::Condition>(
|
||||
_condition->itemData(idx).toInt()));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionPluginStateEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_condition->setCurrentIndex(_condition->findData(
|
||||
static_cast<int>(_entryData->GetCondition())));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionPluginStateEdit::SetWidgetVisibility()
|
||||
{
|
||||
_shutdownLimitation->setVisible(
|
||||
_entryData->GetCondition() ==
|
||||
MacroConditionPluginState::Condition::OBS_SHUTDOWN);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
72
plugins/base/macro-condition-plugin-state.hpp
Normal file
72
plugins/base/macro-condition-plugin-state.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionPluginState : public MacroCondition {
|
||||
public:
|
||||
MacroConditionPluginState(Macro *m) : MacroCondition(m) {}
|
||||
~MacroConditionPluginState();
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionPluginState>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
PLUGIN_START,
|
||||
PLUGIN_RESTART,
|
||||
PLUGIN_RUNNING,
|
||||
OBS_SHUTDOWN,
|
||||
SCENE_COLLECTION_CHANGE,
|
||||
PLUGIN_SCENE_CHANGE,
|
||||
};
|
||||
void SetCondition(Condition);
|
||||
Condition GetCondition() const { return _condition; }
|
||||
|
||||
private:
|
||||
Condition _condition = Condition::PLUGIN_SCENE_CHANGE;
|
||||
bool _firstCheckAfterSceneCollectionChange = true;
|
||||
|
||||
const static uint32_t _version;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionPluginStateEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionPluginStateEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionPluginState> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionPluginStateEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionPluginState>(
|
||||
cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int idx);
|
||||
|
||||
protected:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_condition;
|
||||
QLabel *_shutdownLimitation;
|
||||
std::shared_ptr<MacroConditionPluginState> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
201
plugins/base/macro-condition-process.cpp
Normal file
201
plugins/base/macro-condition-process.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "macro-condition-process.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionProcess::id = "process";
|
||||
|
||||
bool MacroConditionProcess::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionProcess::id,
|
||||
{MacroConditionProcess::Create, MacroConditionProcessEdit::Create,
|
||||
"AdvSceneSwitcher.condition.process"});
|
||||
|
||||
bool MacroConditionProcess::CheckCondition()
|
||||
{
|
||||
QStringList runningProcesses;
|
||||
QString proc = QString::fromStdString(_process);
|
||||
GetProcessList(runningProcesses);
|
||||
std::string foregroundProcessName;
|
||||
GetForegroundProcessName(foregroundProcessName);
|
||||
|
||||
SetVariableValue(foregroundProcessName);
|
||||
|
||||
if (!_regex.Enabled()) {
|
||||
if (runningProcesses.contains(proc) &&
|
||||
(!_checkFocus || IsInFocus(proc))) {
|
||||
SetTempVarValue("name", proc.toStdString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto matchIndex = runningProcesses.indexOf(QRegularExpression(proc));
|
||||
if (matchIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
if (!_checkFocus) {
|
||||
SetTempVarValue("name",
|
||||
runningProcesses.at(matchIndex).toStdString());
|
||||
return true;
|
||||
}
|
||||
if (!IsInFocus(proc)) {
|
||||
return false;
|
||||
}
|
||||
SetTempVarValue("name", foregroundProcessName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionProcess::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_string(obj, "process", _process.c_str());
|
||||
obs_data_set_bool(obj, "focus", _checkFocus);
|
||||
_regex.Save(obj);
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionProcess::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_process = obs_data_get_string(obj, "process");
|
||||
_checkFocus = obs_data_get_bool(obj, "focus");
|
||||
// Fall back to partial match regex as default for old version
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_regex.SetEnabled(true);
|
||||
} else {
|
||||
_regex.Load(obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionProcess::GetShortDesc() const
|
||||
{
|
||||
return _process;
|
||||
}
|
||||
|
||||
void MacroConditionProcess::SetupTempVars()
|
||||
{
|
||||
MacroCondition::SetupTempVars();
|
||||
AddTempvar("name",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.process.name"));
|
||||
}
|
||||
|
||||
MacroConditionProcessEdit::MacroConditionProcessEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionProcess> entryData)
|
||||
: QWidget(parent),
|
||||
_processSelection(new QComboBox()),
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_focused(new QCheckBox()),
|
||||
_focusProcess(new QLabel()),
|
||||
_focusLayout(new QHBoxLayout())
|
||||
{
|
||||
_processSelection->setEditable(true);
|
||||
_processSelection->setMaxVisibleItems(20);
|
||||
|
||||
QWidget::connect(_processSelection,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(ProcessChanged(const QString &)));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(FocusChanged(int)));
|
||||
QWidget::connect(&_timer, SIGNAL(timeout()), this,
|
||||
SLOT(UpdateFocusProcess()));
|
||||
|
||||
PopulateProcessSelection(_processSelection);
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{processes}}", _processSelection},
|
||||
{"{{regex}}", _regex},
|
||||
{"{{focused}}", _focused},
|
||||
{"{{focusProcess}}", _focusProcess},
|
||||
};
|
||||
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.process.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.process.entry.focus"),
|
||||
_focusLayout, widgetPlaceholders);
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addLayout(_focusLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
|
||||
_timer.start(1000);
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::ProcessChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_process = text.toStdString();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regex = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::FocusChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_checkFocus = state;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::UpdateFocusProcess()
|
||||
{
|
||||
std::string name;
|
||||
GetForegroundProcessName(name);
|
||||
_focusProcess->setText(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
SetLayoutVisible(_focusLayout, _entryData->_checkFocus);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionProcessEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_processSelection->setCurrentText(_entryData->_process.c_str());
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_focused->setChecked(_entryData->_checkFocus);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
71
plugins/base/macro-condition-process.hpp
Normal file
71
plugins/base/macro-condition-process.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionProcess : public MacroCondition {
|
||||
public:
|
||||
MacroConditionProcess(Macro *m) : MacroCondition(m, true) {}
|
||||
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<MacroConditionProcess>(m);
|
||||
}
|
||||
|
||||
std::string _process;
|
||||
bool _checkFocus = true;
|
||||
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionProcessEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionProcessEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionProcess> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionProcessEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionProcess>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ProcessChanged(const QString &text);
|
||||
void RegexChanged(const RegexConfig &);
|
||||
void FocusChanged(int state);
|
||||
void UpdateFocusProcess();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_processSelection;
|
||||
RegexConfigWidget *_regex;
|
||||
QCheckBox *_focused;
|
||||
QLabel *_focusProcess;
|
||||
QHBoxLayout *_focusLayout;
|
||||
QTimer _timer;
|
||||
std::shared_ptr<MacroConditionProcess> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
86
plugins/base/macro-condition-profile.cpp
Normal file
86
plugins/base/macro-condition-profile.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "macro-condition-profile.hpp"
|
||||
#include "profile-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionProfile::id = "profile";
|
||||
|
||||
bool MacroConditionProfile::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionProfile::id,
|
||||
{MacroConditionProfile::Create, MacroConditionProfileEdit::Create,
|
||||
"AdvSceneSwitcher.condition.profile"});
|
||||
|
||||
bool MacroConditionProfile::CheckCondition()
|
||||
{
|
||||
auto currentProfile = obs_frontend_get_current_profile();
|
||||
const bool match = _profile == currentProfile;
|
||||
bfree(currentProfile);
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionProfile::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_string(obj, "profile", _profile.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionProfile::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_profile = obs_data_get_string(obj, "profile");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionProfile::GetShortDesc() const
|
||||
{
|
||||
return _profile;
|
||||
}
|
||||
|
||||
MacroConditionProfileEdit::MacroConditionProfileEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionProfile> entryData)
|
||||
: QWidget(parent), _profiles(new QComboBox())
|
||||
{
|
||||
PopulateProfileSelection(_profiles);
|
||||
QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(ProfileChanged(const QString &)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{profiles}}", _profiles},
|
||||
};
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.profile.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionProfileEdit::ProfileChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_profile = text.toStdString();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionProfileEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_profiles->setCurrentText(QString::fromStdString(_entryData->_profile));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
58
plugins/base/macro-condition-profile.hpp
Normal file
58
plugins/base/macro-condition-profile.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionProfile : public MacroCondition {
|
||||
public:
|
||||
MacroConditionProfile(Macro *m) : MacroCondition(m) {}
|
||||
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<MacroConditionProfile>(m);
|
||||
}
|
||||
|
||||
std::string _profile;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionProfileEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionProfileEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionProfile> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionProfileEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionProfile>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ProfileChanged(const QString &text);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_profiles;
|
||||
std::shared_ptr<MacroConditionProfile> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
203
plugins/base/macro-condition-recording.cpp
Normal file
203
plugins/base/macro-condition-recording.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "macro-condition-recording.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionRecord::id = "recording";
|
||||
|
||||
bool MacroConditionRecord::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionRecord::id,
|
||||
{MacroConditionRecord::Create, MacroConditionRecordEdit::Create,
|
||||
"AdvSceneSwitcher.condition.record"});
|
||||
|
||||
const static std::map<MacroConditionRecord::Condition, std::string>
|
||||
recordStates = {
|
||||
{MacroConditionRecord::Condition::STOP,
|
||||
"AdvSceneSwitcher.condition.record.state.stop"},
|
||||
{MacroConditionRecord::Condition::PAUSE,
|
||||
"AdvSceneSwitcher.condition.record.state.pause"},
|
||||
{MacroConditionRecord::Condition::START,
|
||||
"AdvSceneSwitcher.condition.record.state.start"},
|
||||
{MacroConditionRecord::Condition::DURATION,
|
||||
"AdvSceneSwitcher.condition.record.state.duration"},
|
||||
};
|
||||
|
||||
static bool SetupRecordingTimer();
|
||||
static bool recordingTimerIsSetup = SetupRecordingTimer();
|
||||
static int currentRecordingDurationInSeconds = 0;
|
||||
|
||||
bool MacroConditionRecord::CheckCondition()
|
||||
{
|
||||
switch (_condition) {
|
||||
case Condition::STOP:
|
||||
return !obs_frontend_recording_active();
|
||||
case Condition::PAUSE:
|
||||
return obs_frontend_recording_paused();
|
||||
case Condition::START:
|
||||
return obs_frontend_recording_active();
|
||||
case Condition::DURATION:
|
||||
SetTempVarValue(
|
||||
"durationSeconds",
|
||||
std::to_string(currentRecordingDurationInSeconds));
|
||||
return currentRecordingDurationInSeconds > _duration.Seconds();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionRecord::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "state", static_cast<int>(_condition));
|
||||
_duration.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionRecord::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "state"));
|
||||
_duration.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroConditionRecord::SetCondition(Condition condition)
|
||||
{
|
||||
_condition = condition;
|
||||
SetupTempVars();
|
||||
}
|
||||
|
||||
void MacroConditionRecord::SetupTempVars()
|
||||
{
|
||||
MacroCondition::SetupTempVars();
|
||||
switch (_condition) {
|
||||
case Condition::DURATION:
|
||||
AddTempvar(
|
||||
"durationSeconds",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.recording.durationSeconds"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.recording.durationSeconds.description"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateStateSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : recordStates) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionRecordEdit::MacroConditionRecordEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionRecord> entryData)
|
||||
: QWidget(parent),
|
||||
_condition(new QComboBox(this)),
|
||||
_duration(new DurationSelection(this))
|
||||
{
|
||||
populateStateSelection(_condition);
|
||||
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"),
|
||||
layout,
|
||||
{{"{{condition}}", _condition},
|
||||
{"{{duration}}", _duration}});
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionRecordEdit::ConditionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->SetCondition(
|
||||
static_cast<MacroConditionRecord::Condition>(value));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionRecordEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_condition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->GetCondition()));
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionRecordEdit::DurationChanged(const Duration &duration)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = duration;
|
||||
}
|
||||
|
||||
void MacroConditionRecordEdit::SetWidgetVisibility()
|
||||
{
|
||||
_duration->setVisible(_entryData->GetCondition() ==
|
||||
MacroConditionRecord::Condition::DURATION);
|
||||
}
|
||||
|
||||
bool SetupRecordingTimer()
|
||||
{
|
||||
static std::atomic_bool recordingStopped = {true};
|
||||
static std::atomic_bool recordingPaused = {false};
|
||||
static auto handleRecordingEvents = [](enum obs_frontend_event event,
|
||||
void *) {
|
||||
switch (event) {
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
|
||||
recordingStopped = true;
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
||||
recordingStopped = false;
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
|
||||
recordingPaused = true;
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
|
||||
recordingPaused = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
};
|
||||
obs_frontend_add_event_callback(handleRecordingEvents, nullptr);
|
||||
std::thread thread([]() {
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (recordingStopped) {
|
||||
currentRecordingDurationInSeconds = 0;
|
||||
continue;
|
||||
}
|
||||
if (recordingPaused) {
|
||||
continue;
|
||||
}
|
||||
++currentRecordingDurationInSeconds;
|
||||
}
|
||||
});
|
||||
thread.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
69
plugins/base/macro-condition-recording.hpp
Normal file
69
plugins/base/macro-condition-recording.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionRecord : public MacroCondition {
|
||||
public:
|
||||
MacroConditionRecord(Macro *m) : MacroCondition(m) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionRecord>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
STOP,
|
||||
PAUSE,
|
||||
START,
|
||||
DURATION,
|
||||
};
|
||||
|
||||
void SetCondition(Condition);
|
||||
Condition GetCondition() const { return _condition; }
|
||||
Duration _duration;
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
|
||||
Condition _condition = Condition::STOP;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionRecordEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionRecordEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionRecord> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionRecordEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionRecord>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int value);
|
||||
void DurationChanged(const Duration &);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_condition;
|
||||
DurationSelection *_duration;
|
||||
std::shared_ptr<MacroConditionRecord> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
132
plugins/base/macro-condition-replay-buffer.cpp
Normal file
132
plugins/base/macro-condition-replay-buffer.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "macro-condition-replay-buffer.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionReplayBuffer::id = "replay_buffer";
|
||||
|
||||
bool MacroConditionReplayBuffer::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionReplayBuffer::id, {MacroConditionReplayBuffer::Create,
|
||||
MacroConditionReplayBufferEdit::Create,
|
||||
"AdvSceneSwitcher.condition.replay"});
|
||||
|
||||
const static std::map<MacroConditionReplayBuffer::Condition, std::string>
|
||||
conditions = {
|
||||
{MacroConditionReplayBuffer::Condition::STOP,
|
||||
"AdvSceneSwitcher.condition.replay.state.stopped"},
|
||||
{MacroConditionReplayBuffer::Condition::START,
|
||||
"AdvSceneSwitcher.condition.replay.state.started"},
|
||||
{MacroConditionReplayBuffer::Condition::SAVE,
|
||||
"AdvSceneSwitcher.condition.replay.state.saved"},
|
||||
};
|
||||
|
||||
static bool replayBufferSaved = false;
|
||||
|
||||
static std::chrono::high_resolution_clock::time_point replayBufferSaveTime{};
|
||||
static bool setupReplayBufferEventHandler();
|
||||
static bool replayBufferEventHandlerIsSetup = setupReplayBufferEventHandler();
|
||||
|
||||
static bool setupReplayBufferEventHandler()
|
||||
{
|
||||
static auto handleReplayBufferEvent = [](enum obs_frontend_event event,
|
||||
void *) {
|
||||
switch (event) {
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
|
||||
replayBufferSaveTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
};
|
||||
obs_frontend_add_event_callback(handleReplayBufferEvent, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionReplayBuffer::ReplayBufferWasSaved()
|
||||
{
|
||||
bool timersAreInitialized =
|
||||
_saveTime.time_since_epoch().count() != 0 &&
|
||||
replayBufferSaveTime.time_since_epoch().count() != 0;
|
||||
bool newSaveOccurred = timersAreInitialized &&
|
||||
_saveTime != replayBufferSaveTime;
|
||||
_saveTime = replayBufferSaveTime;
|
||||
return newSaveOccurred;
|
||||
}
|
||||
|
||||
bool MacroConditionReplayBuffer::CheckCondition()
|
||||
{
|
||||
switch (_state) {
|
||||
case Condition::STOP:
|
||||
return !obs_frontend_replay_buffer_active();
|
||||
case Condition::START:
|
||||
return obs_frontend_replay_buffer_active();
|
||||
case Condition::SAVE:
|
||||
return ReplayBufferWasSaved();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionReplayBuffer::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "state", static_cast<int>(_state));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionReplayBuffer::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_state = static_cast<Condition>(obs_data_get_int(obj, "state"));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void populateStateSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : conditions) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionReplayBufferEdit::MacroConditionReplayBufferEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionReplayBuffer> entryData)
|
||||
: QWidget(parent), _state(new QComboBox())
|
||||
{
|
||||
QWidget::connect(_state, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(StateChanged(int)));
|
||||
|
||||
populateStateSelection(_state);
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.replay.entry"),
|
||||
layout, {{"{{state}}", _state}});
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionReplayBufferEdit::StateChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_state =
|
||||
static_cast<MacroConditionReplayBuffer::Condition>(value);
|
||||
}
|
||||
|
||||
void MacroConditionReplayBufferEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_state->setCurrentIndex(static_cast<int>(_entryData->_state));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
65
plugins/base/macro-condition-replay-buffer.hpp
Normal file
65
plugins/base/macro-condition-replay-buffer.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionReplayBuffer : public MacroCondition {
|
||||
public:
|
||||
MacroConditionReplayBuffer(Macro *m) : MacroCondition(m) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionReplayBuffer>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
STOP,
|
||||
START,
|
||||
SAVE,
|
||||
};
|
||||
|
||||
Condition _state = Condition::STOP;
|
||||
|
||||
private:
|
||||
bool ReplayBufferWasSaved();
|
||||
|
||||
std::chrono::high_resolution_clock::time_point _saveTime = {};
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionReplayBufferEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionReplayBufferEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionReplayBuffer> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionReplayBufferEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionReplayBuffer>(
|
||||
cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void StateChanged(int value);
|
||||
|
||||
protected:
|
||||
QComboBox *_state;
|
||||
std::shared_ptr<MacroConditionReplayBuffer> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
196
plugins/base/macro-condition-run.cpp
Normal file
196
plugins/base/macro-condition-run.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "macro-condition-run.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
namespace advss {
|
||||
|
||||
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 (_error) {
|
||||
case ProcessConfig::ProcStartError::FAILED_TO_START:
|
||||
SetVariableValue("Failed to start process");
|
||||
break;
|
||||
case ProcessConfig::ProcStartError::TIMEOUT:
|
||||
SetVariableValue("Timeout while running process");
|
||||
break;
|
||||
case ProcessConfig::ProcStartError::CRASH:
|
||||
SetVariableValue("Timeout while running process");
|
||||
break;
|
||||
case ProcessConfig::ProcStartError::NONE:
|
||||
ret = _checkExitCode ? _exitCodeToCheck == _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()
|
||||
{
|
||||
auto result = _procConfig.StartProcessAndWait(_timeout.Milliseconds());
|
||||
|
||||
if (std::holds_alternative<ProcessConfig::ProcStartError>(result)) {
|
||||
_error = std::get<ProcessConfig::ProcStartError>(result);
|
||||
} else {
|
||||
_error = ProcessConfig::ProcStartError::NONE;
|
||||
_procExitCode = std::get<int>(result);
|
||||
}
|
||||
_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", _exitCodeToCheck);
|
||||
_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");
|
||||
_exitCodeToCheck = obs_data_get_int(obj, "exitCode");
|
||||
_timeout.Load(obj, "timeout");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionRun::GetShortDesc() const
|
||||
{
|
||||
return _procConfig.UnresolvedPath();
|
||||
}
|
||||
|
||||
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(const Duration &)),
|
||||
this, SLOT(TimeoutChanged(const Duration &)));
|
||||
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->_exitCodeToCheck);
|
||||
}
|
||||
|
||||
void MacroConditionRunEdit::TimeoutChanged(const Duration &dur)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_timeout = dur;
|
||||
}
|
||||
|
||||
void MacroConditionRunEdit::CheckExitCodeChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_checkExitCode = state;
|
||||
}
|
||||
|
||||
void MacroConditionRunEdit::ExitCodeChanged(int exitCode)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_exitCodeToCheck = exitCode;
|
||||
}
|
||||
|
||||
void MacroConditionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_procConfig = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
77
plugins/base/macro-condition-run.hpp
Normal file
77
plugins/base/macro-condition-run.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "process-config.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
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 _exitCodeToCheck = 0;
|
||||
Duration _timeout = Duration(0.1);
|
||||
|
||||
private:
|
||||
void RunProcess();
|
||||
std::thread _thread;
|
||||
std::atomic_bool _threadDone{true};
|
||||
ProcessConfig::ProcStartError _error =
|
||||
ProcessConfig::ProcStartError::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(const Duration &);
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
356
plugins/base/macro-condition-scene-order.cpp
Normal file
356
plugins/base/macro-condition-scene-order.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "macro-condition-scene-order.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionSceneOrder::id = "scene_order";
|
||||
|
||||
bool MacroConditionSceneOrder::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionSceneOrder::id,
|
||||
{MacroConditionSceneOrder::Create, MacroConditionSceneOrderEdit::Create,
|
||||
"AdvSceneSwitcher.condition.sceneOrder"});
|
||||
|
||||
const static std::map<MacroConditionSceneOrder::Condition, std::string>
|
||||
sceneOrderConditionTypes = {
|
||||
{MacroConditionSceneOrder::Condition::ABOVE,
|
||||
"AdvSceneSwitcher.condition.sceneOrder.type.above"},
|
||||
{MacroConditionSceneOrder::Condition::BELOW,
|
||||
"AdvSceneSwitcher.condition.sceneOrder.type.below"},
|
||||
{MacroConditionSceneOrder::Condition::POSITION,
|
||||
"AdvSceneSwitcher.condition.sceneOrder.type.position"},
|
||||
};
|
||||
|
||||
struct PosInfo2 {
|
||||
obs_scene_item *item;
|
||||
int pos = -1;
|
||||
int curPos = 0;
|
||||
};
|
||||
|
||||
static bool getSceneItemPositionHelper(obs_scene_t *, obs_sceneitem_t *item,
|
||||
void *ptr)
|
||||
{
|
||||
auto posInfo = reinterpret_cast<PosInfo2 *>(ptr);
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, getSceneItemPositionHelper, ptr);
|
||||
}
|
||||
if (posInfo->item == item) {
|
||||
posInfo->pos = posInfo->curPos;
|
||||
return false;
|
||||
}
|
||||
|
||||
posInfo->curPos += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static PosInfo2 getSceneItemPos(obs_scene_item *item, obs_scene *scene)
|
||||
{
|
||||
PosInfo2 pos{item};
|
||||
obs_scene_enum_items(scene, getSceneItemPositionHelper, &pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
static std::vector<int> getSceneItemPositions(std::vector<OBSSceneItem> &items,
|
||||
obs_scene *scene)
|
||||
{
|
||||
std::vector<int> positions;
|
||||
for (const auto &item : items) {
|
||||
auto pos = getSceneItemPos(item, scene);
|
||||
if (pos.pos != -1) {
|
||||
positions.emplace_back(pos.pos);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
static bool isAbove(std::vector<int> &pos1, std::vector<int> &pos2)
|
||||
{
|
||||
if (pos1.empty() || pos2.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (int i : pos1) {
|
||||
for (int j : pos2) {
|
||||
if (i <= j) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isBelow(std::vector<int> &pos1, std::vector<int> &pos2)
|
||||
{
|
||||
if (pos1.empty() || pos2.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (int i : pos1) {
|
||||
for (int j : pos2) {
|
||||
if (i >= j) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneOrder::CheckCondition()
|
||||
{
|
||||
auto items1 = _source.GetSceneItems(_scene);
|
||||
if (items1.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto items2 = _source2.GetSceneItems(_scene);
|
||||
auto s = obs_weak_source_get_source(_scene.GetScene(false));
|
||||
auto scene = obs_scene_from_source(s);
|
||||
auto positions1 = getSceneItemPositions(items1, scene);
|
||||
auto positions2 = getSceneItemPositions(items2, scene);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
switch (_condition) {
|
||||
case Condition::ABOVE:
|
||||
ret = isAbove(positions1, positions2);
|
||||
break;
|
||||
case Condition::BELOW:
|
||||
ret = isBelow(positions1, positions2);
|
||||
break;
|
||||
case Condition::POSITION:
|
||||
for (int p : positions1) {
|
||||
if (p == _position) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
obs_source_release(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneOrder::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
_source2.Save(obj, "sceneItemSelection2");
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_int(obj, "position", _position);
|
||||
_position.Save(obj, "position");
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneOrder::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "source")) {
|
||||
auto sourceName = obs_data_get_string(obj, "source");
|
||||
obs_data_set_string(obj, "sceneItem", sourceName);
|
||||
auto sourceName2 = obs_data_get_string(obj, "source2");
|
||||
obs_data_set_string(obj, "sceneItem2", sourceName2);
|
||||
}
|
||||
|
||||
MacroCondition::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "sceneItem2")) {
|
||||
_source2.Load(obj, "sceneItem2", "sceneItemTarget2",
|
||||
"sceneItemIdx2");
|
||||
} else {
|
||||
_source2.Load(obj, "sceneItemSelection2");
|
||||
}
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
_position = obs_data_get_int(obj, "position");
|
||||
} else {
|
||||
_position.Load(obj, "position");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionSceneOrder::GetShortDesc() const
|
||||
{
|
||||
if (_source.ToString().empty()) {
|
||||
return "";
|
||||
}
|
||||
std::string header = _scene.ToString() + " - " + _source.ToString();
|
||||
if (!_source2.ToString().empty() &&
|
||||
_condition != MacroConditionSceneOrder::Condition::POSITION) {
|
||||
header += " - " + _source2.ToString();
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : sceneOrderConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionSceneOrderEdit::MacroConditionSceneOrderEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionSceneOrder> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
|
||||
_conditions(new QComboBox()),
|
||||
_sources(new SceneItemSelectionWidget(parent)),
|
||||
_sources2(new SceneItemSelectionWidget(parent)),
|
||||
_position(new VariableSpinBox()),
|
||||
_posInfo(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneOrder.positionInfo")))
|
||||
{
|
||||
populateConditionSelection(_conditions);
|
||||
if (entryData.get()) {
|
||||
if (entryData->_condition ==
|
||||
MacroConditionSceneOrder::Condition::POSITION) {
|
||||
_sources->SetPlaceholderType(
|
||||
SceneItemSelectionWidget::Placeholder::ANY);
|
||||
} else {
|
||||
_sources->SetPlaceholderType(
|
||||
SceneItemSelectionWidget::Placeholder::ALL);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources2, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_sources2,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this,
|
||||
SLOT(Source2Changed(const SceneItemSelection &)));
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(
|
||||
_position,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(PositionChanged(const NumberVariable<int> &)));
|
||||
|
||||
auto entryLayout = new QHBoxLayout();
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
|
||||
{"{{sources2}}", _sources2}, {"{{conditions}}", _conditions},
|
||||
{"{{position}}", _position},
|
||||
};
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.sceneOrder.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addWidget(_posInfo);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::SourceChanged(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::Source2Changed(const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source2 = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionSceneOrder::Condition>(index);
|
||||
}
|
||||
SetWidgetVisibility(_entryData->_condition ==
|
||||
MacroConditionSceneOrder::Condition::POSITION);
|
||||
if (_entryData->_condition ==
|
||||
MacroConditionSceneOrder::Condition::POSITION) {
|
||||
_sources->SetPlaceholderType(
|
||||
SceneItemSelectionWidget::Placeholder::ANY);
|
||||
} else {
|
||||
_sources->SetPlaceholderType(
|
||||
SceneItemSelectionWidget::Placeholder::ALL);
|
||||
}
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::PositionChanged(
|
||||
const NumberVariable<int> &value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_position = value;
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::SetWidgetVisibility(bool showPos)
|
||||
{
|
||||
_sources2->setVisible(!showPos);
|
||||
_position->setVisible(showPos);
|
||||
_posInfo->setVisible(showPos);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionSceneOrderEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_sources2->SetSceneItem(_entryData->_source2);
|
||||
_position->SetValue(_entryData->_position);
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
SetWidgetVisibility(_entryData->_condition ==
|
||||
MacroConditionSceneOrder::Condition::POSITION);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
83
plugins/base/macro-condition-scene-order.hpp
Normal file
83
plugins/base/macro-condition-scene-order.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionSceneOrder : public MacroCondition {
|
||||
public:
|
||||
MacroConditionSceneOrder(Macro *m) : MacroCondition(m) {}
|
||||
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<MacroConditionSceneOrder>(m);
|
||||
}
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
SceneItemSelection _source2;
|
||||
NumberVariable<int> _position = 0;
|
||||
|
||||
enum class Condition {
|
||||
ABOVE,
|
||||
BELOW,
|
||||
POSITION,
|
||||
};
|
||||
Condition _condition = Condition::ABOVE;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionSceneOrderEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionSceneOrderEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionSceneOrder> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionSceneOrderEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionSceneOrder>(
|
||||
cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void Source2Changed(const SceneItemSelection &);
|
||||
void ConditionChanged(int cond);
|
||||
void PositionChanged(const NumberVariable<int> &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
QComboBox *_conditions;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
SceneItemSelectionWidget *_sources2;
|
||||
VariableSpinBox *_position;
|
||||
QLabel *_posInfo;
|
||||
|
||||
std::shared_ptr<MacroConditionSceneOrder> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility(bool showPos);
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
320
plugins/base/macro-condition-scene-transform.cpp
Normal file
320
plugins/base/macro-condition-scene-transform.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "macro-condition-scene-transform.hpp"
|
||||
#include "scene-item-transform-helpers.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "text-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroConditionSceneTransform::id = "scene_transform";
|
||||
|
||||
bool MacroConditionSceneTransform::_registered =
|
||||
MacroConditionFactory::Register(
|
||||
MacroConditionSceneTransform::id,
|
||||
{MacroConditionSceneTransform::Create,
|
||||
MacroConditionSceneTransformEdit::Create,
|
||||
"AdvSceneSwitcher.condition.sceneTransform"});
|
||||
|
||||
const static std::map<MacroConditionSceneTransform::CondType, std::string>
|
||||
conditionTypes = {
|
||||
{MacroConditionSceneTransform::CondType::MATCHES,
|
||||
"AdvSceneSwitcher.condition.sceneTransform.entry.type.matches"},
|
||||
{MacroConditionSceneTransform::CondType::CHANGED,
|
||||
"AdvSceneSwitcher.condition.sceneTransform.entry.type.changed"},
|
||||
};
|
||||
|
||||
static bool doesTransformOfAnySceneItemMatch(
|
||||
const std::vector<OBSSceneItem> &items, const std::string &jsonCompare,
|
||||
const RegexConfig ®ex, std::string &newVariable)
|
||||
{
|
||||
bool ret = false;
|
||||
std::string json;
|
||||
for (const auto &item : items) {
|
||||
json = GetSceneItemTransform(item);
|
||||
if (MatchJson(json, jsonCompare, regex)) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
newVariable = json;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
didTransformOfAnySceneItemChange(const std::vector<OBSSceneItem> &items,
|
||||
std::vector<std::string> &previousTransform,
|
||||
std::string &newVariable)
|
||||
{
|
||||
bool ret = false;
|
||||
std::string json;
|
||||
RegexConfig regex;
|
||||
auto numItems = items.size();
|
||||
if (previousTransform.size() < numItems) {
|
||||
ret = true;
|
||||
previousTransform.resize(numItems);
|
||||
}
|
||||
for (size_t idx = 0; idx < numItems; ++idx) {
|
||||
auto const &item = items[idx];
|
||||
json = GetSceneItemTransform(item);
|
||||
if (!MatchJson(json, previousTransform[idx], regex)) {
|
||||
ret = true;
|
||||
previousTransform[idx] = json;
|
||||
}
|
||||
}
|
||||
newVariable = json;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneTransform::CheckCondition()
|
||||
{
|
||||
auto items = _source.GetSceneItems(_scene);
|
||||
if (items.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string newVariable = "";
|
||||
bool ret = false;
|
||||
switch (_type) {
|
||||
case CondType::MATCHES:
|
||||
ret = doesTransformOfAnySceneItemMatch(items, _settings, _regex,
|
||||
newVariable);
|
||||
break;
|
||||
case CondType::CHANGED:
|
||||
ret = didTransformOfAnySceneItemChange(
|
||||
items, _previousTransform, newVariable);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
SetVariableValue(newVariable);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneTransform::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
_settings.Save(obj, "settings");
|
||||
_regex.Save(obj);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionSceneTransform::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "source")) {
|
||||
auto sourceName = obs_data_get_string(obj, "source");
|
||||
obs_data_set_string(obj, "sceneItem", sourceName);
|
||||
}
|
||||
|
||||
MacroCondition::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_settings.Load(obj, "settings");
|
||||
_regex.Load(obj);
|
||||
_type = static_cast<CondType>(obs_data_get_int(obj, "type"));
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "regex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "regex"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionSceneTransform::GetShortDesc() const
|
||||
{
|
||||
if (_source.ToString().empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return _scene.ToString() + " - " + _source.ToString();
|
||||
}
|
||||
|
||||
static inline void populateTypesSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : conditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionSceneTransform> entryData)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
|
||||
_sources(new SceneItemSelectionWidget(
|
||||
parent, true, SceneItemSelectionWidget::Placeholder::ANY)),
|
||||
_types(new QComboBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.getTransform"))),
|
||||
_settings(new VariableTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent))
|
||||
{
|
||||
populateTypesSelection(_types);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
_sources, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources,
|
||||
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
|
||||
this, SLOT(SourceChanged(const SceneItemSelection &)));
|
||||
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sources}}", _sources},
|
||||
{"{{types}}", _types},
|
||||
{"{{settings}}", _settings},
|
||||
{"{{getSettings}}", _getSettings},
|
||||
{"{{regex}}", _regex},
|
||||
};
|
||||
|
||||
QHBoxLayout *line1Layout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.entry.line1"),
|
||||
line1Layout, widgetPlaceholders);
|
||||
QHBoxLayout *line2Layout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.entry.line2"),
|
||||
line2Layout, widgetPlaceholders, false);
|
||||
QHBoxLayout *line3Layout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.entry.line3"),
|
||||
line3Layout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(line1Layout);
|
||||
mainLayout->addLayout(line2Layout);
|
||||
mainLayout->addLayout(line3Layout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_settings->setPlainText(_entryData->_settings);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = s;
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::SourceChanged(
|
||||
const SceneItemSelection &item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_source = item;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::TypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_type =
|
||||
static_cast<MacroConditionSceneTransform::CondType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::GetSettingsClicked()
|
||||
{
|
||||
|
||||
if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto settings = FormatJsonString(GetSceneItemTransform(items[0]));
|
||||
if (_entryData->_regex.Enabled()) {
|
||||
settings = EscapeForRegex(settings);
|
||||
}
|
||||
_settings->setPlainText(settings);
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::SettingsChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::SetWidgetVisibility()
|
||||
{
|
||||
const bool showSettingsAndRegex =
|
||||
_entryData->_type ==
|
||||
MacroConditionSceneTransform::CondType::MATCHES;
|
||||
_settings->setVisible(showSettingsAndRegex);
|
||||
_getSettings->setVisible(showSettingsAndRegex);
|
||||
_regex->setVisible(showSettingsAndRegex);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user