566 lines
13 KiB
C++
566 lines
13 KiB
C++
#include "scripts.hpp"
|
|
#include "frontend-tools-config.h"
|
|
#include "../../properties-view.hpp"
|
|
|
|
#include <QFileDialog>
|
|
#include <QPlainTextEdit>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QScrollBar>
|
|
#include <QPushButton>
|
|
#include <QFontDatabase>
|
|
#include <QFont>
|
|
#include <QDialogButtonBox>
|
|
#include <QResizeEvent>
|
|
#include <QAction>
|
|
|
|
#include <obs.hpp>
|
|
#include <obs-module.h>
|
|
#include <obs-frontend-api.h>
|
|
#include <obs-scripting.h>
|
|
|
|
#include <util/config-file.h>
|
|
#include <util/platform.h>
|
|
#include <util/util.hpp>
|
|
|
|
#include <string>
|
|
|
|
#include "ui_scripts.h"
|
|
|
|
#if COMPILE_PYTHON && (defined(_WIN32) || defined(__APPLE__))
|
|
#define PYTHON_UI 1
|
|
#else
|
|
#define PYTHON_UI 0
|
|
#endif
|
|
|
|
#if ARCH_BITS == 64
|
|
#define ARCH_NAME "64bit"
|
|
#else
|
|
#define ARCH_NAME "32bit"
|
|
#endif
|
|
|
|
#define PYTHONPATH_LABEL_TEXT "PythonSettings.PythonInstallPath" ARCH_NAME
|
|
|
|
/* ----------------------------------------------------------------- */
|
|
|
|
using OBSScript = OBSObj<obs_script_t *, obs_script_destroy>;
|
|
|
|
struct ScriptData {
|
|
std::vector<OBSScript> scripts;
|
|
|
|
inline obs_script_t *FindScript(const char *path)
|
|
{
|
|
for (OBSScript &script : scripts) {
|
|
const char *script_path = obs_script_get_path(script);
|
|
if (strcmp(script_path, path) == 0) {
|
|
return script;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool ScriptOpened(const char *path)
|
|
{
|
|
for (OBSScript &script : scripts) {
|
|
const char *script_path = obs_script_get_path(script);
|
|
if (strcmp(script_path, path) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
static ScriptData *scriptData = nullptr;
|
|
static ScriptsTool *scriptsWindow = nullptr;
|
|
static ScriptLogWindow *scriptLogWindow = nullptr;
|
|
static QPlainTextEdit *scriptLogWidget = nullptr;
|
|
|
|
/* ----------------------------------------------------------------- */
|
|
|
|
ScriptLogWindow::ScriptLogWindow() : QWidget(nullptr)
|
|
{
|
|
const QFont fixedFont =
|
|
QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
|
|
QPlainTextEdit *edit = new QPlainTextEdit();
|
|
edit->setReadOnly(true);
|
|
edit->setFont(fixedFont);
|
|
edit->setWordWrapMode(QTextOption::NoWrap);
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
|
QPushButton *clearButton = new QPushButton(tr("Clear"));
|
|
connect(clearButton, &QPushButton::clicked, this,
|
|
&ScriptLogWindow::ClearWindow);
|
|
QPushButton *closeButton = new QPushButton(tr("Close"));
|
|
connect(closeButton, &QPushButton::clicked, this, &QDialog::hide);
|
|
|
|
buttonLayout->addStretch();
|
|
buttonLayout->addWidget(clearButton);
|
|
buttonLayout->addWidget(closeButton);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->addWidget(edit);
|
|
layout->addLayout(buttonLayout);
|
|
|
|
setLayout(layout);
|
|
scriptLogWidget = edit;
|
|
|
|
resize(600, 400);
|
|
|
|
config_t *global_config = obs_frontend_get_global_config();
|
|
const char *geom =
|
|
config_get_string(global_config, "ScriptLogWindow", "geometry");
|
|
if (geom != nullptr) {
|
|
QByteArray ba = QByteArray::fromBase64(QByteArray(geom));
|
|
restoreGeometry(ba);
|
|
}
|
|
|
|
setWindowTitle(obs_module_text("ScriptLogWindow"));
|
|
|
|
connect(edit->verticalScrollBar(), &QAbstractSlider::sliderMoved, this,
|
|
&ScriptLogWindow::ScrollChanged);
|
|
}
|
|
|
|
ScriptLogWindow::~ScriptLogWindow()
|
|
{
|
|
config_t *global_config = obs_frontend_get_global_config();
|
|
config_set_string(global_config, "ScriptLogWindow", "geometry",
|
|
saveGeometry().toBase64().constData());
|
|
}
|
|
|
|
void ScriptLogWindow::ScrollChanged(int val)
|
|
{
|
|
QScrollBar *scroll = scriptLogWidget->verticalScrollBar();
|
|
bottomScrolled = (val == scroll->maximum());
|
|
}
|
|
|
|
void ScriptLogWindow::resizeEvent(QResizeEvent *event)
|
|
{
|
|
QWidget::resizeEvent(event);
|
|
|
|
if (bottomScrolled) {
|
|
QScrollBar *scroll = scriptLogWidget->verticalScrollBar();
|
|
scroll->setValue(scroll->maximum());
|
|
}
|
|
}
|
|
|
|
void ScriptLogWindow::AddLogMsg(int log_level, QString msg)
|
|
{
|
|
QScrollBar *scroll = scriptLogWidget->verticalScrollBar();
|
|
bottomScrolled = scroll->value() == scroll->maximum();
|
|
|
|
lines += QStringLiteral("\n");
|
|
lines += msg;
|
|
scriptLogWidget->setPlainText(lines);
|
|
|
|
if (bottomScrolled)
|
|
scroll->setValue(scroll->maximum());
|
|
|
|
if (log_level <= LOG_WARNING) {
|
|
show();
|
|
raise();
|
|
}
|
|
}
|
|
|
|
void ScriptLogWindow::ClearWindow()
|
|
{
|
|
Clear();
|
|
scriptLogWidget->setPlainText(QString());
|
|
}
|
|
|
|
void ScriptLogWindow::Clear()
|
|
{
|
|
lines.clear();
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- */
|
|
|
|
ScriptsTool::ScriptsTool() : QWidget(nullptr), ui(new Ui_ScriptsTool)
|
|
{
|
|
ui->setupUi(this);
|
|
RefreshLists();
|
|
|
|
#if PYTHON_UI
|
|
config_t *config = obs_frontend_get_global_config();
|
|
const char *path =
|
|
config_get_string(config, "Python", "Path" ARCH_NAME);
|
|
ui->pythonPath->setText(path);
|
|
ui->pythonPathLabel->setText(obs_module_text(PYTHONPATH_LABEL_TEXT));
|
|
#else
|
|
delete ui->pythonSettingsTab;
|
|
ui->pythonSettingsTab = nullptr;
|
|
#endif
|
|
|
|
delete propertiesView;
|
|
propertiesView = new QWidget();
|
|
propertiesView->setSizePolicy(QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding);
|
|
ui->propertiesLayout->addWidget(propertiesView);
|
|
}
|
|
|
|
ScriptsTool::~ScriptsTool()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void ScriptsTool::RemoveScript(const char *path)
|
|
{
|
|
for (size_t i = 0; i < scriptData->scripts.size(); i++) {
|
|
OBSScript &script = scriptData->scripts[i];
|
|
|
|
const char *script_path = obs_script_get_path(script);
|
|
if (strcmp(script_path, path) == 0) {
|
|
scriptData->scripts.erase(scriptData->scripts.begin() +
|
|
i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptsTool::ReloadScript(const char *path)
|
|
{
|
|
for (OBSScript &script : scriptData->scripts) {
|
|
const char *script_path = obs_script_get_path(script);
|
|
if (strcmp(script_path, path) == 0) {
|
|
obs_script_reload(script);
|
|
|
|
OBSData settings = obs_data_create();
|
|
obs_data_release(settings);
|
|
|
|
obs_properties_t *prop =
|
|
obs_script_get_properties(script);
|
|
obs_properties_apply_settings(prop, settings);
|
|
obs_properties_destroy(prop);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptsTool::RefreshLists()
|
|
{
|
|
ui->scripts->clear();
|
|
|
|
for (OBSScript &script : scriptData->scripts) {
|
|
const char *script_file = obs_script_get_file(script);
|
|
const char *script_path = obs_script_get_path(script);
|
|
|
|
QListWidgetItem *item = new QListWidgetItem(script_file);
|
|
item->setData(Qt::UserRole, QString(script_path));
|
|
ui->scripts->addItem(item);
|
|
}
|
|
}
|
|
|
|
void ScriptsTool::on_close_clicked()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void ScriptsTool::on_addScripts_clicked()
|
|
{
|
|
const char **formats = obs_scripting_supported_formats();
|
|
const char **cur_format = formats;
|
|
QString extensions;
|
|
QString filter;
|
|
|
|
while (*cur_format) {
|
|
if (!extensions.isEmpty())
|
|
extensions += QStringLiteral(" ");
|
|
|
|
extensions += QStringLiteral("*.");
|
|
extensions += *cur_format;
|
|
|
|
cur_format++;
|
|
}
|
|
|
|
if (!extensions.isEmpty()) {
|
|
filter += obs_module_text("FileFilter.ScriptFiles");
|
|
filter += QStringLiteral(" (");
|
|
filter += extensions;
|
|
filter += QStringLiteral(")");
|
|
}
|
|
|
|
if (filter.isEmpty())
|
|
return;
|
|
|
|
static std::string lastBrowsedDir;
|
|
|
|
if (lastBrowsedDir.empty()) {
|
|
BPtr<char> baseScriptPath = obs_module_file("scripts");
|
|
lastBrowsedDir = baseScriptPath;
|
|
}
|
|
|
|
QFileDialog dlg(this, obs_module_text("AddScripts"));
|
|
dlg.setFileMode(QFileDialog::ExistingFiles);
|
|
dlg.setDirectory(QDir(lastBrowsedDir.c_str()));
|
|
dlg.setNameFilter(filter);
|
|
dlg.exec();
|
|
|
|
QStringList files = dlg.selectedFiles();
|
|
if (!files.count())
|
|
return;
|
|
|
|
lastBrowsedDir = dlg.directory().path().toUtf8().constData();
|
|
|
|
for (const QString &file : files) {
|
|
QByteArray pathBytes = file.toUtf8();
|
|
const char *path = pathBytes.constData();
|
|
|
|
if (scriptData->ScriptOpened(path)) {
|
|
continue;
|
|
}
|
|
|
|
obs_script_t *script = obs_script_create(path, NULL);
|
|
if (script) {
|
|
const char *script_file = obs_script_get_file(script);
|
|
|
|
scriptData->scripts.emplace_back(script);
|
|
|
|
QListWidgetItem *item =
|
|
new QListWidgetItem(script_file);
|
|
item->setData(Qt::UserRole, QString(file));
|
|
ui->scripts->addItem(item);
|
|
|
|
OBSData settings = obs_data_create();
|
|
obs_data_release(settings);
|
|
|
|
obs_properties_t *prop =
|
|
obs_script_get_properties(script);
|
|
obs_properties_apply_settings(prop, settings);
|
|
obs_properties_destroy(prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptsTool::on_removeScripts_clicked()
|
|
{
|
|
QList<QListWidgetItem *> items = ui->scripts->selectedItems();
|
|
|
|
for (QListWidgetItem *item : items)
|
|
RemoveScript(item->data(Qt::UserRole)
|
|
.toString()
|
|
.toUtf8()
|
|
.constData());
|
|
RefreshLists();
|
|
}
|
|
|
|
void ScriptsTool::on_reloadScripts_clicked()
|
|
{
|
|
QList<QListWidgetItem *> items = ui->scripts->selectedItems();
|
|
for (QListWidgetItem *item : items)
|
|
ReloadScript(item->data(Qt::UserRole)
|
|
.toString()
|
|
.toUtf8()
|
|
.constData());
|
|
|
|
on_scripts_currentRowChanged(ui->scripts->currentRow());
|
|
}
|
|
|
|
void ScriptsTool::on_scriptLog_clicked()
|
|
{
|
|
scriptLogWindow->show();
|
|
scriptLogWindow->raise();
|
|
}
|
|
|
|
void ScriptsTool::on_pythonPathBrowse_clicked()
|
|
{
|
|
QString curPath = ui->pythonPath->text();
|
|
QString newPath = QFileDialog::getExistingDirectory(
|
|
this, ui->pythonPathLabel->text(), curPath);
|
|
|
|
if (newPath.isEmpty())
|
|
return;
|
|
|
|
QByteArray array = newPath.toUtf8();
|
|
const char *path = array.constData();
|
|
|
|
config_t *config = obs_frontend_get_global_config();
|
|
config_set_string(config, "Python", "Path" ARCH_NAME, path);
|
|
|
|
ui->pythonPath->setText(newPath);
|
|
|
|
if (obs_scripting_python_loaded())
|
|
return;
|
|
if (!obs_scripting_load_python(path))
|
|
return;
|
|
|
|
for (OBSScript &script : scriptData->scripts) {
|
|
enum obs_script_lang lang = obs_script_get_lang(script);
|
|
if (lang == OBS_SCRIPT_LANG_PYTHON) {
|
|
obs_script_reload(script);
|
|
}
|
|
}
|
|
|
|
on_scripts_currentRowChanged(ui->scripts->currentRow());
|
|
}
|
|
|
|
void ScriptsTool::on_scripts_currentRowChanged(int row)
|
|
{
|
|
ui->propertiesLayout->removeWidget(propertiesView);
|
|
delete propertiesView;
|
|
|
|
if (row == -1) {
|
|
propertiesView = new QWidget();
|
|
propertiesView->setSizePolicy(QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding);
|
|
ui->propertiesLayout->addWidget(propertiesView);
|
|
ui->description->setText(QString());
|
|
return;
|
|
}
|
|
|
|
QByteArray array =
|
|
ui->scripts->item(row)->data(Qt::UserRole).toString().toUtf8();
|
|
const char *path = array.constData();
|
|
|
|
obs_script_t *script = scriptData->FindScript(path);
|
|
if (!script) {
|
|
propertiesView = nullptr;
|
|
return;
|
|
}
|
|
|
|
OBSData settings = obs_script_get_settings(script);
|
|
obs_data_release(settings);
|
|
|
|
propertiesView = new OBSPropertiesView(
|
|
settings, script,
|
|
(PropertiesReloadCallback)obs_script_get_properties,
|
|
(PropertiesUpdateCallback)obs_script_update);
|
|
ui->propertiesLayout->addWidget(propertiesView);
|
|
ui->description->setText(obs_script_get_description(script));
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- */
|
|
|
|
extern "C" void FreeScripts()
|
|
{
|
|
obs_scripting_unload();
|
|
}
|
|
|
|
static void obs_event(enum obs_frontend_event event, void *)
|
|
{
|
|
if (event == OBS_FRONTEND_EVENT_EXIT) {
|
|
delete scriptData;
|
|
delete scriptsWindow;
|
|
delete scriptLogWindow;
|
|
|
|
} else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP) {
|
|
scriptLogWindow->hide();
|
|
scriptLogWindow->Clear();
|
|
|
|
delete scriptData;
|
|
scriptData = new ScriptData;
|
|
}
|
|
}
|
|
|
|
static void load_script_data(obs_data_t *load_data, bool, void *)
|
|
{
|
|
obs_data_array_t *array = obs_data_get_array(load_data, "scripts-tool");
|
|
|
|
delete scriptData;
|
|
scriptData = new ScriptData;
|
|
|
|
size_t size = obs_data_array_count(array);
|
|
for (size_t i = 0; i < size; i++) {
|
|
obs_data_t *obj = obs_data_array_item(array, i);
|
|
const char *path = obs_data_get_string(obj, "path");
|
|
obs_data_t *settings = obs_data_get_obj(obj, "settings");
|
|
|
|
obs_script_t *script = obs_script_create(path, settings);
|
|
if (script) {
|
|
scriptData->scripts.emplace_back(script);
|
|
}
|
|
|
|
obs_data_release(settings);
|
|
obs_data_release(obj);
|
|
}
|
|
|
|
if (scriptsWindow)
|
|
scriptsWindow->RefreshLists();
|
|
|
|
obs_data_array_release(array);
|
|
}
|
|
|
|
static void save_script_data(obs_data_t *save_data, bool saving, void *)
|
|
{
|
|
if (!saving)
|
|
return;
|
|
|
|
obs_data_array_t *array = obs_data_array_create();
|
|
|
|
for (OBSScript &script : scriptData->scripts) {
|
|
const char *script_path = obs_script_get_path(script);
|
|
obs_data_t *settings = obs_script_save(script);
|
|
|
|
obs_data_t *obj = obs_data_create();
|
|
obs_data_set_string(obj, "path", script_path);
|
|
obs_data_set_obj(obj, "settings", settings);
|
|
obs_data_array_push_back(array, obj);
|
|
obs_data_release(obj);
|
|
|
|
obs_data_release(settings);
|
|
}
|
|
|
|
obs_data_set_array(save_data, "scripts-tool", array);
|
|
obs_data_array_release(array);
|
|
}
|
|
|
|
static void script_log(void *, obs_script_t *script, int log_level,
|
|
const char *message)
|
|
{
|
|
QString qmsg;
|
|
|
|
if (script) {
|
|
qmsg = QStringLiteral("[%1] %2").arg(
|
|
obs_script_get_file(script), message);
|
|
} else {
|
|
qmsg = QStringLiteral("[Unknown Script] %1").arg(message);
|
|
}
|
|
|
|
QMetaObject::invokeMethod(scriptLogWindow, "AddLogMsg",
|
|
Q_ARG(int, log_level), Q_ARG(QString, qmsg));
|
|
}
|
|
|
|
extern "C" void InitScripts()
|
|
{
|
|
scriptLogWindow = new ScriptLogWindow();
|
|
|
|
obs_scripting_load();
|
|
obs_scripting_set_log_callback(script_log, nullptr);
|
|
|
|
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
|
obs_module_text("Scripts"));
|
|
|
|
#if PYTHON_UI
|
|
config_t *config = obs_frontend_get_global_config();
|
|
const char *python_path =
|
|
config_get_string(config, "Python", "Path" ARCH_NAME);
|
|
|
|
if (!obs_scripting_python_loaded() && python_path && *python_path)
|
|
obs_scripting_load_python(python_path);
|
|
#endif
|
|
|
|
scriptData = new ScriptData;
|
|
|
|
auto cb = []() {
|
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
|
|
|
if (!scriptsWindow) {
|
|
scriptsWindow = new ScriptsTool();
|
|
scriptsWindow->show();
|
|
} else {
|
|
scriptsWindow->show();
|
|
scriptsWindow->raise();
|
|
}
|
|
|
|
obs_frontend_pop_ui_translation();
|
|
};
|
|
|
|
obs_frontend_add_save_callback(save_script_data, nullptr);
|
|
obs_frontend_add_preload_callback(load_script_data, nullptr);
|
|
obs_frontend_add_event_callback(obs_event, nullptr);
|
|
|
|
action->connect(action, &QAction::triggered, cb);
|
|
}
|