mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 09:54:54 -05:00
Adds websocket vendor requests to start and stop the plugin. Adds vendor events to indicate when the plugin is started or stopped. These are ntended to be used for the StreamDeck plugin support.
794 lines
19 KiB
C++
794 lines
19 KiB
C++
#include "advanced-scene-switcher.hpp"
|
|
#include "backup.hpp"
|
|
#include "curl-helper.hpp"
|
|
#include "log-helper.hpp"
|
|
#include "macro-helpers.hpp"
|
|
#include "obs-module-helper.hpp"
|
|
#include "path-helpers.hpp"
|
|
#include "platform-funcs.hpp"
|
|
#include "scene-switch-helpers.hpp"
|
|
#include "source-helpers.hpp"
|
|
#include "status-control.hpp"
|
|
#include "switcher-data.hpp"
|
|
#include "ui-helpers.hpp"
|
|
#include "tab-helpers.hpp"
|
|
#include "utility.hpp"
|
|
#include "version.h"
|
|
#include "websocket-api.hpp"
|
|
|
|
#include <obs-frontend-api.h>
|
|
#include <QAction>
|
|
#include <QDirIterator>
|
|
#include <QMainWindow>
|
|
#include <QTextStream>
|
|
#include <regex>
|
|
|
|
namespace advss {
|
|
|
|
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
|
|
|
/******************************************************************************
|
|
* Create the Advanced Scene Switcher settings window
|
|
******************************************************************************/
|
|
AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
|
|
: QDialog(parent),
|
|
ui(new Ui_AdvSceneSwitcher)
|
|
{
|
|
switcher->settingsWindowOpened = true;
|
|
ui->setupUi(this);
|
|
std::lock_guard<std::mutex> lock(switcher->m);
|
|
switcher->Prune();
|
|
LoadUI();
|
|
}
|
|
|
|
AdvSceneSwitcher::~AdvSceneSwitcher()
|
|
{
|
|
if (switcher) {
|
|
switcher->settingsWindowOpened = false;
|
|
SaveLastOpenedTab(ui->tabWidget);
|
|
}
|
|
}
|
|
|
|
static bool translationAvailable()
|
|
{
|
|
return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"),
|
|
"AdvSceneSwitcher.pluginName");
|
|
}
|
|
|
|
static void DisplayMissingDependencyWarning()
|
|
{
|
|
if (!switcher->warnPluginLoadFailure ||
|
|
switcher->loadFailureLibs.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QString failedLibsString = "<ul>";
|
|
for (const auto &lib : switcher->loadFailureLibs) {
|
|
failedLibsString += "<li>" + lib + "</li>";
|
|
}
|
|
failedLibsString += "</ul>";
|
|
QString warning(obs_module_text(
|
|
"AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage"));
|
|
DisplayMessage(warning.arg(failedLibsString));
|
|
|
|
// Only display the warning once per plugin load
|
|
switcher->loadFailureLibs.clear();
|
|
}
|
|
|
|
static void DisplayMissingDataDirWarning()
|
|
{
|
|
if (translationAvailable()) {
|
|
return;
|
|
}
|
|
|
|
QString msg = "Failed to find plug-in's 'data' directory.\n"
|
|
"Please check installation instructions!\n\n"
|
|
"Data most likely expected at:\n\n";
|
|
#ifdef _WIN32
|
|
|
|
msg += QDir::currentPath();
|
|
msg += "/";
|
|
#endif
|
|
msg += obs_get_module_data_path(obs_current_module());
|
|
DisplayMessage(msg);
|
|
}
|
|
|
|
bool CanCreateDefaultAction();
|
|
bool CanCreateDefaultCondition();
|
|
|
|
static void DisplayCorruptedInstallWarning()
|
|
{
|
|
if (CanCreateDefaultAction() && CanCreateDefaultCondition()) {
|
|
return;
|
|
}
|
|
|
|
DisplayMessage(obs_module_text(
|
|
"AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage"));
|
|
}
|
|
|
|
void AdvSceneSwitcher::LoadUI()
|
|
{
|
|
DisplayMissingDataDirWarning();
|
|
DisplayMissingDependencyWarning();
|
|
DisplayCorruptedInstallWarning();
|
|
|
|
SetupGeneralTab();
|
|
SetupTitleTab();
|
|
SetupExecutableTab();
|
|
SetupRegionTab();
|
|
SetupPauseTab();
|
|
SetupSequenceTab();
|
|
SetupTransitionsTab();
|
|
SetupIdleTab();
|
|
SetupRandomTab();
|
|
SetupMediaTab();
|
|
SetupFileTab();
|
|
SetupTimeTab();
|
|
SetupAudioTab();
|
|
SetupVideoTab();
|
|
SetupSceneGroupTab();
|
|
SetupMacroTab();
|
|
SetupOtherTabs(ui->tabWidget);
|
|
|
|
SetDeprecationWarnings();
|
|
SetTabOrder(ui->tabWidget);
|
|
SetCurrentTab(ui->tabWidget);
|
|
RestoreWindowGeo();
|
|
CheckFirstTimeSetup();
|
|
|
|
loading = false;
|
|
}
|
|
|
|
bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
auto eventType = event->type();
|
|
if (obj == ui->macroElseActions && eventType == QEvent::Resize) {
|
|
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
|
|
|
|
if (resizeEvent->size().height() == 0) {
|
|
SetElseActionsStateToHidden();
|
|
return QDialog::eventFilter(obj, event);
|
|
}
|
|
|
|
SetElseActionsStateToVisible();
|
|
} else if (eventType == QEvent::KeyPress) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
|
auto pressedKey = keyEvent->key();
|
|
|
|
if (obj == ui->macros && ui->macros->isVisible()) {
|
|
if (pressedKey == Qt::Key_F2) {
|
|
RenameSelectedMacro();
|
|
} else if (pressedKey == Qt::Key_Delete) {
|
|
RemoveSelectedMacros();
|
|
}
|
|
}
|
|
}
|
|
|
|
return QDialog::eventFilter(obj, event);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Saving and loading
|
|
******************************************************************************/
|
|
|
|
static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
|
|
{
|
|
if (!switcher) {
|
|
return;
|
|
}
|
|
|
|
if (saving) {
|
|
std::lock_guard<std::mutex> lock(switcher->m);
|
|
switcher->Prune();
|
|
OBSDataAutoRelease data = obs_data_create();
|
|
switcher->SaveSettings(data);
|
|
obs_data_set_obj(save_data, "advanced-scene-switcher", data);
|
|
} else {
|
|
// Stop the scene switcher at least once to
|
|
// avoid scene duplication issues with scene collection changes
|
|
switcher->Stop();
|
|
|
|
OBSDataAutoRelease data =
|
|
obs_data_get_obj(save_data, "advanced-scene-switcher");
|
|
if (!data) {
|
|
data = obs_data_create();
|
|
}
|
|
|
|
switcher->m.lock();
|
|
if (switcher->VersionChanged(data, g_GIT_SHA1)) {
|
|
auto json = obs_data_get_json(data);
|
|
static QString jsonQString = json ? json : "";
|
|
std::thread t([]() {
|
|
obs_queue_task(
|
|
OBS_TASK_UI,
|
|
[](void *) {
|
|
AskForBackup(jsonQString);
|
|
},
|
|
nullptr, false);
|
|
});
|
|
t.detach();
|
|
}
|
|
|
|
switcher->LoadSettings(data);
|
|
switcher->m.unlock();
|
|
|
|
if (!switcher->stop) {
|
|
switcher->Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Main switcher thread
|
|
******************************************************************************/
|
|
void SwitcherData::Thread()
|
|
{
|
|
blog(LOG_INFO, "started");
|
|
int sleep = 0;
|
|
int linger = 0;
|
|
std::chrono::milliseconds duration;
|
|
auto startTime = std::chrono::high_resolution_clock::now();
|
|
auto endTime = std::chrono::high_resolution_clock::now();
|
|
switcher->firstIntervalAfterStop = true;
|
|
|
|
while (true) {
|
|
std::unique_lock<std::mutex> lock(m);
|
|
mainLoopLock = &lock;
|
|
|
|
bool match = false;
|
|
OBSWeakSource scene;
|
|
OBSWeakSource transition;
|
|
// The previous scene might have changed during the linger duration,
|
|
// if a longer transition is used than the configured check interval
|
|
bool setPrevSceneAfterLinger = false;
|
|
bool macroMatch = false;
|
|
endTime = std::chrono::high_resolution_clock::now();
|
|
auto runTime =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
endTime - startTime);
|
|
|
|
if (sleep) {
|
|
duration = std::chrono::milliseconds(sleep);
|
|
} else {
|
|
duration = std::chrono::milliseconds(interval) +
|
|
std::chrono::milliseconds(linger) - runTime;
|
|
if (duration.count() < 1) {
|
|
vblog(LOG_INFO,
|
|
"detected busy loop - refusing to sleep less than 1ms");
|
|
duration = std::chrono::milliseconds(10);
|
|
}
|
|
}
|
|
|
|
vblog(LOG_INFO, "try to sleep for %ld",
|
|
(long int)duration.count());
|
|
SetWaitScene();
|
|
cv.wait_for(lock, duration);
|
|
|
|
startTime = std::chrono::high_resolution_clock::now();
|
|
sleep = 0;
|
|
linger = 0;
|
|
|
|
Prune();
|
|
if (stop) {
|
|
break;
|
|
}
|
|
if (checkPause()) {
|
|
continue;
|
|
}
|
|
SetPreconditions();
|
|
match = CheckForMatch(scene, transition, linger,
|
|
setPrevSceneAfterLinger, macroMatch);
|
|
if (stop) {
|
|
break;
|
|
}
|
|
CheckNoMatchSwitch(match, scene, transition, sleep);
|
|
checkSwitchCooldown(match);
|
|
|
|
if (linger) {
|
|
duration = std::chrono::milliseconds(linger);
|
|
vblog(LOG_INFO, "sleep for %ld before switching scene",
|
|
(long int)duration.count());
|
|
|
|
SetWaitScene();
|
|
cv.wait_for(lock, duration);
|
|
|
|
if (stop) {
|
|
break;
|
|
}
|
|
|
|
if (SceneChangedDuringWait()) {
|
|
vblog(LOG_INFO,
|
|
"scene was changed manually - ignoring match");
|
|
|
|
match = false;
|
|
linger = 0;
|
|
} else if (setPrevSceneAfterLinger) {
|
|
scene = previousScene;
|
|
}
|
|
}
|
|
|
|
ResetForNextInterval();
|
|
|
|
if (match) {
|
|
if (macroMatch) {
|
|
RunMacros();
|
|
} else {
|
|
SwitchScene({scene, transition, 0});
|
|
}
|
|
}
|
|
|
|
writeSceneInfoToFile();
|
|
switcher->firstInterval = false;
|
|
switcher->firstIntervalAfterStop = false;
|
|
}
|
|
|
|
mainLoopLock = nullptr;
|
|
blog(LOG_INFO, "stopped");
|
|
}
|
|
|
|
void SwitcherData::SetPreconditions()
|
|
{
|
|
// Window title
|
|
lastTitle = currentTitle;
|
|
std::string title;
|
|
GetCurrentWindowTitle(title);
|
|
for (auto &window : ignoreWindowsSwitches) {
|
|
bool equals = (title == window);
|
|
bool matches = false;
|
|
if (!equals) {
|
|
try {
|
|
std::regex expr(window);
|
|
matches = std::regex_match(title, expr);
|
|
|
|
} catch (const std::regex_error &) {
|
|
}
|
|
}
|
|
if (equals || matches) {
|
|
title = lastTitle;
|
|
break;
|
|
}
|
|
}
|
|
currentTitle = title;
|
|
|
|
// Process name
|
|
GetForegroundProcessName(currentForegroundProcess);
|
|
|
|
// Macro
|
|
InvalidateMacroTempVarValues();
|
|
}
|
|
|
|
void SwitcherData::ResetForNextInterval()
|
|
{
|
|
// Plugin reset functions
|
|
for (const auto &func : resetIntervalSteps) {
|
|
func();
|
|
}
|
|
}
|
|
|
|
bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
|
|
OBSWeakSource &transition, int &linger,
|
|
bool &setPrevSceneAfterLinger,
|
|
bool ¯oMatch)
|
|
{
|
|
bool match = false;
|
|
|
|
if (uninterruptibleSceneSequenceActive) {
|
|
match = checkSceneSequence(scene, transition, linger,
|
|
setPrevSceneAfterLinger);
|
|
if (match) {
|
|
return match;
|
|
}
|
|
}
|
|
|
|
for (int switchFuncName : functionNamesByPriority) {
|
|
switch (switchFuncName) {
|
|
case read_file_func:
|
|
match = checkSwitchInfoFromFile(scene, transition) ||
|
|
checkFileContent(scene, transition);
|
|
break;
|
|
case idle_func:
|
|
match = checkIdleSwitch(scene, transition);
|
|
break;
|
|
case exe_func:
|
|
match = checkExeSwitch(scene, transition);
|
|
break;
|
|
case screen_region_func:
|
|
match = checkScreenRegionSwitch(scene, transition);
|
|
break;
|
|
case window_title_func:
|
|
match = checkWindowTitleSwitch(scene, transition);
|
|
break;
|
|
case round_trip_func:
|
|
match = checkSceneSequence(scene, transition, linger,
|
|
setPrevSceneAfterLinger);
|
|
break;
|
|
case media_func:
|
|
match = checkMediaSwitch(scene, transition);
|
|
break;
|
|
case time_func:
|
|
match = checkTimeSwitch(scene, transition);
|
|
break;
|
|
case audio_func:
|
|
match = checkAudioSwitch(scene, transition);
|
|
break;
|
|
case video_func:
|
|
match = checkVideoSwitch(scene, transition);
|
|
break;
|
|
case macro_func:
|
|
if (CheckMacros()) {
|
|
match = true;
|
|
macroMatch = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (stop) {
|
|
return false;
|
|
}
|
|
if (match) {
|
|
break;
|
|
}
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static void ResetMacros()
|
|
{
|
|
for (auto &m : GetMacros()) {
|
|
ResetMacroRunCount(m.get());
|
|
ResetMacroConditionTimers(m.get());
|
|
}
|
|
}
|
|
|
|
void AutoStartActionQueues();
|
|
|
|
void SwitcherData::Start()
|
|
{
|
|
if (!(th && th->isRunning())) {
|
|
ResetForNextInterval();
|
|
ResetMacros();
|
|
AutoStartActionQueues();
|
|
|
|
stop = false;
|
|
th = new SwitcherThread();
|
|
th->start((QThread::Priority)threadPriority);
|
|
|
|
// Will be overwritten quickly but might be useful
|
|
writeToStatusFile("Advanced Scene Switcher running");
|
|
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
|
|
nullptr);
|
|
}
|
|
|
|
if (showSystemTrayNotifications) {
|
|
DisplayTrayMessage(
|
|
obs_module_text("AdvSceneSwitcher.pluginName"),
|
|
obs_module_text("AdvSceneSwitcher.running"));
|
|
}
|
|
}
|
|
|
|
void CloseAllInputDialogs();
|
|
void StopAndClearAllActionQueues();
|
|
|
|
void SwitcherData::Stop()
|
|
{
|
|
if (th && th->isRunning()) {
|
|
stop = true;
|
|
cv.notify_all();
|
|
SetMacroAbortWait(true);
|
|
GetMacroWaitCV().notify_all();
|
|
GetMacroTransitionCV().notify_all();
|
|
StopAllMacros();
|
|
StopAndClearAllActionQueues();
|
|
CloseAllInputDialogs();
|
|
th->wait();
|
|
delete th;
|
|
th = nullptr;
|
|
writeToStatusFile("Advanced Scene Switcher stopped");
|
|
if (!obsIsShuttingDown) {
|
|
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
if (showSystemTrayNotifications) {
|
|
DisplayTrayMessage(
|
|
obs_module_text("AdvSceneSwitcher.pluginName"),
|
|
obs_module_text("AdvSceneSwitcher.stopped"));
|
|
}
|
|
}
|
|
|
|
void SwitcherData::SetWaitScene()
|
|
{
|
|
waitScene = obs_frontend_get_current_scene();
|
|
obs_source_release(waitScene);
|
|
}
|
|
|
|
bool SwitcherData::SceneChangedDuringWait()
|
|
{
|
|
OBSSourceAutoRelease currentSource = obs_frontend_get_current_scene();
|
|
if (!currentSource) {
|
|
return true;
|
|
}
|
|
return (waitScene && currentSource != waitScene);
|
|
}
|
|
|
|
// Relies on the fact that switcher->currentScene will only be updated on event
|
|
// OBS_FRONTEND_EVENT_SCENE_CHANGED but obs_frontend_get_current_scene() will
|
|
// already return the scene to be transitioned to.
|
|
bool SwitcherData::AnySceneTransitionStarted()
|
|
{
|
|
OBSSourceAutoRelease currentSceneSrouce =
|
|
obs_frontend_get_current_scene();
|
|
OBSWeakSourceAutoRelease currentScene =
|
|
obs_source_get_weak_source(currentSceneSrouce);
|
|
bool ret = switcher->currentScene != currentScene;
|
|
return ret;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* OBS module setup
|
|
******************************************************************************/
|
|
extern "C" EXPORT void FreeSceneSwitcher()
|
|
{
|
|
PlatformCleanup();
|
|
RunPluginCleanupSteps();
|
|
|
|
delete switcher;
|
|
switcher = nullptr;
|
|
}
|
|
|
|
static void handleSceneChange()
|
|
{
|
|
// Stop waiting if scene was changed
|
|
if (switcher->SceneChangedDuringWait()) {
|
|
switcher->cv.notify_one();
|
|
}
|
|
|
|
// Set current and previous scene
|
|
OBSSourceAutoRelease source = obs_frontend_get_current_scene();
|
|
OBSWeakSourceAutoRelease ws = obs_source_get_weak_source(source);
|
|
|
|
if (ws && ws != switcher->currentScene) {
|
|
switcher->previousScene = switcher->currentScene;
|
|
switcher->currentScene = ws;
|
|
vblog(LOG_INFO, "current scene: %s",
|
|
GetWeakSourceName(switcher->currentScene).c_str());
|
|
vblog(LOG_INFO, "previous scene: %s",
|
|
GetWeakSourceName(switcher->previousScene).c_str());
|
|
}
|
|
|
|
switcher->checkDefaultSceneTransitions();
|
|
}
|
|
|
|
static void setLiveTime()
|
|
{
|
|
switcher->liveTime = QDateTime::currentDateTime();
|
|
}
|
|
|
|
static void resetLiveTime()
|
|
{
|
|
switcher->liveTime = QDateTime();
|
|
}
|
|
|
|
static void checkAutoStartRecording()
|
|
{
|
|
if (switcher->autoStartEvent == SwitcherData::AutoStart::RECORDING ||
|
|
switcher->autoStartEvent ==
|
|
SwitcherData::AutoStart::RECORINDG_OR_STREAMING)
|
|
switcher->Start();
|
|
}
|
|
|
|
static void checkAutoStartStreaming()
|
|
{
|
|
if (switcher->autoStartEvent == SwitcherData::AutoStart::STREAMING ||
|
|
switcher->autoStartEvent ==
|
|
SwitcherData::AutoStart::RECORINDG_OR_STREAMING)
|
|
switcher->Start();
|
|
}
|
|
|
|
static void handleTransitionEnd()
|
|
{
|
|
GetMacroTransitionCV().notify_all();
|
|
}
|
|
|
|
static void handleShutdown()
|
|
{
|
|
if (!switcher) {
|
|
return;
|
|
}
|
|
switcher->obsIsShuttingDown = true;
|
|
if (ShutdownCheckIsNecessary()) {
|
|
switcher->Stop();
|
|
CheckMacros();
|
|
RunMacros();
|
|
|
|
// Unfortunately this will not work as OBS will now allow saving
|
|
// the scene collection data at this point, So any OBS specific
|
|
// changes done during shutdown will be lost
|
|
//
|
|
// TODO: Look for a way to possibly resolve this
|
|
obs_frontend_save();
|
|
}
|
|
|
|
BackupSettingsOfCurrentVersion();
|
|
}
|
|
|
|
static void handleSceneCollectionChanging()
|
|
{
|
|
if (switcher->settingsWindowOpened) {
|
|
AdvSceneSwitcher::window->close();
|
|
}
|
|
if (!switcher->stop) {
|
|
switcher->sceneColletionStop = true;
|
|
switcher->Stop();
|
|
}
|
|
}
|
|
|
|
static void handleSceneCollectionCleanup()
|
|
{
|
|
// Reset the plugin to default settings to avoid settings being carried
|
|
// over to newly created scene collections.
|
|
|
|
// Skip this step for the initial OBS startup.
|
|
// OBSBasic::LoadData() will call ClearSceneData() which will trigger
|
|
// this function to be called.
|
|
// This would result in the plugin being started with default settings
|
|
// before the "real" settings are loaded.
|
|
static bool isOBSStartup = true;
|
|
if (isOBSStartup) {
|
|
isOBSStartup = false;
|
|
return;
|
|
}
|
|
|
|
SaveSceneSwitcher(nullptr, false, nullptr);
|
|
}
|
|
|
|
// Note to future self:
|
|
// be careful using switcher->m here as there is potential for deadlocks when using
|
|
// frontend functions such as obs_frontend_set_current_scene()
|
|
static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
|
{
|
|
if (!switcher) {
|
|
return;
|
|
}
|
|
|
|
switch (event) {
|
|
case OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN:
|
|
// Note: We are intentionally not listening for
|
|
// OBS_FRONTEND_EVENT_EXIT here as at that point all scene
|
|
// collection data will already have been cleared and thus all
|
|
// macros will have already been cleared
|
|
handleShutdown();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
|
handleSceneChange();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
|
setLiveTime();
|
|
checkAutoStartRecording();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
|
setLiveTime();
|
|
checkAutoStartStreaming();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
|
|
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
|
resetLiveTime();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
|
|
handleTransitionEnd();
|
|
break;
|
|
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 2, 0)
|
|
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
|
|
handleSceneCollectionChanging();
|
|
break;
|
|
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP:
|
|
handleSceneCollectionCleanup();
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void LoadPlugins()
|
|
{
|
|
QFileInfo libPath(
|
|
QString(obs_get_module_binary_path(obs_current_module())));
|
|
QString pluginDir(libPath.absolutePath() + "/" ADVSS_PLUGIN_FOLDER);
|
|
#ifdef _WIN32
|
|
QString libPattern = "*.dll";
|
|
SetDllDirectory(pluginDir.toStdWString().c_str());
|
|
#else
|
|
QString libPattern = "*.so";
|
|
#endif
|
|
QDirIterator it(pluginDir, QStringList() << libPattern, QDir::Files);
|
|
while (it.hasNext()) {
|
|
auto file = it.next();
|
|
blog(LOG_INFO, "attempting to load \"%s\"",
|
|
file.toStdString().c_str());
|
|
auto lib = new QLibrary(file, nullptr);
|
|
if (lib->load()) {
|
|
blog(LOG_INFO, "successfully loaded \"%s\"",
|
|
file.toStdString().c_str());
|
|
} else {
|
|
blog(LOG_WARNING, "failed to load \"%s\": %s",
|
|
file.toStdString().c_str(),
|
|
lib->errorString().toStdString().c_str());
|
|
switcher->loadFailureLibs << file;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpenSettingsWindow()
|
|
{
|
|
if (switcher->settingsWindowOpened) {
|
|
AdvSceneSwitcher::window->show();
|
|
AdvSceneSwitcher::window->raise();
|
|
AdvSceneSwitcher::window->activateWindow();
|
|
} else {
|
|
AdvSceneSwitcher::window =
|
|
new AdvSceneSwitcher(static_cast<QMainWindow *>(
|
|
obs_frontend_get_main_window()));
|
|
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
|
AdvSceneSwitcher::window->show();
|
|
}
|
|
}
|
|
|
|
void AdvSceneSwitcher::HighligthMacroSettingsButton(bool enable)
|
|
{
|
|
static QObject *highlight = nullptr;
|
|
if ((highlight && enable) || (!highlight && !enable)) {
|
|
return;
|
|
}
|
|
|
|
if (highlight && !enable) {
|
|
highlight->deleteLater();
|
|
highlight = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (!HighlightUIElementsEnabled()) {
|
|
return;
|
|
}
|
|
|
|
highlight = HighlightWidget(ui->macroSettings, Qt::green);
|
|
}
|
|
|
|
void HighligthMacroSettingsButton(bool enable)
|
|
{
|
|
auto window = GetSettingsWindow();
|
|
if (!window) {
|
|
return;
|
|
}
|
|
static_cast<AdvSceneSwitcher *>(window)->HighligthMacroSettingsButton(
|
|
enable);
|
|
}
|
|
|
|
void SetupActionQueues();
|
|
|
|
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
|
translateFunc translate)
|
|
{
|
|
blog(LOG_INFO, "version: %s", g_GIT_TAG);
|
|
blog(LOG_INFO, "version: %s", g_GIT_SHA1);
|
|
|
|
switcher = new SwitcherData(module, translate);
|
|
|
|
PlatformInit();
|
|
LoadPlugins();
|
|
SetupDock();
|
|
SetupActionQueues();
|
|
|
|
RunPluginInitSteps();
|
|
|
|
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
|
|
obs_frontend_add_event_callback(OBSEvent, switcher);
|
|
|
|
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
|
obs_module_text("AdvSceneSwitcher.pluginName"));
|
|
action->connect(action, &QAction::triggered, OpenSettingsWindow);
|
|
}
|
|
|
|
} // namespace advss
|