From 39592ff5ebb9aa641ae460b7c044e3fc1cc0196f Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sat, 3 Sep 2016 09:15:32 -0700 Subject: [PATCH] frontend-tools: Add scene switcher plugin --- UI/CMakeLists.txt | 2 + UI/frontend-plugins/CMakeLists.txt | 3 + .../frontend-tools/CMakeLists.txt | 46 ++ .../frontend-tools/auto-scene-switcher-osx.mm | 43 ++ .../auto-scene-switcher-win.cpp | 69 ++ .../frontend-tools/auto-scene-switcher.cpp | 588 ++++++++++++++++++ .../frontend-tools/auto-scene-switcher.hpp | 47 ++ .../frontend-tools/data/locale/en-US.ini | 11 + .../forms/auto-scene-switcher.ui | 310 +++++++++ .../frontend-tools/frontend-tools.c | 18 + 10 files changed, 1137 insertions(+) create mode 100644 UI/frontend-plugins/CMakeLists.txt create mode 100644 UI/frontend-plugins/frontend-tools/CMakeLists.txt create mode 100644 UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm create mode 100644 UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp create mode 100644 UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp create mode 100644 UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp create mode 100644 UI/frontend-plugins/frontend-tools/data/locale/en-US.ini create mode 100644 UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui create mode 100644 UI/frontend-plugins/frontend-tools/frontend-tools.c diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index e66a26d35..1a415345f 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -239,3 +239,5 @@ if (UNIX AND UNIX_STRUCTURE AND NOT APPLE) install(FILES forms/images/obs.png DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/icons/hicolor/256x256/apps) endif() + +add_subdirectory(frontend-plugins) diff --git a/UI/frontend-plugins/CMakeLists.txt b/UI/frontend-plugins/CMakeLists.txt new file mode 100644 index 000000000..0a90a60a0 --- /dev/null +++ b/UI/frontend-plugins/CMakeLists.txt @@ -0,0 +1,3 @@ +if(WIN32 OR APPLE) + add_subdirectory(frontend-tools) +endif() diff --git a/UI/frontend-plugins/frontend-tools/CMakeLists.txt b/UI/frontend-plugins/frontend-tools/CMakeLists.txt new file mode 100644 index 000000000..b15dc5256 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/CMakeLists.txt @@ -0,0 +1,46 @@ +project(frontend-tools) + +if(APPLE) + find_library(COCOA Cocoa) + include_directories(${COCOA}) +endif() + +set(frontend-tools_HEADERS + auto-scene-switcher.hpp + ) +set(frontend-tools_SOURCES + frontend-tools.c + auto-scene-switcher.cpp + ) +set(frontend-tools_UI + forms/auto-scene-switcher.ui + ) + +if(WIN32) + set(frontend-tools_PLATFORM_SOURCES + auto-scene-switcher-win.cpp) +elseif(APPLE) + set(frontend-tools_PLATFORM_SOURCES + auto-scene-switcher-osx.mm) + set_source_files_properties(auto-scene-switcher-osx.mm + PROPERTIES COMPILE_FLAGS "-fobjc-arc") + + set(frontend-tools_PLATFORM_LIBS + ${COCOA}) +endif() + +qt5_wrap_ui(frontend-tools_UI_HEADERS ${frontend-tools_UI}) + +add_library(frontend-tools MODULE + ${frontend-tools_HEADERS} + ${frontend-tools_SOURCES} + ${frontend-tools_PLATFORM_SOURCES} + ${frontend-tools_UI_HEADERS} + ) +target_link_libraries(frontend-tools + ${frontend-tools_PLATFORM_LIBS} + obs-frontend-api + Qt5::Widgets + libobs) + +install_obs_plugin_with_data(frontend-tools data) diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm new file mode 100644 index 000000000..92dc8b757 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-osx.mm @@ -0,0 +1,43 @@ +#import +#include +#include "auto-scene-switcher.hpp" + +using namespace std; + +void GetWindowList(vector &windows) +{ + windows.resize(0); + + @autoreleasepool { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSArray *array = [ws runningApplications]; + for (NSRunningApplication *app in array) { + NSString *name = app.localizedName; + if (!name) + continue; + + const char *str = name.UTF8String; + if (str && *str) + windows.emplace_back(str); + } + } +} + +void GetCurrentWindowTitle(string &title) +{ + title.resize(0); + + @autoreleasepool { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSRunningApplication *app = [ws frontmostApplication]; + if (app) { + NSString *name = app.localizedName; + if (!name) + return; + + const char *str = name.UTF8String; + if (str && *str) + title = str; + } + } +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp new file mode 100644 index 000000000..604504034 --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher-win.cpp @@ -0,0 +1,69 @@ +#include +#include +#include "auto-scene-switcher.hpp" + +using namespace std; + +static bool GetWindowTitle(HWND window, string &title) +{ + size_t len = (size_t)GetWindowTextLengthW(window); + wstring wtitle; + + wtitle.resize(len); + if (!GetWindowTextW(window, &wtitle[0], (int)len + 1)) + return false; + + len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0); + title.resize(len); + os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1); + return true; +} + +static bool WindowValid(HWND window) +{ + LONG_PTR styles, ex_styles; + RECT rect; + DWORD id; + + if (!IsWindowVisible(window)) + return false; + GetWindowThreadProcessId(window, &id); + if (id == GetCurrentProcessId()) + return false; + + GetClientRect(window, &rect); + styles = GetWindowLongPtr(window, GWL_STYLE); + ex_styles = GetWindowLongPtr(window, GWL_EXSTYLE); + + if (ex_styles & WS_EX_TOOLWINDOW) + return false; + if (styles & WS_CHILD) + return false; + + return true; +} + +void GetWindowList(vector &windows) +{ + HWND window = GetWindow(GetDesktopWindow(), GW_CHILD); + + while (window) { + string title; + if (WindowValid(window) && GetWindowTitle(window, title)) + windows.emplace_back(title); + window = GetNextWindow(window, GW_HWNDNEXT); + } +} + +void GetCurrentWindowTitle(string &title) +{ + HWND window = GetForegroundWindow(); + DWORD id; + + GetWindowThreadProcessId(window, &id); + if (id == GetCurrentProcessId()) { + title = ""; + return; + } + GetWindowTitle(window, title); +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp new file mode 100644 index 000000000..9c737bf4a --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp @@ -0,0 +1,588 @@ +#include +#include +#include +#include +#include +#include +#include "auto-scene-switcher.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define DEFAULT_INTERVAL 300 + +struct SceneSwitch { + OBSWeakSource scene; + string window; + regex re; + + inline SceneSwitch(OBSWeakSource scene_, const char *window_) + : scene(scene_), window(window_), re(window_) + { + } +}; + +static inline bool WeakSourceValid(obs_weak_source_t *ws) +{ + obs_source_t *source = obs_weak_source_get_source(ws); + if (source) + obs_source_release(source); + return !!source; +} + +struct SwitcherData { + thread th; + condition_variable cv; + mutex m; + bool stop = false; + + vector switches; + OBSWeakSource nonMatchingScene; + int interval = DEFAULT_INTERVAL; + bool switchIfNotMatching = false; + bool startAtLaunch = false; + + void Thread(); + void Start(); + void Stop(); + + void Prune() + { + for (size_t i = 0; i < switches.size(); i++) { + SceneSwitch &s = switches[i]; + if (!WeakSourceValid(s.scene)) + switches.erase(switches.begin() + i--); + } + + if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) { + switchIfNotMatching = false; + nonMatchingScene = nullptr; + } + } + + inline ~SwitcherData() + { + Stop(); + } +}; + +static SwitcherData *switcher = nullptr; + +static inline QString MakeSwitchName(const QString &scene, + const QString &window) +{ + return QStringLiteral("[") + scene + QStringLiteral("]: ") + window; +} + +static inline string GetWeakSourceName(obs_weak_source_t *weak_source) +{ + string name; + + obs_source_t *source = obs_weak_source_get_source(weak_source); + if (source) { + name = obs_source_get_name(source); + obs_source_release(source); + } + + return name; +} + +static inline OBSWeakSource GetWeakSourceByName(const char *name) +{ + OBSWeakSource weak; + obs_source_t *source = obs_get_source_by_name(name); + if (source) { + weak = obs_source_get_weak_source(source); + obs_weak_source_release(weak); + obs_source_release(source); + } + + return weak; +} + +static inline OBSWeakSource GetWeakSourceByQString(const QString &name) +{ + return GetWeakSourceByName(name.toUtf8().constData()); +} + +SceneSwitcher::SceneSwitcher(QWidget *parent) + : QDialog(parent), + ui(new Ui_SceneSwitcher) +{ + ui->setupUi(this); + + lock_guard lock(switcher->m); + + switcher->Prune(); + + BPtr scenes = obs_frontend_get_scene_names(); + char **temp = scenes; + while (*temp) { + const char *name = *temp; + ui->scenes->addItem(name); + ui->noMatchSwitchScene->addItem(name); + temp++; + } + + if (switcher->switchIfNotMatching) + ui->noMatchSwitch->setChecked(true); + else + ui->noMatchDontSwitch->setChecked(true); + + ui->noMatchSwitchScene->setCurrentText( + GetWeakSourceName(switcher->nonMatchingScene).c_str()); + ui->checkInterval->setValue(switcher->interval); + + vector windows; + GetWindowList(windows); + + for (string &window : windows) + ui->windows->addItem(window.c_str()); + + for (auto &s : switcher->switches) { + string sceneName = GetWeakSourceName(s.scene); + QString text = MakeSwitchName(sceneName.c_str(), + s.window.c_str()); + + QListWidgetItem *item = new QListWidgetItem(text, + ui->switches); + item->setData(Qt::UserRole, s.window.c_str()); + } + + if (switcher->th.joinable()) + SetStarted(); + else + SetStopped(); + + loading = false; +} + +void SceneSwitcher::closeEvent(QCloseEvent*) +{ + obs_frontend_save(); +} + +int SceneSwitcher::FindByData(const QString &window) +{ + int count = ui->switches->count(); + int idx = -1; + + for (int i = 0; i < count; i++) { + QListWidgetItem *item = ui->switches->item(i); + QString itemWindow = + item->data(Qt::UserRole).toString(); + + if (itemWindow == window) { + idx = i; + break; + } + } + + return idx; +} + +void SceneSwitcher::on_switches_currentRowChanged(int idx) +{ + if (loading) + return; + if (idx == -1) + return; + + QListWidgetItem *item = ui->switches->item(idx); + + QString window = item->data(Qt::UserRole).toString(); + + lock_guard lock(switcher->m); + for (auto &s : switcher->switches) { + if (window.compare(s.window.c_str()) == 0) { + string name = GetWeakSourceName(s.scene); + ui->scenes->setCurrentText(name.c_str()); + ui->windows->setCurrentText(window); + break; + } + } +} + +void SceneSwitcher::on_close_clicked() +{ + done(0); +} + +void SceneSwitcher::on_add_clicked() +{ + QString sceneName = ui->scenes->currentText(); + QString windowName = ui->windows->currentText(); + + if (windowName.isEmpty()) + return; + + OBSWeakSource source = GetWeakSourceByQString(sceneName); + QVariant v = QVariant::fromValue(windowName); + + QString text = MakeSwitchName(sceneName, windowName); + + int idx = FindByData(windowName); + + if (idx == -1) { + QListWidgetItem *item = new QListWidgetItem(text, + ui->switches); + item->setData(Qt::UserRole, v); + + lock_guard lock(switcher->m); + switcher->switches.emplace_back(source, + windowName.toUtf8().constData()); + } else { + QListWidgetItem *item = ui->switches->item(idx); + item->setText(text); + + string window = windowName.toUtf8().constData(); + + { + lock_guard lock(switcher->m); + for (auto &s : switcher->switches) { + if (s.window == window) { + s.scene = source; + break; + } + } + } + + ui->switches->sortItems(); + } +} + +void SceneSwitcher::on_remove_clicked() +{ + QListWidgetItem *item = ui->switches->currentItem(); + if (!item) + return; + + string window = + item->data(Qt::UserRole).toString().toUtf8().constData(); + + { + lock_guard lock(switcher->m); + auto &switches = switcher->switches; + + for (auto it = switches.begin(); it != switches.end(); ++it) { + auto &s = *it; + + if (s.window == window) { + switches.erase(it); + break; + } + } + } + + delete item; +} + +void SceneSwitcher::on_startAtLaunch_toggled(bool value) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->startAtLaunch = value; +} + +void SceneSwitcher::UpdateNonMatchingScene(const QString &name) +{ + obs_source_t *scene = obs_get_source_by_name( + name.toUtf8().constData()); + obs_weak_source_t *ws = obs_source_get_weak_source(scene); + + switcher->nonMatchingScene = ws; + + obs_weak_source_release(ws); + obs_source_release(scene); +} + +void SceneSwitcher::on_noMatchDontSwitch_clicked() +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->switchIfNotMatching = false; +} + +void SceneSwitcher::on_noMatchSwitch_clicked() +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->switchIfNotMatching = true; + UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText()); +} + +void SceneSwitcher::on_noMatchSwitchScene_currentTextChanged( + const QString &text) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + UpdateNonMatchingScene(text); +} + +void SceneSwitcher::on_checkInterval_valueChanged(int value) +{ + if (loading) + return; + + lock_guard lock(switcher->m); + switcher->interval = value; +} + +void SceneSwitcher::SetStarted() +{ + ui->toggleStartButton->setText(obs_module_text("Stop")); + ui->pluginRunningText->setText(obs_module_text("Active")); +} + +void SceneSwitcher::SetStopped() +{ + ui->toggleStartButton->setText(obs_module_text("Start")); + ui->pluginRunningText->setText(obs_module_text("Inactive")); +} + +void SceneSwitcher::on_toggleStartButton_clicked() +{ + if (switcher->th.joinable()) { + switcher->Stop(); + SetStopped(); + } else { + switcher->Start(); + SetStarted(); + } +} + +static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *) +{ + if (saving) { + lock_guard lock(switcher->m); + obs_data_t *obj = obs_data_create(); + obs_data_array_t *array = obs_data_array_create(); + + switcher->Prune(); + + for (SceneSwitch &s : switcher->switches) { + obs_data_t *array_obj = obs_data_create(); + + obs_source_t *source = obs_weak_source_get_source( + s.scene); + if (source) { + const char *n = obs_source_get_name(source); + obs_data_set_string(array_obj, "scene", n); + obs_data_set_string(array_obj, "window_title", + s.window.c_str()); + obs_data_array_push_back(array, array_obj); + obs_source_release(source); + } + + obs_data_release(array_obj); + } + + string nonMatchingSceneName = + GetWeakSourceName(switcher->nonMatchingScene); + + obs_data_set_int(obj, "interval", switcher->interval); + obs_data_set_string(obj, "non_matching_scene", + nonMatchingSceneName.c_str()); + obs_data_set_bool(obj, "switch_if_not_matching", + switcher->switchIfNotMatching); + obs_data_set_bool(obj, "active", switcher->th.joinable()); + obs_data_set_array(obj, "switches", array); + + obs_data_set_obj(save_data, "auto-scene-switcher", obj); + + obs_data_array_release(array); + obs_data_release(obj); + } else { + switcher->m.lock(); + + obs_data_t *obj = obs_data_get_obj(save_data, + "auto-scene-switcher"); + obs_data_array_t *array = obs_data_get_array(obj, "switches"); + size_t count = obs_data_array_count(array); + + if (!obj) + obj = obs_data_create(); + + obs_data_set_default_int(obj, "interval", DEFAULT_INTERVAL); + + switcher->interval = obs_data_get_int(obj, "interval"); + switcher->switchIfNotMatching = + obs_data_get_bool(obj, "switch_if_not_matching"); + string nonMatchingScene = + obs_data_get_string(obj, "non_matching_scene"); + bool active = obs_data_get_bool(obj, "active"); + + switcher->nonMatchingScene = + GetWeakSourceByName(nonMatchingScene.c_str()); + + switcher->switches.clear(); + + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(array, i); + + const char *scene = + obs_data_get_string(array_obj, "scene"); + const char *window = + obs_data_get_string(array_obj, "window_title"); + + switcher->switches.emplace_back( + GetWeakSourceByName(scene), + window); + + obs_data_release(array_obj); + } + + obs_data_array_release(array); + obs_data_release(obj); + + switcher->m.unlock(); + + if (active) + switcher->Start(); + else + switcher->Stop(); + } +} + +void SwitcherData::Thread() +{ + chrono::duration duration = + chrono::milliseconds(interval); + string lastTitle; + string title; + + for (;;) { + unique_lock lock(m); + OBSWeakSource scene; + bool match = false; + + cv.wait_for(lock, duration); + if (switcher->stop) { + switcher->stop = false; + break; + } + + duration = chrono::milliseconds(interval); + + GetCurrentWindowTitle(title); + + if (lastTitle != title) { + switcher->Prune(); + + for (SceneSwitch &s : switches) { + if (s.window == title) { + match = true; + scene = s.scene; + break; + } + } + + /* try regex */ + if (!match) { + for (SceneSwitch &s : switches) { + try { + bool matches = regex_match( + title, s.re); + if (matches) { + match = true; + scene = s.scene; + break; + } + } catch (const regex_error &) {} + } + } + + if (!match && switchIfNotMatching && + nonMatchingScene) { + match = true; + scene = nonMatchingScene; + } + + if (match) { + obs_source_t *source = + obs_weak_source_get_source(scene); + obs_source_t *currentSource = + obs_frontend_get_current_scene(); + + if (source && source != currentSource) + obs_frontend_set_current_scene(source); + + obs_source_release(currentSource); + obs_source_release(source); + } + } + + lastTitle = title; + } +} + +void SwitcherData::Start() +{ + if (!switcher->th.joinable()) + switcher->th = thread([] () {switcher->Thread();}); +} + +void SwitcherData::Stop() +{ + if (th.joinable()) { + { + lock_guard lock(m); + stop = true; + } + cv.notify_one(); + th.join(); + } +} + +extern "C" void FreeSceneSwitcher() +{ + delete switcher; + switcher = nullptr; +} + +static void OBSEvent(enum obs_frontend_event event, void *) +{ + if (event == OBS_FRONTEND_EVENT_EXIT) + FreeSceneSwitcher(); +} + +extern "C" void InitSceneSwitcher() +{ + QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction( + obs_module_text("SceneSwitcher")); + + switcher = new SwitcherData; + + auto cb = [] () + { + obs_frontend_push_ui_translation(obs_module_get_string); + + QMainWindow *window = + (QMainWindow*)obs_frontend_get_main_window(); + + SceneSwitcher ss(window); + ss.exec(); + + obs_frontend_pop_ui_translation(); + }; + + obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); + obs_frontend_add_event_callback(OBSEvent, nullptr); + + action->connect(action, &QAction::triggered, cb); +} diff --git a/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp new file mode 100644 index 000000000..4a155ac9c --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +#include "ui_auto-scene-switcher.h" + +struct obs_weak_source; +typedef struct obs_weak_source obs_weak_source_t; + +class QCloseEvent; + +class SceneSwitcher : public QDialog { + Q_OBJECT + +public: + std::unique_ptr ui; + bool loading = true; + + SceneSwitcher(QWidget *parent); + + void closeEvent(QCloseEvent *event) override; + + void SetStarted(); + void SetStopped(); + + int FindByData(const QString &window); + + void UpdateNonMatchingScene(const QString &name); + +public slots: + void on_switches_currentRowChanged(int idx); + void on_close_clicked(); + void on_add_clicked(); + void on_remove_clicked(); + void on_noMatchDontSwitch_clicked(); + void on_noMatchSwitch_clicked(); + void on_startAtLaunch_toggled(bool value); + void on_noMatchSwitchScene_currentTextChanged(const QString &text); + void on_checkInterval_valueChanged(int value); + void on_toggleStartButton_clicked(); +}; + +void GetWindowList(std::vector &windows); +void GetCurrentWindowTitle(std::string &title); diff --git a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini new file mode 100644 index 000000000..0bf3532cb --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini @@ -0,0 +1,11 @@ +SceneSwitcher="Automatic Scene Switcher" +SceneSwitcher.OnNoMatch="When no window matches:" +SceneSwitcher.OnNoMatch.DontSwitch="Don't switch" +SceneSwitcher.OnNoMatch.SwitchTo="Switch to:" +SceneSwitcher.CheckInterval="Check active window title every:" +SceneSwitcher.StartAtLaunch="Automatically start for this scene collection" +SceneSwitcher.ActiveOrNotActive="Scene Switcher is:" +Active="Active" +Inactive="Inactive" +Start="Start" +Stop="Stop" diff --git a/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui b/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui new file mode 100644 index 000000000..ea8b51edf --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui @@ -0,0 +1,310 @@ + + + SceneSwitcher + + + + 0 + 0 + 743 + 563 + + + + SceneSwitcher + + + + + + + + true + + + 20 + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + + + 0 + 0 + + + + true + + + + + + + 4 + + + + + + 22 + 22 + + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + 0 + 0 + + + + SceneSwitcher.OnNoMatch + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SceneSwitcher.OnNoMatch.DontSwitch + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SceneSwitcher.OnNoMatch.SwitchTo + + + + + + + false + + + + 100 + 0 + + + + + + + + + + + + SceneSwitcher.CheckInterval + + + checkInterval + + + + + + + + 100 + 0 + + + + ms + + + 50 + + + 20000 + + + 300 + + + + + + + SceneSwitcher.ActiveOrNotActive + + + + + + + Qt::Horizontal + + + + 200 + 20 + + + + + + + + Start + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 40 + + + + + + + + Not Active + + + + + + + + + Close + + + + + + + + + noMatchSwitch + toggled(bool) + noMatchSwitchScene + setEnabled(bool) + + + 286 + 347 + + + 483 + 352 + + + + + diff --git a/UI/frontend-plugins/frontend-tools/frontend-tools.c b/UI/frontend-plugins/frontend-tools/frontend-tools.c new file mode 100644 index 000000000..3826f041f --- /dev/null +++ b/UI/frontend-plugins/frontend-tools/frontend-tools.c @@ -0,0 +1,18 @@ +#include + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("frontend-tools", "en-US") + +void InitSceneSwitcher(); +void FreeSceneSwitcher(); + +bool obs_module_load(void) +{ + InitSceneSwitcher(); + return true; +} + +void obs_module_unload(void) +{ + FreeSceneSwitcher(); +}