frontend-tools: Add scene switcher plugin

This commit is contained in:
jp9000 2016-09-03 09:15:32 -07:00
parent 988bbc6080
commit 39592ff5eb
10 changed files with 1137 additions and 0 deletions

View File

@ -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)

View File

@ -0,0 +1,3 @@
if(WIN32 OR APPLE)
add_subdirectory(frontend-tools)
endif()

View File

@ -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)

View File

@ -0,0 +1,43 @@
#import <AppKit/AppKit.h>
#include <util/platform.h>
#include "auto-scene-switcher.hpp"
using namespace std;
void GetWindowList(vector<string> &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;
}
}
}

View File

@ -0,0 +1,69 @@
#include <windows.h>
#include <util/platform.h>
#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<string> &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);
}

View File

@ -0,0 +1,588 @@
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <obs.hpp>
#include <util/util.hpp>
#include <QMainWindow>
#include <QAction>
#include "auto-scene-switcher.hpp"
#include <condition_variable>
#include <chrono>
#include <string>
#include <vector>
#include <thread>
#include <regex>
#include <mutex>
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<SceneSwitch> 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<mutex> lock(switcher->m);
switcher->Prune();
BPtr<char*> 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<string> 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<mutex> 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<mutex> 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<mutex> 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<mutex> 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<mutex> 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<mutex> lock(switcher->m);
switcher->switchIfNotMatching = false;
}
void SceneSwitcher::on_noMatchSwitch_clicked()
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->switchIfNotMatching = true;
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
}
void SceneSwitcher::on_noMatchSwitchScene_currentTextChanged(
const QString &text)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
UpdateNonMatchingScene(text);
}
void SceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading)
return;
lock_guard<mutex> 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<mutex> 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<long long, milli> duration =
chrono::milliseconds(interval);
string lastTitle;
string title;
for (;;) {
unique_lock<mutex> 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<mutex> 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);
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <QDialog>
#include <memory>
#include <vector>
#include <string>
#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_SceneSwitcher> 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<std::string> &windows);
void GetCurrentWindowTitle(std::string &title);

View File

@ -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"

View File

@ -0,0 +1,310 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SceneSwitcher</class>
<widget class="QDialog" name="SceneSwitcher">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>743</width>
<height>563</height>
</rect>
</property>
<property name="windowTitle">
<string>SceneSwitcher</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="windows">
<property name="editable">
<bool>true</bool>
</property>
<property name="maxVisibleItems">
<number>20</number>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="scenes">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="switches">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QPushButton" name="add">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SceneSwitcher.OnNoMatch</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="noMatchDontSwitch">
<property name="text">
<string>SceneSwitcher.OnNoMatch.DontSwitch</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="noMatchSwitch">
<property name="text">
<string>SceneSwitcher.OnNoMatch.SwitchTo</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="noMatchSwitchScene">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>SceneSwitcher.CheckInterval</string>
</property>
<property name="buddy">
<cstring>checkInterval</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="checkInterval">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>50</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>SceneSwitcher.ActiveOrNotActive</string>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="toggleStartButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QLabel" name="pluginRunningText">
<property name="text">
<string notr="true">Not Active</string>
</property>
</widget>
</item>
</layout>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>noMatchSwitch</sender>
<signal>toggled(bool)</signal>
<receiver>noMatchSwitchScene</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>286</x>
<y>347</y>
</hint>
<hint type="destinationlabel">
<x>483</x>
<y>352</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,18 @@
#include <obs-module.h>
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();
}