frontend-tools: Add scene switcher plugin
This commit is contained in:
parent
988bbc6080
commit
39592ff5eb
@ -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)
|
||||
|
3
UI/frontend-plugins/CMakeLists.txt
Normal file
3
UI/frontend-plugins/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
if(WIN32 OR APPLE)
|
||||
add_subdirectory(frontend-tools)
|
||||
endif()
|
46
UI/frontend-plugins/frontend-tools/CMakeLists.txt
Normal file
46
UI/frontend-plugins/frontend-tools/CMakeLists.txt
Normal 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)
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
588
UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp
Normal file
588
UI/frontend-plugins/frontend-tools/auto-scene-switcher.cpp
Normal 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);
|
||||
}
|
47
UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp
Normal file
47
UI/frontend-plugins/frontend-tools/auto-scene-switcher.hpp
Normal 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);
|
11
UI/frontend-plugins/frontend-tools/data/locale/en-US.ini
Normal file
11
UI/frontend-plugins/frontend-tools/data/locale/en-US.ini
Normal 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"
|
310
UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui
Normal file
310
UI/frontend-plugins/frontend-tools/forms/auto-scene-switcher.ui
Normal 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>
|
18
UI/frontend-plugins/frontend-tools/frontend-tools.c
Normal file
18
UI/frontend-plugins/frontend-tools/frontend-tools.c
Normal 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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user