diff --git a/CMakeLists.txt b/CMakeLists.txt index 74d433cf..7c624b58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ if(BUILD_OUT_OF_TREE) src/general.cpp src/pause-switch.cpp src/random.cpp + src/time-switch.cpp ) set(advanced-scene-switcher_UI @@ -182,6 +183,7 @@ else () src/general.cpp src/pause-switch.cpp src/random.cpp + src/time-switch.cpp ) set(advanced-scene-switcher_UI diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui index 1564578c..12d12b74 100644 --- a/forms/advanced-scene-switcher.ui +++ b/forms/advanced-scene-switcher.ui @@ -2416,7 +2416,7 @@ ms - 00 + 0 1000000 @@ -2543,6 +2543,140 @@ + + + Time + + + + + + + + At + + + + + + + HH:mm:ss + + + + + + + switch to + + + + + + + + 100 + 0 + + + + + + + + using the + + + + + + + + + + transition + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + true + + + + + + + 4 + + + + + + 22 + 22 + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index a4356ab0..70e5ef49 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -57,6 +57,7 @@ SceneSwitcher::SceneSwitcher(QWidget *parent) ui->randomScenes->addItem(name); ui->fileScenes->addItem(name); ui->mediaScenes->addItem(name); + ui->timeScenes->addItem(name); temp++; } @@ -91,6 +92,7 @@ SceneSwitcher::SceneSwitcher(QWidget *parent) ui->sceneRoundTripScenes2->addItem(PREVIOUS_SCENE_NAME); ui->idleScenes->addItem(PREVIOUS_SCENE_NAME); ui->mediaScenes->addItem(PREVIOUS_SCENE_NAME); + ui->timeScenes->addItem(PREVIOUS_SCENE_NAME); obs_frontend_source_list *transitions = new obs_frontend_source_list(); obs_frontend_get_transitions(transitions); @@ -108,6 +110,7 @@ SceneSwitcher::SceneSwitcher(QWidget *parent) ui->randomTransitions->addItem(name); ui->fileTransitions->addItem(name); ui->mediaTransitions->addItem(name); + ui->timeTransitions->addItem(name); } obs_frontend_source_list_free(transitions); @@ -305,6 +308,19 @@ SceneSwitcher::SceneSwitcher(QWidget *parent) item->setData(Qt::UserRole, listText); } + for (auto &s : switcher->timeSwitches) { + string sceneName = (s.usePreviousScene) + ? PREVIOUS_SCENE_NAME + : GetWeakSourceName(s.scene); + string transitionName = GetWeakSourceName(s.transition); + QString listText = MakeTimeSwitchName( + sceneName.c_str(), transitionName.c_str(), s.time); + + QListWidgetItem *item = + new QListWidgetItem(listText, ui->timeSwitches); + item->setData(Qt::UserRole, listText); + } + ui->idleCheckBox->setChecked(switcher->idleData.idleEnable); ui->idleScenes->setCurrentText( switcher->idleData.usePreviousScene @@ -375,6 +391,9 @@ SceneSwitcher::SceneSwitcher(QWidget *parent) case MEDIA_FUNC: s = "Media"; break; + case TIME_FUNC: + s = "Time"; + break; } QString text(s.c_str()); QListWidgetItem *item = @@ -407,6 +426,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_array_t *randomArray = obs_data_array_create(); obs_data_array_t *fileArray = obs_data_array_create(); obs_data_array_t *mediaArray = obs_data_array_create(); + obs_data_array_t *timeArray = obs_data_array_create(); switcher->Prune(); @@ -755,6 +775,35 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_release(array_obj); } + for (TimeSwitch &s : switcher->timeSwitches) { + obs_data_t *array_obj = obs_data_create(); + + obs_source_t *sceneSource = + obs_weak_source_get_source(s.scene); + obs_source_t *transition = + obs_weak_source_get_source(s.transition); + if ((s.usePreviousScene || sceneSource) && transition) { + const char *sceneName = + obs_source_get_name(sceneSource); + const char *transitionName = + obs_source_get_name(transition); + obs_data_set_string( + array_obj, "scene", + s.usePreviousScene ? PREVIOUS_SCENE_NAME + : sceneName); + obs_data_set_string(array_obj, "transition", + transitionName); + obs_data_set_string( + array_obj, "time", + s.time.toString().toStdString().c_str()); + obs_data_array_push_back(timeArray, array_obj); + } + obs_source_release(sceneSource); + obs_source_release(transition); + + obs_data_release(array_obj); + } + string nonMatchingSceneName = GetWeakSourceName(switcher->nonMatchingScene); @@ -781,6 +830,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_set_array(obj, "randomSwitches", randomArray); obs_data_set_array(obj, "fileSwitches", fileArray); obs_data_set_array(obj, "mediaSwitches", mediaArray); + obs_data_set_array(obj, "timeSwitches", timeArray); string autoStopSceneName = GetWeakSourceName(switcher->autoStopScene); @@ -826,6 +876,8 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) switcher->functionNamesByPriority[5]); obs_data_set_int(obj, "priority6", switcher->functionNamesByPriority[6]); + obs_data_set_int(obj, "priority7", + switcher->functionNamesByPriority[7]); obs_data_set_obj(save_data, "advanced-scene-switcher", obj); @@ -842,6 +894,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_array_release(randomArray); obs_data_array_release(fileArray); obs_data_array_release(mediaArray); + obs_data_array_release(timeArray); obs_data_release(obj); } else { @@ -874,6 +927,8 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_get_array(obj, "fileSwitches"); obs_data_array_t *mediaArray = obs_data_get_array(obj, "mediaSwitches"); + obs_data_array_t *timeArray = + obs_data_get_array(obj, "timeSwitches"); if (!obj) obj = obs_data_create(); @@ -1187,6 +1242,36 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) GetWeakTransitionByName(transition), state, restriction, time, (strcmp(scene, PREVIOUS_SCENE_NAME) == 0)); + + obs_data_release(array_obj); + } + + switcher->timeSwitches.clear(); + count = obs_data_array_count(timeArray); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = + obs_data_array_item(timeArray, i); + + const char *scene = + obs_data_get_string(array_obj, "scene"); + const char *transition = + obs_data_get_string(array_obj, "transition"); + QTime time = QTime::fromString( + obs_data_get_string(array_obj, "time")); + + string timeSwitchStr = + MakeTimeSwitchName(scene, transition, time) + .toUtf8() + .constData(); + + switcher->timeSwitches.emplace_back( + GetWeakSourceByName(scene), + GetWeakTransitionByName(transition), time, + (strcmp(scene, PREVIOUS_SCENE_NAME) == 0), + timeSwitchStr); + + obs_data_release(array_obj); } string autoStopScene = @@ -1230,6 +1315,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_set_default_int(obj, "priority4", DEFAULT_PRIORITY_4); obs_data_set_default_int(obj, "priority5", DEFAULT_PRIORITY_5); obs_data_set_default_int(obj, "priority6", DEFAULT_PRIORITY_6); + obs_data_set_default_int(obj, "priority7", DEFAULT_PRIORITY_7); switcher->functionNamesByPriority[0] = (obs_data_get_int(obj, "priority0")); @@ -1245,6 +1331,8 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) (obs_data_get_int(obj, "priority5")); switcher->functionNamesByPriority[6] = (obs_data_get_int(obj, "priority6")); + switcher->functionNamesByPriority[6] = + (obs_data_get_int(obj, "priority7")); if (!switcher->prioFuncsValid()) { switcher->functionNamesByPriority[0] = (DEFAULT_PRIORITY_0); @@ -1260,6 +1348,8 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) (DEFAULT_PRIORITY_5); switcher->functionNamesByPriority[6] = (DEFAULT_PRIORITY_6); + switcher->functionNamesByPriority[7] = + (DEFAULT_PRIORITY_7); } obs_data_array_release(array); @@ -1273,6 +1363,8 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) obs_data_array_release(ignoreIdleWindowsArray); obs_data_array_release(randomArray); obs_data_array_release(fileArray); + obs_data_array_release(mediaArray); + obs_data_array_release(timeArray); obs_data_release(obj); @@ -1356,7 +1448,11 @@ void SwitcherData::Thread() case MEDIA_FUNC: checkMediaSwitch(match, scene, transition); break; + case TIME_FUNC: + checkTimeSwitch(match, scene, transition); + break; } + if (switcher->stop) { goto endLoop; } diff --git a/src/headers/advanced-scene-switcher.hpp b/src/headers/advanced-scene-switcher.hpp index 88317c64..179130d0 100644 --- a/src/headers/advanced-scene-switcher.hpp +++ b/src/headers/advanced-scene-switcher.hpp @@ -42,6 +42,7 @@ public: int executableFindByData(const QString &exe); int IgnoreIdleWindowsFindByData(const QString &window); int randomFindByData(const QString &scene); + int timeFindByData(const QString &timeStr); void UpdateNonMatchingScene(const QString &name); void UpdateAutoStopScene(const QString &name); @@ -122,6 +123,10 @@ public slots: void on_mediaAdd_clicked(); void on_mediaRemove_clicked(); + void on_timeSwitches_currentRowChanged(int idx); + void on_timeAdd_clicked(); + void on_timeRemove_clicked(); + void on_priorityUp_clicked(); void on_priorityDown_clicked(); diff --git a/src/headers/switcher-data-structs.hpp b/src/headers/switcher-data-structs.hpp index 6b88e49c..2aae300b 100644 --- a/src/headers/switcher-data-structs.hpp +++ b/src/headers/switcher-data-structs.hpp @@ -23,6 +23,7 @@ #define SCREEN_REGION_FUNC 4 #define WINDOW_TITLE_FUNC 5 #define MEDIA_FUNC 6 +#define TIME_FUNC 7 #define DEFAULT_PRIORITY_0 READ_FILE_FUNC #define DEFAULT_PRIORITY_1 ROUND_TRIP_FUNC @@ -31,6 +32,7 @@ #define DEFAULT_PRIORITY_4 SCREEN_REGION_FUNC #define DEFAULT_PRIORITY_5 WINDOW_TITLE_FUNC #define DEFAULT_PRIORITY_6 MEDIA_FUNC +#define DEFAULT_PRIORITY_7 TIME_FUNC using namespace std; @@ -225,6 +227,26 @@ struct MediaSwitch { } }; +struct TimeSwitch { + OBSWeakSource scene; + OBSWeakSource transition; + QTime time; + bool matched; + bool usePreviousScene; + string timeSwitchStr; + + inline TimeSwitch(OBSWeakSource scene_, OBSWeakSource transition_, + QTime time_, bool usePreviousScene_, + string timeSwitchStr_) + : scene(scene_), + transition(transition_), + time(time_), + usePreviousScene(usePreviousScene_), + timeSwitchStr(timeSwitchStr_) + { + } +}; + typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch; /******************************************************************************** @@ -279,10 +301,12 @@ struct SwitcherData { vector mediaSwitches; + vector timeSwitches; + vector functionNamesByPriority = vector{ DEFAULT_PRIORITY_0, DEFAULT_PRIORITY_1, DEFAULT_PRIORITY_2, DEFAULT_PRIORITY_3, DEFAULT_PRIORITY_4, DEFAULT_PRIORITY_5, - DEFAULT_PRIORITY_6}; + DEFAULT_PRIORITY_6, DEFAULT_PRIORITY_7}; void Thread(); void Start(); @@ -313,6 +337,8 @@ struct SwitcherData { OBSWeakSource &transition, int &delay); void checkMediaSwitch(bool &match, OBSWeakSource &scene, OBSWeakSource &transition); + void checkTimeSwitch(bool &match, OBSWeakSource &scene, + OBSWeakSource &transition); void Prune() { @@ -399,6 +425,13 @@ struct SwitcherData { fileSwitches.erase(fileSwitches.begin() + i--); } + for (size_t i = 0; i < timeSwitches.size(); i++) { + TimeSwitch &s = timeSwitches[i]; + if (!WeakSourceValid(s.scene) || + !WeakSourceValid(s.transition)) + timeSwitches.erase(timeSwitches.begin() + i--); + } + if (!idleData.usePreviousScene && !WeakSourceValid(idleData.scene) || !WeakSourceValid(idleData.transition)) { diff --git a/src/headers/utility.hpp b/src/headers/utility.hpp index f1cca4f9..dc98a350 100644 --- a/src/headers/utility.hpp +++ b/src/headers/utility.hpp @@ -5,9 +5,9 @@ #include "switcher-data-structs.hpp" using namespace std; -static inline bool WeakSourceValid(obs_weak_source_t* ws) +static inline bool WeakSourceValid(obs_weak_source_t *ws) { - obs_source_t* source = obs_weak_source_get_source(ws); + obs_source_t *source = obs_weak_source_get_source(ws); if (source) obs_source_release(source); return !!source; @@ -107,7 +107,7 @@ static inline QString MakeFileSwitchName(const QString &scene, return switchName; } -typedef enum { +typedef enum { TIME_RESTRICTION_NONE, TIME_RESTRICTION_SHORTER, TIME_RESTRICTION_LONGER, @@ -159,6 +159,15 @@ MakeMediaSwitchName(const QString &source, const QString &scene, return switchName; } +static inline QString MakeTimeSwitchName(const QString &scene, + const QString &transition, QTime &time) +{ + QString switchName = QStringLiteral("At ") + time.toString() + + QStringLiteral(" switch to ") + scene + + QStringLiteral(" using ") + transition; + return switchName; +} + static inline string GetWeakSourceName(obs_weak_source_t *weak_source) { string name; diff --git a/src/priority.cpp b/src/priority.cpp index caaf99bd..22019b9c 100644 --- a/src/priority.cpp +++ b/src/priority.cpp @@ -36,7 +36,7 @@ bool SwitcherData::prioFuncsValid() for (int p : functionNamesByPriority) { - if (p < 0 || p > 6) + if (p < 0 || p > 7) return false; } return true; diff --git a/src/time-switch.cpp b/src/time-switch.cpp new file mode 100644 index 00000000..e45cd98f --- /dev/null +++ b/src/time-switch.cpp @@ -0,0 +1,147 @@ +#include "headers/advanced-scene-switcher.hpp" + +void SceneSwitcher::on_timeSwitches_currentRowChanged(int idx) +{ + if (loading) + return; + if (idx == -1) + return; + + QListWidgetItem *item = ui->timeSwitches->item(idx); + + QString timeScenestr = item->data(Qt::UserRole).toString(); + + lock_guard lock(switcher->m); + for (auto &s : switcher->timeSwitches) { + if (timeScenestr.compare(s.timeSwitchStr.c_str()) == 0) { + QString sceneName = GetWeakSourceName(s.scene).c_str(); + QString transitionName = + GetWeakSourceName(s.transition).c_str(); + ui->timeScenes->setCurrentText(sceneName); + ui->timeEdit->setTime(s.time); + ui->timeTransitions->setCurrentText(transitionName); + break; + } + } +} + +int SceneSwitcher::timeFindByData(const QString &timeStr) +{ + QRegExp rx("At " + timeStr + " switch to .*"); + int count = ui->timeSwitches->count(); + + for (int i = 0; i < count; i++) { + QListWidgetItem *item = ui->timeSwitches->item(i); + QString str = item->data(Qt::UserRole).toString(); + + if (rx.exactMatch(str)) + return i; + } + + return -1; +} + +void SceneSwitcher::on_timeAdd_clicked() +{ + QString sceneName = ui->timeScenes->currentText(); + QString transitionName = ui->timeTransitions->currentText(); + QTime time = ui->timeEdit->time(); + + if (sceneName.isEmpty()) + return; + + OBSWeakSource source = GetWeakSourceByQString(sceneName); + OBSWeakSource transition = GetWeakTransitionByQString(transitionName); + + QString text = MakeTimeSwitchName(sceneName, transitionName, time); + QVariant v = QVariant::fromValue(text); + + int idx = timeFindByData(time.toString()); + + if (idx == -1) { + lock_guard lock(switcher->m); + switcher->timeSwitches.emplace_back( + source, transition, time, + (sceneName == QString(PREVIOUS_SCENE_NAME)), + text.toUtf8().constData()); + + QListWidgetItem *item = + new QListWidgetItem(text, ui->timeSwitches); + item->setData(Qt::UserRole, v); + } else { + QListWidgetItem *item = ui->timeSwitches->item(idx); + item->setText(text); + + { + lock_guard lock(switcher->m); + for (auto &s : switcher->timeSwitches) { + if (s.time == time) { + s.scene = source; + s.transition = transition; + s.usePreviousScene = + (sceneName == + QString(PREVIOUS_SCENE_NAME)); + s.timeSwitchStr = + text.toUtf8().constData(); + ; + break; + } + } + } + + ui->timeSwitches->sortItems(); + } +} + +void SceneSwitcher::on_timeRemove_clicked() +{ + QListWidgetItem *item = ui->timeSwitches->currentItem(); + if (!item) + return; + + string text = item->data(Qt::UserRole).toString().toUtf8().constData(); + + { + lock_guard lock(switcher->m); + auto &switches = switcher->timeSwitches; + + for (auto it = switches.begin(); it != switches.end(); ++it) { + auto &s = *it; + + if (s.timeSwitchStr == text) { + switches.erase(it); + break; + } + } + } + + delete item; +} + +void SwitcherData::checkTimeSwitch(bool &match, OBSWeakSource &scene, + OBSWeakSource &transition) +{ + if (timeSwitches.size() == 0) + return; + + QTime now = QTime::currentTime(); + + for (TimeSwitch &s : timeSwitches) { + + QTime validSwitchTimeWindow = s.time.addMSecs(interval); + + match = s.time <= now && now <= validSwitchTimeWindow; + if (!match && + validSwitchTimeWindow.msecsSinceStartOfDay() < interval) { + // check for overflow + match = now >= s.time || now <= validSwitchTimeWindow; + } + + if (match) { + scene = (s.usePreviousScene) ? previousScene : s.scene; + transition = s.transition; + match = true; + break; + } + } +}