mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-09-10 02:26:02 -05:00
Restructure library and plugins
The "core" macro conditions and actions have been extracted out to the "base" plugin. The library now mostly contains functionality which is required across all plugins and (e.g. definitions for macro segments). The goal is to reduce the complexity and cross-dependencies and group the source files in a better way. This should relsove the "library limit of 65535 objects exceeded" build issue occuring in some Windows build environments.
This commit is contained in:
16
plugins/base/utils/audio-helpers.cpp
Normal file
16
plugins/base/utils/audio-helpers.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "audio-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void PopulateMonitorTypeSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.audio.monitor.none"));
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.audio.monitor.monitorOnly"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.audio.monitor.both"));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
8
plugins/base/utils/audio-helpers.hpp
Normal file
8
plugins/base/utils/audio-helpers.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void PopulateMonitorTypeSelection(QComboBox *list);
|
||||
|
||||
} // namespace advss
|
||||
537
plugins/base/utils/connection-manager.cpp
Normal file
537
plugins/base/utils/connection-manager.cpp
Normal file
@@ -0,0 +1,537 @@
|
||||
#include "connection-manager.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
Q_DECLARE_METATYPE(advss::Connection *);
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::deque<std::shared_ptr<Item>> connections;
|
||||
static void saveConnections(obs_data_t *obj);
|
||||
static void loadConnections(obs_data_t *obj);
|
||||
static bool setup();
|
||||
static bool setupDone = setup();
|
||||
|
||||
bool setup()
|
||||
{
|
||||
AddSaveStep(saveConnections);
|
||||
AddLoadStep(loadConnections);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void saveConnections(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *connectionArray = obs_data_array_create();
|
||||
for (const auto &c : connections) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
c->Save(array_obj);
|
||||
obs_data_array_push_back(connectionArray, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, "connections", connectionArray);
|
||||
obs_data_array_release(connectionArray);
|
||||
}
|
||||
|
||||
static void loadConnections(obs_data_t *obj)
|
||||
{
|
||||
connections.clear();
|
||||
|
||||
obs_data_array_t *connectionArray =
|
||||
obs_data_get_array(obj, "connections");
|
||||
size_t count = obs_data_array_count(connectionArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(connectionArray, i);
|
||||
auto con = Connection::Create();
|
||||
connections.emplace_back(con);
|
||||
connections.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(connectionArray);
|
||||
}
|
||||
|
||||
Connection::Connection(bool useCustomURI, std::string customURI,
|
||||
std::string name, std::string address, uint64_t port,
|
||||
std::string pass, bool connectOnStart, bool reconnect,
|
||||
int reconnectDelay, bool useOBSWebsocketProtocol)
|
||||
: Item(name),
|
||||
_useCustomURI(useCustomURI),
|
||||
_customURI(customURI),
|
||||
_address(address),
|
||||
_port(port),
|
||||
_password(pass),
|
||||
_connectOnStart(connectOnStart),
|
||||
_reconnect(reconnect),
|
||||
_reconnectDelay(reconnectDelay),
|
||||
_useOBSWSProtocol(useOBSWebsocketProtocol),
|
||||
_client(useOBSWebsocketProtocol)
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(const Connection &other) : Item(other)
|
||||
{
|
||||
_useCustomURI = other._useCustomURI;
|
||||
_customURI = other._customURI;
|
||||
_name = other._name;
|
||||
_address = other._address;
|
||||
_port = other._port;
|
||||
_password = other._password;
|
||||
_connectOnStart = other._connectOnStart;
|
||||
_reconnect = other._reconnect;
|
||||
_reconnectDelay = other._reconnectDelay;
|
||||
_useOBSWSProtocol = other._useOBSWSProtocol;
|
||||
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
|
||||
}
|
||||
|
||||
Connection &Connection::operator=(const Connection &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
_useCustomURI = other._useCustomURI;
|
||||
_customURI = other._customURI;
|
||||
_name = other._name;
|
||||
_address = other._address;
|
||||
_port = other._port;
|
||||
_password = other._password;
|
||||
_connectOnStart = other._connectOnStart;
|
||||
_reconnect = other._reconnect;
|
||||
_reconnectDelay = other._reconnectDelay;
|
||||
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
|
||||
_useOBSWSProtocol = other._useOBSWSProtocol;
|
||||
_client.Disconnect();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
_client.Disconnect();
|
||||
}
|
||||
|
||||
static std::string constructUri(std::string addr, int port)
|
||||
{
|
||||
return "ws://" + addr + ":" + std::to_string(port);
|
||||
}
|
||||
|
||||
std::string Connection::GetURI()
|
||||
{
|
||||
if (_useCustomURI) {
|
||||
return _customURI;
|
||||
}
|
||||
return constructUri(_address, _port);
|
||||
}
|
||||
|
||||
void Connection::Reconnect()
|
||||
{
|
||||
_client.Disconnect();
|
||||
_client.Connect(GetURI(), _password, _reconnect, _reconnectDelay);
|
||||
}
|
||||
|
||||
void Connection::SendMsg(const std::string &msg)
|
||||
{
|
||||
const auto status = _client.GetStatus();
|
||||
if (status == WSConnection::Status::DISCONNECTED) {
|
||||
_client.Connect(GetURI(), _password, _reconnect,
|
||||
_reconnectDelay);
|
||||
blog(LOG_WARNING,
|
||||
"could not send message '%s' (connection to '%s' not established)",
|
||||
msg.c_str(), GetURI().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == WSConnection::Status::AUTHENTICATED) {
|
||||
_client.SendRequest(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Load(obs_data_t *obj)
|
||||
{
|
||||
Item::Load(obj);
|
||||
|
||||
if (obs_data_has_user_value(obj, "version")) {
|
||||
UseOBSWebsocketProtocol(
|
||||
obs_data_get_bool(obj, "useOBSWSProtocol"));
|
||||
} else {
|
||||
// TODO: Remove this fallback in future version
|
||||
_useOBSWSProtocol = true;
|
||||
}
|
||||
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
|
||||
|
||||
_useCustomURI = obs_data_get_bool(obj, "useCustomURI");
|
||||
_customURI = obs_data_get_string(obj, "customURI");
|
||||
_address = obs_data_get_string(obj, "address");
|
||||
_port = obs_data_get_int(obj, "port");
|
||||
_password = obs_data_get_string(obj, "password");
|
||||
_connectOnStart = obs_data_get_bool(obj, "connectOnStart");
|
||||
_reconnect = obs_data_get_bool(obj, "reconnect");
|
||||
_reconnectDelay = obs_data_get_int(obj, "reconnectDelay");
|
||||
|
||||
if (_connectOnStart) {
|
||||
_client.Connect(GetURI(), _password, _reconnect,
|
||||
_reconnectDelay);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Save(obs_data_t *obj) const
|
||||
{
|
||||
Item::Save(obj);
|
||||
obs_data_set_bool(obj, "useCustomURI", _useCustomURI);
|
||||
obs_data_set_string(obj, "customURI", _customURI.c_str());
|
||||
obs_data_set_bool(obj, "useOBSWSProtocol", _useOBSWSProtocol);
|
||||
obs_data_set_string(obj, "address", _address.c_str());
|
||||
obs_data_set_int(obj, "port", _port);
|
||||
obs_data_set_string(obj, "password", _password.c_str());
|
||||
obs_data_set_bool(obj, "connectOnStart", _connectOnStart);
|
||||
obs_data_set_bool(obj, "reconnect", _reconnect);
|
||||
obs_data_set_int(obj, "reconnectDelay", _reconnectDelay);
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
}
|
||||
|
||||
void Connection::UseOBSWebsocketProtocol(bool useOBSWSProtocol)
|
||||
{
|
||||
_useOBSWSProtocol = useOBSWSProtocol;
|
||||
_client.UseOBSWebsocketProtocol(useOBSWSProtocol);
|
||||
}
|
||||
|
||||
Connection *GetConnectionByName(const QString &name)
|
||||
{
|
||||
return GetConnectionByName(name.toStdString());
|
||||
}
|
||||
|
||||
Connection *GetConnectionByName(const std::string &name)
|
||||
{
|
||||
for (auto &con : connections) {
|
||||
if (con->Name() == name) {
|
||||
return dynamic_cast<Connection *>(con.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
|
||||
{
|
||||
for (const auto &c : connections) {
|
||||
if (c->Name() == name) {
|
||||
std::weak_ptr<Connection> wp =
|
||||
std::dynamic_pointer_cast<Connection>(c);
|
||||
return wp;
|
||||
}
|
||||
}
|
||||
return std::weak_ptr<Connection>();
|
||||
}
|
||||
|
||||
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name)
|
||||
{
|
||||
return GetWeakConnectionByName(name.toStdString());
|
||||
}
|
||||
|
||||
std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
|
||||
{
|
||||
auto con = connection.lock();
|
||||
if (!con) {
|
||||
return obs_module_text("AdvSceneSwitcher.connection.invalid");
|
||||
}
|
||||
return con->Name();
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Item>> &GetConnections()
|
||||
{
|
||||
return connections;
|
||||
}
|
||||
|
||||
static bool ConnectionNameAvailable(const QString &name)
|
||||
{
|
||||
return !GetConnectionByName(name);
|
||||
}
|
||||
|
||||
static bool ConnectionNameAvailable(const std::string &name)
|
||||
{
|
||||
return ConnectionNameAvailable(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
|
||||
{
|
||||
Connection &ConnectionSettings = dynamic_cast<Connection &>(settings);
|
||||
return ConnectionSettingsDialog::AskForSettings(parent,
|
||||
ConnectionSettings);
|
||||
}
|
||||
|
||||
ConnectionSelection::ConnectionSelection(QWidget *parent)
|
||||
: ItemSelection(connections, Connection::Create, AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||
"AdvSceneSwitcher.connection.configure", parent)
|
||||
{
|
||||
// Connect to slots
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
|
||||
this, SLOT(RenameItem(const QString &, const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(ConnectionAdded(const QString &)),
|
||||
this, SLOT(AddItem(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)),
|
||||
this, SLOT(RemoveItem(const QString &)));
|
||||
|
||||
// Forward signals
|
||||
QWidget::connect(
|
||||
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
|
||||
window(),
|
||||
SIGNAL(ConnectionRenamed(const QString &, const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
|
||||
SIGNAL(ConnectionAdded(const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
|
||||
SIGNAL(ConnectionRemoved(const QString &)));
|
||||
}
|
||||
|
||||
void ConnectionSelection::SetConnection(const std::string &con)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
if (!!GetConnectionByName(con)) {
|
||||
_selection->setCurrentText(QString::fromStdString(con));
|
||||
} else {
|
||||
_selection->setCurrentIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSelection::SetConnection(
|
||||
const std::weak_ptr<Connection> &connection_)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
auto connection = connection_.lock();
|
||||
if (connection) {
|
||||
SetConnection(connection->Name());
|
||||
} else {
|
||||
_selection->setCurrentIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
const Connection &settings)
|
||||
: ItemSettingsDialog(settings, connections,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
||||
_useCustomURI(new QCheckBox()),
|
||||
_customUri(new QLineEdit()),
|
||||
_address(new QLineEdit()),
|
||||
_port(new QSpinBox()),
|
||||
_password(new QLineEdit()),
|
||||
_showPassword(new QPushButton()),
|
||||
_connectOnStart(new QCheckBox()),
|
||||
_reconnect(new QCheckBox()),
|
||||
_reconnectDelay(new QSpinBox()),
|
||||
_useOBSWSProtocol(new QCheckBox()),
|
||||
_test(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.connection.test"))),
|
||||
_status(new QLabel()),
|
||||
_layout(new QGridLayout())
|
||||
{
|
||||
_port->setMaximum(65535);
|
||||
_showPassword->setMaximumWidth(22);
|
||||
_showPassword->setFlat(true);
|
||||
_showPassword->setStyleSheet(
|
||||
"QPushButton { background-color: transparent; border: 0px }");
|
||||
_reconnectDelay->setMaximum(9999);
|
||||
_reconnectDelay->setSuffix("s");
|
||||
|
||||
_useCustomURI->setChecked(settings._useCustomURI);
|
||||
_customUri->setText(QString::fromStdString(settings._customURI));
|
||||
_address->setText(QString::fromStdString(settings._address));
|
||||
_port->setValue(settings._port);
|
||||
_password->setText(QString::fromStdString(settings._password));
|
||||
_connectOnStart->setChecked(settings._connectOnStart);
|
||||
_reconnect->setChecked(settings._reconnect);
|
||||
_reconnectDelay->setValue(settings._reconnectDelay);
|
||||
_useOBSWSProtocol->setChecked(settings._useOBSWSProtocol);
|
||||
|
||||
QWidget::connect(_useCustomURI, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseCustomURIChanged(int)));
|
||||
QWidget::connect(_useOBSWSProtocol, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(ProtocolChanged(int)));
|
||||
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(ReconnectChanged(int)));
|
||||
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
|
||||
SLOT(ShowPassword()));
|
||||
QWidget::connect(_showPassword, SIGNAL(released()), this,
|
||||
SLOT(HidePassword()));
|
||||
QWidget::connect(_test, SIGNAL(clicked()), this,
|
||||
SLOT(TestConnection()));
|
||||
|
||||
int row = 0;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text("AdvSceneSwitcher.connection.name")),
|
||||
row, 0);
|
||||
QHBoxLayout *nameLayout = new QHBoxLayout;
|
||||
nameLayout->addWidget(_name);
|
||||
nameLayout->addWidget(_nameHint);
|
||||
_layout->addLayout(nameLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.useCustomURI")),
|
||||
row, 0);
|
||||
_layout->addWidget(_useCustomURI, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.customURI")),
|
||||
row, 0);
|
||||
_layout->addWidget(_customUri, row, 1);
|
||||
_customURIRow = row;
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.address")),
|
||||
row, 0);
|
||||
_layout->addWidget(_address, row, 1);
|
||||
_addressRow = row;
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text("AdvSceneSwitcher.connection.port")),
|
||||
row, 0);
|
||||
_layout->addWidget(_port, row, 1);
|
||||
_portRow = row;
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.password")),
|
||||
row, 0);
|
||||
auto passLayout = new QHBoxLayout;
|
||||
passLayout->addWidget(_password);
|
||||
passLayout->addWidget(_showPassword);
|
||||
_layout->addLayout(passLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.connectOnStart")),
|
||||
row, 0);
|
||||
_layout->addWidget(_connectOnStart, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.reconnect")),
|
||||
row, 0);
|
||||
_layout->addWidget(_reconnect, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.reconnectDelay")),
|
||||
row, 0);
|
||||
_layout->addWidget(_reconnectDelay, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.useOBSWebsocketProtocol")),
|
||||
row, 0);
|
||||
_layout->addWidget(_useOBSWSProtocol, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(_test, row, 0);
|
||||
_layout->addWidget(_status, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(_buttonbox, row, 0, 1, -1);
|
||||
setLayout(_layout);
|
||||
|
||||
MinimizeSizeOfColumn(_layout, 0);
|
||||
ReconnectChanged(_reconnect->isChecked());
|
||||
ProtocolChanged(_useOBSWSProtocol->isChecked());
|
||||
HidePassword();
|
||||
UseCustomURIChanged(settings._useCustomURI);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::UseCustomURIChanged(int state)
|
||||
{
|
||||
SetGridLayoutRowVisible(_layout, _addressRow, !state);
|
||||
SetGridLayoutRowVisible(_layout, _portRow, !state);
|
||||
SetGridLayoutRowVisible(_layout, _customURIRow, state);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::ProtocolChanged(int state)
|
||||
{
|
||||
_password->setEnabled(state);
|
||||
_showPassword->setEnabled(state);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::ReconnectChanged(int state)
|
||||
{
|
||||
_reconnectDelay->setEnabled(state);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::SetStatus()
|
||||
{
|
||||
switch (_testConnection.GetStatus()) {
|
||||
case WSConnection::Status::DISCONNECTED:
|
||||
_status->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.status.disconnected"));
|
||||
break;
|
||||
case WSConnection::Status::CONNECTING:
|
||||
_status->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.status.connecting"));
|
||||
break;
|
||||
case WSConnection::Status::CONNECTED:
|
||||
_status->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.status.connected"));
|
||||
break;
|
||||
case WSConnection::Status::AUTHENTICATED:
|
||||
_status->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.status.authenticated"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::ShowPassword()
|
||||
{
|
||||
SetButtonIcon(_showPassword, ":res/images/visible.svg");
|
||||
_password->setEchoMode(QLineEdit::Normal);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::HidePassword()
|
||||
{
|
||||
SetButtonIcon(_showPassword, ":res/images/invisible.svg");
|
||||
_password->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::TestConnection()
|
||||
{
|
||||
_testConnection.UseOBSWebsocketProtocol(_useOBSWSProtocol->isChecked());
|
||||
_testConnection.Disconnect();
|
||||
std::string uri = _useCustomURI->isChecked()
|
||||
? _customUri->text().toStdString()
|
||||
: constructUri(_address->text().toStdString(),
|
||||
_port->value());
|
||||
_testConnection.Connect(uri, _password->text().toStdString(), false);
|
||||
_statusTimer.setInterval(1000);
|
||||
QWidget::connect(&_statusTimer, &QTimer::timeout, this,
|
||||
&ConnectionSettingsDialog::SetStatus);
|
||||
_statusTimer.start();
|
||||
}
|
||||
|
||||
bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
|
||||
Connection &settings)
|
||||
{
|
||||
ConnectionSettingsDialog dialog(parent, settings);
|
||||
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
settings._name = dialog._name->text().toStdString();
|
||||
settings._useCustomURI = dialog._useCustomURI->isChecked();
|
||||
settings._customURI = dialog._customUri->text().toStdString();
|
||||
settings._address = dialog._address->text().toStdString();
|
||||
settings._port = dialog._port->value();
|
||||
settings._password = dialog._password->text().toStdString();
|
||||
settings._connectOnStart = dialog._connectOnStart->isChecked();
|
||||
settings._reconnect = dialog._reconnect->isChecked();
|
||||
settings._reconnectDelay = dialog._reconnectDelay->value();
|
||||
settings.UseOBSWebsocketProtocol(dialog._useOBSWSProtocol->isChecked());
|
||||
settings.Reconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
122
plugins/base/utils/connection-manager.hpp
Normal file
122
plugins/base/utils/connection-manager.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
#include "item-selection-helpers.hpp"
|
||||
#include "websocket-helpers.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
#include <QGridLayout>
|
||||
#include <deque>
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ConnectionSelection;
|
||||
class ConnectionSettingsDialog;
|
||||
|
||||
class Connection : public Item {
|
||||
public:
|
||||
Connection(bool useCustomURI, std::string customURI, std::string name,
|
||||
std::string address, uint64_t port, std::string pass,
|
||||
bool connectOnStart, bool reconnect, int reconnectDelay,
|
||||
bool useOBSWebsocketProtocol);
|
||||
Connection() = default;
|
||||
Connection(const Connection &);
|
||||
Connection &operator=(const Connection &);
|
||||
~Connection();
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
return std::make_shared<Connection>();
|
||||
}
|
||||
|
||||
void Reconnect();
|
||||
void SendMsg(const std::string &msg);
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
std::string GetName() { return _name; }
|
||||
std::vector<std::string> &Events() { return _client.Events(); }
|
||||
bool IsUsingOBSProtocol() { return _useOBSWSProtocol; }
|
||||
|
||||
private:
|
||||
void UseOBSWebsocketProtocol(bool);
|
||||
std::string GetURI();
|
||||
|
||||
bool _useCustomURI = false;
|
||||
std::string _customURI = "ws://localhost:4455";
|
||||
std::string _address = "localhost";
|
||||
uint64_t _port = 4455;
|
||||
std::string _password = "password";
|
||||
bool _connectOnStart = true;
|
||||
bool _reconnect = true;
|
||||
int _reconnectDelay = 3;
|
||||
bool _useOBSWSProtocol = true;
|
||||
|
||||
WSConnection _client;
|
||||
|
||||
friend ConnectionSelection;
|
||||
friend ConnectionSettingsDialog;
|
||||
};
|
||||
|
||||
Connection *GetConnectionByName(const QString &);
|
||||
Connection *GetConnectionByName(const std::string &);
|
||||
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
|
||||
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
|
||||
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
|
||||
std::deque<std::shared_ptr<Item>> &GetConnections();
|
||||
|
||||
class ConnectionSettingsDialog : public ItemSettingsDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConnectionSettingsDialog(QWidget *parent, const Connection &);
|
||||
static bool AskForSettings(QWidget *parent, Connection &settings);
|
||||
|
||||
private slots:
|
||||
void UseCustomURIChanged(int);
|
||||
void ProtocolChanged(int);
|
||||
void ReconnectChanged(int);
|
||||
void ShowPassword();
|
||||
void HidePassword();
|
||||
void SetStatus();
|
||||
void TestConnection();
|
||||
|
||||
private:
|
||||
QCheckBox *_useCustomURI;
|
||||
QLineEdit *_customUri;
|
||||
QLineEdit *_address;
|
||||
QSpinBox *_port;
|
||||
QLineEdit *_password;
|
||||
QPushButton *_showPassword;
|
||||
QCheckBox *_connectOnStart;
|
||||
QCheckBox *_reconnect;
|
||||
QSpinBox *_reconnectDelay;
|
||||
QCheckBox *_useOBSWSProtocol;
|
||||
QPushButton *_test;
|
||||
QLabel *_status;
|
||||
QGridLayout *_layout;
|
||||
|
||||
QTimer _statusTimer;
|
||||
WSConnection _testConnection;
|
||||
|
||||
int _customURIRow = -1;
|
||||
int _addressRow = -1;
|
||||
int _portRow = -1;
|
||||
};
|
||||
|
||||
class ConnectionSelection : public ItemSelection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConnectionSelection(QWidget *parent = 0);
|
||||
void SetConnection(const std::string &);
|
||||
void SetConnection(const std::weak_ptr<Connection> &);
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
22
plugins/base/utils/cursor-helpers.cpp
Normal file
22
plugins/base/utils/cursor-helpers.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "cursor-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
// Implemented in Windows specific implementation file
|
||||
|
||||
#ifndef _WIN32
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseLeftClickTime()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseMiddleClickTime()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseRightClickTime()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace advss
|
||||
10
plugins/base/utils/cursor-helpers.hpp
Normal file
10
plugins/base/utils/cursor-helpers.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseLeftClickTime();
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseMiddleClickTime();
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseRightClickTime();
|
||||
|
||||
} // namespace advss
|
||||
301
plugins/base/utils/filter-selection.cpp
Normal file
301
plugins/base/utils/filter-selection.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "filter-selection.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
constexpr std::string_view typeSaveName = "type";
|
||||
constexpr std::string_view nameSaveName = "name";
|
||||
|
||||
void FilterSelection::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
|
||||
switch (_type) {
|
||||
case Type::SOURCE:
|
||||
obs_data_set_string(data, nameSaveName.data(),
|
||||
_filter ? GetWeakSourceName(_filter).c_str()
|
||||
: _filterName.c_str());
|
||||
break;
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
break;
|
||||
}
|
||||
obs_data_set_string(data, nameSaveName.data(),
|
||||
var->Name().c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_data_set_obj(obj, name, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void FilterSelection::Load(obs_data_t *obj, const SourceSelection &source,
|
||||
const char *name)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, name);
|
||||
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
|
||||
_filterName = obs_data_get_string(data, nameSaveName.data());
|
||||
switch (_type) {
|
||||
case Type::SOURCE:
|
||||
_filter = GetWeakFilterByName(source.GetSource(),
|
||||
_filterName.c_str());
|
||||
break;
|
||||
case Type::VARIABLE:
|
||||
_variable = GetWeakVariableByName(_filterName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obs_data_has_user_value(data, typeSaveName.data())) {
|
||||
LoadFallback(obj, source, name);
|
||||
}
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void FilterSelection::LoadFallback(obs_data_t *obj,
|
||||
const SourceSelection &source,
|
||||
const char *name)
|
||||
{
|
||||
blog(LOG_INFO, "Falling back to Load() without variable support");
|
||||
_type = Type::SOURCE;
|
||||
_filter = GetWeakFilterByName(source.GetSource(), name);
|
||||
_filterName = obs_data_get_string(obj, name);
|
||||
}
|
||||
|
||||
static std::vector<OBSWeakSource> getFiltersOfSource(OBSWeakSource source)
|
||||
{
|
||||
if (!source) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *ptr) {
|
||||
auto filters =
|
||||
reinterpret_cast<std::vector<OBSWeakSource> *>(ptr);
|
||||
OBSWeakSourceAutoRelease weakFilter =
|
||||
obs_source_get_weak_source(filter);
|
||||
filters->emplace_back(weakFilter);
|
||||
};
|
||||
|
||||
std::vector<OBSWeakSource> filters;
|
||||
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
|
||||
obs_source_enum_filters(s, enumFilters, &filters);
|
||||
return filters;
|
||||
}
|
||||
|
||||
std::vector<OBSWeakSource>
|
||||
FilterSelection::GetFilters(const SourceSelection &source) const
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::ALL:
|
||||
return getFiltersOfSource(source.GetSource());
|
||||
case Type::SOURCE:
|
||||
return {GetWeakFilterByName(
|
||||
source.GetSource(),
|
||||
_filter ? GetWeakSourceName(_filter).c_str()
|
||||
: _filterName.c_str())};
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return {};
|
||||
}
|
||||
return {GetWeakFilterByName(source.GetSource(),
|
||||
var->Value().c_str())};
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string FilterSelection::ToString(bool resolve) const
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::ALL:
|
||||
return obs_module_text("AdvSceneSwitcher.filterSelection.all");
|
||||
case Type::SOURCE:
|
||||
return _filter ? GetWeakSourceName(_filter) : _filterName;
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return "";
|
||||
}
|
||||
if (resolve) {
|
||||
return var->Name() + "[" + var->Value() + "]";
|
||||
}
|
||||
return var->Name();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
FilterSelection FilterSelectionWidget::CurrentSelection()
|
||||
{
|
||||
FilterSelection s;
|
||||
const int idx = currentIndex();
|
||||
const auto name = currentText();
|
||||
if (idx == -1 || name.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (idx < _allEndIdx) {
|
||||
s._type = FilterSelection::Type::ALL;
|
||||
} else if (idx < _variablesEndIdx) {
|
||||
s._type = FilterSelection::Type::VARIABLE;
|
||||
s._variable = GetWeakVariableByQString(name);
|
||||
} else if (idx < _filterEndIdx) {
|
||||
s._type = FilterSelection::Type::SOURCE;
|
||||
s._filter = GetWeakSourceByQString(name);
|
||||
s._filterName = name.toStdString();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::Reset()
|
||||
{
|
||||
auto previousSelection = _currentSelection;
|
||||
PopulateSelection();
|
||||
SetFilter(_source, previousSelection);
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::PopulateSelection()
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
clear();
|
||||
|
||||
AddSelectionGroup(
|
||||
this,
|
||||
{obs_module_text("AdvSceneSwitcher.filterSelection.all")});
|
||||
_allEndIdx = count();
|
||||
|
||||
if (_addVariables) {
|
||||
const QStringList variables = GetVariablesNameList();
|
||||
AddSelectionGroup(this, variables);
|
||||
}
|
||||
_variablesEndIdx = count();
|
||||
|
||||
AddSelectionGroup(this, GetFilterNames(_source.GetSource()));
|
||||
_filterEndIdx = count();
|
||||
|
||||
// Remove last separator
|
||||
removeItem(count() - 1);
|
||||
setCurrentIndex(-1);
|
||||
}
|
||||
|
||||
FilterSelectionWidget::FilterSelectionWidget(QWidget *parent,
|
||||
SourceSelectionWidget *sources,
|
||||
bool addVariables)
|
||||
: FilterComboBox(parent,
|
||||
obs_module_text("AdvSceneSwitcher.selectFilter")),
|
||||
_addVariables(addVariables)
|
||||
{
|
||||
setDuplicatesEnabled(true);
|
||||
|
||||
QWidget::connect(this, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SelectionChanged(int)));
|
||||
QWidget::connect(sources,
|
||||
SIGNAL(SourceChanged(const SourceSelection &)), this,
|
||||
SLOT(SourceChanged(const SourceSelection &)));
|
||||
|
||||
// Variables
|
||||
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
|
||||
SLOT(ItemAdd(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
|
||||
this, SLOT(ItemRemove(const QString &)));
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
|
||||
SLOT(ItemRename(const QString &, const QString &)));
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::SetFilter(const SourceSelection &source,
|
||||
const FilterSelection &filter)
|
||||
{
|
||||
_source = source;
|
||||
PopulateSelection();
|
||||
|
||||
int idx = -1;
|
||||
|
||||
switch (filter.GetType()) {
|
||||
case FilterSelection::Type::ALL:
|
||||
idx = findText(obs_module_text(
|
||||
"AdvSceneSwitcher.filterSelection.all"));
|
||||
break;
|
||||
case FilterSelection::Type::SOURCE: {
|
||||
if (_filterEndIdx == -1) {
|
||||
idx = -1;
|
||||
break;
|
||||
}
|
||||
idx = FindIdxInRagne(this, _variablesEndIdx, _filterEndIdx,
|
||||
filter.ToString());
|
||||
break;
|
||||
}
|
||||
case FilterSelection::Type::VARIABLE: {
|
||||
if (_variablesEndIdx == -1) {
|
||||
idx = -1;
|
||||
break;
|
||||
}
|
||||
idx = FindIdxInRagne(this, _selectIdx, _variablesEndIdx,
|
||||
filter.ToString());
|
||||
break;
|
||||
default:
|
||||
idx = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setCurrentIndex(idx);
|
||||
_currentSelection = filter;
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::SourceChanged(const SourceSelection &source)
|
||||
{
|
||||
if (source == _source) {
|
||||
return;
|
||||
}
|
||||
_source = source;
|
||||
_currentSelection = FilterSelection();
|
||||
Reset();
|
||||
emit FilterChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::SelectionChanged(int)
|
||||
{
|
||||
_currentSelection = CurrentSelection();
|
||||
emit FilterChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::ItemAdd(const QString &)
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool FilterSelectionWidget::NameUsed(const QString &name)
|
||||
{
|
||||
return _currentSelection._type == FilterSelection::Type::VARIABLE &&
|
||||
currentText() == name;
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::ItemRemove(const QString &name)
|
||||
{
|
||||
if (NameUsed(name)) {
|
||||
_currentSelection = FilterSelection();
|
||||
emit FilterChanged(_currentSelection);
|
||||
}
|
||||
const QSignalBlocker b(this);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FilterSelectionWidget::ItemRename(const QString &, const QString &)
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
Reset();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
79
plugins/base/utils/filter-selection.hpp
Normal file
79
plugins/base/utils/filter-selection.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include "source-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
class FilterSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "filter") const;
|
||||
void Load(obs_data_t *obj, const SourceSelection &source,
|
||||
const char *name = "filter");
|
||||
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
VARIABLE,
|
||||
ALL,
|
||||
};
|
||||
|
||||
Type GetType() const { return _type; }
|
||||
std::vector<OBSWeakSource>
|
||||
GetFilters(const SourceSelection &source) const;
|
||||
std::string ToString(bool resolve = false) const;
|
||||
|
||||
private:
|
||||
// TODO: Remove in future version
|
||||
// Used for backwards compatibility to older settings versions
|
||||
void LoadFallback(obs_data_t *obj, const SourceSelection &source,
|
||||
const char *name);
|
||||
|
||||
OBSWeakSource _filter;
|
||||
// Storing the name separately as depending on the source selection
|
||||
// the filter source might not be available at the moment.
|
||||
std::string _filterName = "";
|
||||
std::weak_ptr<Variable> _variable;
|
||||
Type _type = Type::SOURCE;
|
||||
friend class FilterSelectionWidget;
|
||||
};
|
||||
|
||||
class FilterSelectionWidget : public FilterComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FilterSelectionWidget(QWidget *parent, SourceSelectionWidget *sources,
|
||||
bool addVariables = true);
|
||||
void SetFilter(const SourceSelection &, const FilterSelection &);
|
||||
|
||||
signals:
|
||||
void FilterChanged(const FilterSelection &);
|
||||
|
||||
public slots:
|
||||
void SourceChanged(const SourceSelection &);
|
||||
private slots:
|
||||
void SelectionChanged(int);
|
||||
void ItemAdd(const QString &name);
|
||||
void ItemRemove(const QString &name);
|
||||
void ItemRename(const QString &oldName, const QString &newName);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
FilterSelection CurrentSelection();
|
||||
void PopulateSelection();
|
||||
bool NameUsed(const QString &name);
|
||||
|
||||
bool _addVariables;
|
||||
FilterSelection _currentSelection;
|
||||
SourceSelection _source;
|
||||
|
||||
// Order of entries
|
||||
// 1. "select entry" entry
|
||||
// 2. All filters
|
||||
// 3. Variables
|
||||
// 4. Regular filters
|
||||
const int _selectIdx = 0;
|
||||
int _allEndIdx = -1;
|
||||
int _variablesEndIdx = -1;
|
||||
int _filterEndIdx = -1;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
127
plugins/base/utils/hotkey-helpers.cpp
Normal file
127
plugins/base/utils/hotkey-helpers.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "hotkey-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::vector<std::weak_ptr<Hotkey>> Hotkey::_registeredHotkeys = {};
|
||||
uint32_t Hotkey::_hotkeyCounter = 1;
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddLoadStep([](obs_data_t *) { Hotkey::ClearAllHotkeys(); });
|
||||
return true;
|
||||
}
|
||||
static bool setupDone = setup();
|
||||
|
||||
std::shared_ptr<Hotkey> Hotkey::GetHotkey(const std::string &description,
|
||||
bool ignoreExistingHotkeys)
|
||||
{
|
||||
// Clean up expired hotkeys
|
||||
auto it = _registeredHotkeys.begin();
|
||||
while (it != _registeredHotkeys.end()) {
|
||||
if (it->expired()) {
|
||||
it = _registeredHotkeys.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing hotkey with same description
|
||||
for (const auto &h : _registeredHotkeys) {
|
||||
auto hotkey = h.lock();
|
||||
if (!hotkey) {
|
||||
continue;
|
||||
}
|
||||
if (hotkey->_description == description) {
|
||||
hotkey->_ignoreExistingHotkeys = ignoreExistingHotkeys;
|
||||
return hotkey;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new hotkey
|
||||
auto hotkey = std::make_shared<Hotkey>(description);
|
||||
_registeredHotkeys.emplace_back(hotkey);
|
||||
hotkey->_ignoreExistingHotkeys = ignoreExistingHotkeys;
|
||||
return hotkey;
|
||||
}
|
||||
|
||||
Hotkey::Hotkey(const std::string &description) : _description(description)
|
||||
{
|
||||
std::string name =
|
||||
"macro_condition_hotkey_" + std::to_string(_hotkeyCounter);
|
||||
_hotkeyID = obs_hotkey_register_frontend(
|
||||
name.c_str(), _description.c_str(), Callback, this);
|
||||
_hotkeyCounter++;
|
||||
}
|
||||
|
||||
bool Hotkey::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_string(obj, "desc", _description.c_str());
|
||||
obs_data_array_t *hotkeyData = obs_hotkey_save(_hotkeyID);
|
||||
obs_data_set_array(obj, "keyBind", hotkeyData);
|
||||
obs_data_array_release(hotkeyData);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Hotkey::Load(obs_data_t *obj)
|
||||
{
|
||||
auto description = obs_data_get_string(obj, "desc");
|
||||
if (!DescriptionAvailable(description)) {
|
||||
return false;
|
||||
}
|
||||
_description = description;
|
||||
obs_data_array_t *hotkeyData = obs_data_get_array(obj, "keyBind");
|
||||
obs_hotkey_load(_hotkeyID, hotkeyData);
|
||||
obs_data_array_release(hotkeyData);
|
||||
obs_hotkey_set_description(_hotkeyID, _description.c_str());
|
||||
_ignoreExistingHotkeys = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Hotkey::~Hotkey()
|
||||
{
|
||||
obs_hotkey_unregister(_hotkeyID);
|
||||
}
|
||||
|
||||
bool Hotkey::UpdateDescription(const std::string &descritpion)
|
||||
{
|
||||
if (!DescriptionAvailable(descritpion)) {
|
||||
return false;
|
||||
}
|
||||
_description = descritpion;
|
||||
obs_hotkey_set_description(_hotkeyID, descritpion.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Hotkey::DescriptionAvailable(const std::string &descritpion)
|
||||
{
|
||||
for (const auto &hotkey : _registeredHotkeys) {
|
||||
auto h = hotkey.lock();
|
||||
if (!h) {
|
||||
continue;
|
||||
}
|
||||
if (!h->_ignoreExistingHotkeys &&
|
||||
h->_description == descritpion) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Hotkey::Callback(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
|
||||
{
|
||||
auto hotkey = static_cast<Hotkey *>(data);
|
||||
if (pressed) {
|
||||
hotkey->_lastPressed =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
hotkey->_pressed = pressed;
|
||||
}
|
||||
|
||||
void Hotkey::ClearAllHotkeys()
|
||||
{
|
||||
_registeredHotkeys.clear();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
169
plugins/base/utils/hotkey-helpers.hpp
Normal file
169
plugins/base/utils/hotkey-helpers.hpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <obs.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class HotkeyType;
|
||||
|
||||
bool CanSimulateKeyPresses();
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration);
|
||||
|
||||
class Hotkey {
|
||||
public:
|
||||
Hotkey(const std::string &description);
|
||||
~Hotkey();
|
||||
|
||||
static std::shared_ptr<Hotkey>
|
||||
GetHotkey(const std::string &description,
|
||||
bool ignoreExistingHotkeys = false);
|
||||
static void ClearAllHotkeys();
|
||||
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
bool GetPressed() const { return _pressed; }
|
||||
auto GetLastPressed() const { return _lastPressed; }
|
||||
std::string GetDescription() const { return _description; }
|
||||
bool UpdateDescription(const std::string &);
|
||||
|
||||
private:
|
||||
static bool DescriptionAvailable(const std::string &);
|
||||
static void Callback(void *data, obs_hotkey_id, obs_hotkey_t *,
|
||||
bool pressed);
|
||||
|
||||
static std::vector<std::weak_ptr<Hotkey>> _registeredHotkeys;
|
||||
static uint32_t _hotkeyCounter;
|
||||
|
||||
std::string _description;
|
||||
obs_hotkey_id _hotkeyID = OBS_INVALID_HOTKEY_ID;
|
||||
bool _pressed = false;
|
||||
std::chrono::high_resolution_clock::time_point _lastPressed{};
|
||||
// When set will not attempt to share settings with existing hotkey
|
||||
bool _ignoreExistingHotkeys = false;
|
||||
};
|
||||
|
||||
enum class HotkeyType {
|
||||
Key_NoKey = 0,
|
||||
|
||||
Key_A,
|
||||
Key_B,
|
||||
Key_C,
|
||||
Key_D,
|
||||
Key_E,
|
||||
Key_F,
|
||||
Key_G,
|
||||
Key_H,
|
||||
Key_I,
|
||||
Key_J,
|
||||
Key_K,
|
||||
Key_L,
|
||||
Key_M,
|
||||
Key_N,
|
||||
Key_O,
|
||||
Key_P,
|
||||
Key_Q,
|
||||
Key_R,
|
||||
Key_S,
|
||||
Key_T,
|
||||
Key_U,
|
||||
Key_V,
|
||||
Key_W,
|
||||
Key_X,
|
||||
Key_Y,
|
||||
Key_Z,
|
||||
|
||||
Key_0,
|
||||
Key_1,
|
||||
Key_2,
|
||||
Key_3,
|
||||
Key_4,
|
||||
Key_5,
|
||||
Key_6,
|
||||
Key_7,
|
||||
Key_8,
|
||||
Key_9,
|
||||
|
||||
Key_F1,
|
||||
Key_F2,
|
||||
Key_F3,
|
||||
Key_F4,
|
||||
Key_F5,
|
||||
Key_F6,
|
||||
Key_F7,
|
||||
Key_F8,
|
||||
Key_F9,
|
||||
Key_F10,
|
||||
Key_F11,
|
||||
Key_F12,
|
||||
Key_F13,
|
||||
Key_F14,
|
||||
Key_F15,
|
||||
Key_F16,
|
||||
Key_F17,
|
||||
Key_F18,
|
||||
Key_F19,
|
||||
Key_F20,
|
||||
Key_F21,
|
||||
Key_F22,
|
||||
Key_F23,
|
||||
Key_F24,
|
||||
|
||||
Key_Escape,
|
||||
Key_Space,
|
||||
Key_Return,
|
||||
Key_Backspace,
|
||||
Key_Tab,
|
||||
|
||||
Key_Shift_L,
|
||||
Key_Shift_R,
|
||||
Key_Control_L,
|
||||
Key_Control_R,
|
||||
Key_Alt_L,
|
||||
Key_Alt_R,
|
||||
Key_Win_L,
|
||||
Key_Win_R,
|
||||
Key_Apps,
|
||||
|
||||
Key_CapsLock,
|
||||
Key_NumLock,
|
||||
Key_ScrollLock,
|
||||
|
||||
Key_PrintScreen,
|
||||
Key_Pause,
|
||||
|
||||
Key_Insert,
|
||||
Key_Delete,
|
||||
Key_PageUP,
|
||||
Key_PageDown,
|
||||
Key_Home,
|
||||
Key_End,
|
||||
|
||||
Key_Left,
|
||||
Key_Right,
|
||||
Key_Up,
|
||||
Key_Down,
|
||||
|
||||
Key_Numpad0,
|
||||
Key_Numpad1,
|
||||
Key_Numpad2,
|
||||
Key_Numpad3,
|
||||
Key_Numpad4,
|
||||
Key_Numpad5,
|
||||
Key_Numpad6,
|
||||
Key_Numpad7,
|
||||
Key_Numpad8,
|
||||
Key_Numpad9,
|
||||
|
||||
Key_NumpadAdd,
|
||||
Key_NumpadSubtract,
|
||||
Key_NumpadMultiply,
|
||||
Key_NumpadDivide,
|
||||
Key_NumpadDecimal,
|
||||
Key_NumpadEnter
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
37
plugins/base/utils/json-helpers.cpp
Normal file
37
plugins/base/utils/json-helpers.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "json-helpers.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QString FormatJsonString(std::string s)
|
||||
{
|
||||
return FormatJsonString(QString::fromStdString(s));
|
||||
}
|
||||
|
||||
QString FormatJsonString(QString json)
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
|
||||
return QString::fromUtf8(doc.toJson(QJsonDocument::Indented));
|
||||
}
|
||||
|
||||
bool MatchJson(const std::string &json1, const std::string &json2,
|
||||
const RegexConfig ®ex)
|
||||
{
|
||||
auto j1 = FormatJsonString(json1).toStdString();
|
||||
auto j2 = FormatJsonString(json2).toStdString();
|
||||
|
||||
if (j1.empty()) {
|
||||
j1 = json1;
|
||||
}
|
||||
if (j2.empty()) {
|
||||
j2 = json2;
|
||||
}
|
||||
|
||||
if (regex.Enabled()) {
|
||||
return regex.Matches(j1, j2);
|
||||
}
|
||||
return j1 == j2;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
13
plugins/base/utils/json-helpers.hpp
Normal file
13
plugins/base/utils/json-helpers.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <string>
|
||||
#include <regex-config.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QString FormatJsonString(std::string);
|
||||
QString FormatJsonString(QString);
|
||||
bool MatchJson(const std::string &json1, const std::string &json2,
|
||||
const RegexConfig ®ex);
|
||||
|
||||
} // namespace advss
|
||||
228
plugins/base/utils/linux/linux.cpp
Normal file
228
plugins/base/utils/linux/linux.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "hotkey-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
// Qt includes must happen before X11 includes
|
||||
#include <QLibrary>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static QLibrary *libXtstHandle = nullptr;
|
||||
typedef int (*keyPressFunc)(Display *, unsigned int, bool, unsigned long);
|
||||
static keyPressFunc pressFunc = nullptr;
|
||||
static bool canSimulateKeyPresses = false;
|
||||
|
||||
static Display *xdisplay = 0;
|
||||
|
||||
static Display *disp()
|
||||
{
|
||||
if (!xdisplay) {
|
||||
xdisplay = XOpenDisplay(NULL);
|
||||
}
|
||||
|
||||
return xdisplay;
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
libXtstHandle = new QLibrary("libXtst", nullptr);
|
||||
pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent");
|
||||
int _;
|
||||
auto display = disp();
|
||||
canSimulateKeyPresses = pressFunc && display &&
|
||||
XQueryExtension(disp(), "XTEST", &_, &_, &_);
|
||||
}
|
||||
|
||||
static void cleanup()
|
||||
{
|
||||
if (libXtstHandle) {
|
||||
delete libXtstHandle;
|
||||
libXtstHandle = nullptr;
|
||||
}
|
||||
if (!xdisplay) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCloseDisplay(xdisplay);
|
||||
xdisplay = nullptr;
|
||||
}
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginInitStep(init);
|
||||
AddPluginCleanupStep(cleanup);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
bool CanSimulateKeyPresses()
|
||||
{
|
||||
return canSimulateKeyPresses;
|
||||
}
|
||||
|
||||
static const std::unordered_map<HotkeyType, long> keyTable = {
|
||||
// Chars
|
||||
{HotkeyType::Key_A, XK_A},
|
||||
{HotkeyType::Key_B, XK_B},
|
||||
{HotkeyType::Key_C, XK_C},
|
||||
{HotkeyType::Key_D, XK_D},
|
||||
{HotkeyType::Key_E, XK_E},
|
||||
{HotkeyType::Key_F, XK_F},
|
||||
{HotkeyType::Key_G, XK_G},
|
||||
{HotkeyType::Key_H, XK_H},
|
||||
{HotkeyType::Key_I, XK_I},
|
||||
{HotkeyType::Key_J, XK_J},
|
||||
{HotkeyType::Key_K, XK_K},
|
||||
{HotkeyType::Key_L, XK_L},
|
||||
{HotkeyType::Key_M, XK_M},
|
||||
{HotkeyType::Key_N, XK_N},
|
||||
{HotkeyType::Key_O, XK_O},
|
||||
{HotkeyType::Key_P, XK_P},
|
||||
{HotkeyType::Key_Q, XK_Q},
|
||||
{HotkeyType::Key_R, XK_R},
|
||||
{HotkeyType::Key_S, XK_S},
|
||||
{HotkeyType::Key_T, XK_T},
|
||||
{HotkeyType::Key_U, XK_U},
|
||||
{HotkeyType::Key_V, XK_V},
|
||||
{HotkeyType::Key_W, XK_W},
|
||||
{HotkeyType::Key_X, XK_X},
|
||||
{HotkeyType::Key_Y, XK_Y},
|
||||
{HotkeyType::Key_Z, XK_Z},
|
||||
|
||||
// Numbers
|
||||
{HotkeyType::Key_0, XK_0},
|
||||
{HotkeyType::Key_1, XK_1},
|
||||
{HotkeyType::Key_2, XK_2},
|
||||
{HotkeyType::Key_3, XK_3},
|
||||
{HotkeyType::Key_4, XK_4},
|
||||
{HotkeyType::Key_5, XK_5},
|
||||
{HotkeyType::Key_6, XK_6},
|
||||
{HotkeyType::Key_7, XK_7},
|
||||
{HotkeyType::Key_8, XK_8},
|
||||
{HotkeyType::Key_9, XK_9},
|
||||
|
||||
{HotkeyType::Key_F1, XK_F1},
|
||||
{HotkeyType::Key_F2, XK_F2},
|
||||
{HotkeyType::Key_F3, XK_F3},
|
||||
{HotkeyType::Key_F4, XK_F4},
|
||||
{HotkeyType::Key_F5, XK_F5},
|
||||
{HotkeyType::Key_F6, XK_F6},
|
||||
{HotkeyType::Key_F7, XK_F7},
|
||||
{HotkeyType::Key_F8, XK_F8},
|
||||
{HotkeyType::Key_F9, XK_F9},
|
||||
{HotkeyType::Key_F10, XK_F10},
|
||||
{HotkeyType::Key_F11, XK_F11},
|
||||
{HotkeyType::Key_F12, XK_F12},
|
||||
{HotkeyType::Key_F13, XK_F13},
|
||||
{HotkeyType::Key_F14, XK_F14},
|
||||
{HotkeyType::Key_F15, XK_F15},
|
||||
{HotkeyType::Key_F16, XK_F16},
|
||||
{HotkeyType::Key_F17, XK_F17},
|
||||
{HotkeyType::Key_F18, XK_F18},
|
||||
{HotkeyType::Key_F19, XK_F19},
|
||||
{HotkeyType::Key_F20, XK_F20},
|
||||
{HotkeyType::Key_F21, XK_F21},
|
||||
{HotkeyType::Key_F22, XK_F22},
|
||||
{HotkeyType::Key_F23, XK_F23},
|
||||
{HotkeyType::Key_F24, XK_F24},
|
||||
|
||||
{HotkeyType::Key_Escape, XK_Escape},
|
||||
{HotkeyType::Key_Space, XK_space},
|
||||
{HotkeyType::Key_Return, XK_Return},
|
||||
{HotkeyType::Key_Backspace, XK_BackSpace},
|
||||
{HotkeyType::Key_Tab, XK_Tab},
|
||||
|
||||
{HotkeyType::Key_Shift_L, XK_Shift_L},
|
||||
{HotkeyType::Key_Shift_R, XK_Shift_R},
|
||||
{HotkeyType::Key_Control_L, XK_Control_L},
|
||||
{HotkeyType::Key_Control_R, XK_Control_R},
|
||||
{HotkeyType::Key_Alt_L, XK_Alt_L},
|
||||
{HotkeyType::Key_Alt_R, XK_Alt_R},
|
||||
{HotkeyType::Key_Win_L, XK_Super_L},
|
||||
{HotkeyType::Key_Win_R, XK_Super_R},
|
||||
{HotkeyType::Key_Apps, XK_Hyper_L},
|
||||
|
||||
{HotkeyType::Key_CapsLock, XK_Caps_Lock},
|
||||
{HotkeyType::Key_NumLock, XK_Num_Lock},
|
||||
{HotkeyType::Key_ScrollLock, XK_Scroll_Lock},
|
||||
|
||||
{HotkeyType::Key_PrintScreen, XK_Print},
|
||||
{HotkeyType::Key_Pause, XK_Pause},
|
||||
|
||||
{HotkeyType::Key_Insert, XK_Insert},
|
||||
{HotkeyType::Key_Delete, XK_Delete},
|
||||
{HotkeyType::Key_PageUP, XK_Page_Up},
|
||||
{HotkeyType::Key_PageDown, XK_Page_Down},
|
||||
{HotkeyType::Key_Home, XK_Home},
|
||||
{HotkeyType::Key_End, XK_End},
|
||||
|
||||
{HotkeyType::Key_Left, XK_Left},
|
||||
{HotkeyType::Key_Up, XK_Up},
|
||||
{HotkeyType::Key_Right, XK_Right},
|
||||
{HotkeyType::Key_Down, XK_Down},
|
||||
|
||||
{HotkeyType::Key_Numpad0, XK_KP_0},
|
||||
{HotkeyType::Key_Numpad1, XK_KP_1},
|
||||
{HotkeyType::Key_Numpad2, XK_KP_2},
|
||||
{HotkeyType::Key_Numpad3, XK_KP_3},
|
||||
{HotkeyType::Key_Numpad4, XK_KP_4},
|
||||
{HotkeyType::Key_Numpad5, XK_KP_5},
|
||||
{HotkeyType::Key_Numpad6, XK_KP_6},
|
||||
{HotkeyType::Key_Numpad7, XK_KP_7},
|
||||
{HotkeyType::Key_Numpad8, XK_KP_8},
|
||||
{HotkeyType::Key_Numpad9, XK_KP_9},
|
||||
|
||||
{HotkeyType::Key_NumpadAdd, XK_KP_Add},
|
||||
{HotkeyType::Key_NumpadSubtract, XK_KP_Subtract},
|
||||
{HotkeyType::Key_NumpadMultiply, XK_KP_Multiply},
|
||||
{HotkeyType::Key_NumpadDivide, XK_KP_Divide},
|
||||
{HotkeyType::Key_NumpadDecimal, XK_KP_Decimal},
|
||||
{HotkeyType::Key_NumpadEnter, XK_KP_Enter},
|
||||
};
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
{
|
||||
if (!canSimulateKeyPresses) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display *display = disp();
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Press keys
|
||||
for (auto &key : keys) {
|
||||
auto it = keyTable.find(key);
|
||||
if (it == keyTable.end()) {
|
||||
continue;
|
||||
}
|
||||
pressFunc(display, XKeysymToKeycode(display, it->second), true,
|
||||
CurrentTime);
|
||||
}
|
||||
XFlush(display);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||
|
||||
// Release keys
|
||||
for (auto &key : keys) {
|
||||
auto it = keyTable.find(key);
|
||||
if (it == keyTable.end()) {
|
||||
continue;
|
||||
}
|
||||
pressFunc(display, XKeysymToKeycode(display, it->second), false,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
XFlush(display);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
50
plugins/base/utils/monitor-helpers.cpp
Normal file
50
plugins/base/utils/monitor-helpers.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "monitor-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include <QString>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QStringList GetMonitorNames()
|
||||
{
|
||||
QStringList monitorNames;
|
||||
QList<QScreen *> screens = QGuiApplication::screens();
|
||||
for (int i = 0; i < screens.size(); i++) {
|
||||
QScreen *screen = screens[i];
|
||||
QRect screenGeometry = screen->geometry();
|
||||
qreal ratio = screen->devicePixelRatio();
|
||||
QString name = "";
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
name = screen->name();
|
||||
#else
|
||||
name = screen->model().simplified();
|
||||
if (name.length() > 1 && name.endsWith("-")) {
|
||||
name.chop(1);
|
||||
}
|
||||
#endif
|
||||
name = name.simplified();
|
||||
|
||||
if (name.length() == 0) {
|
||||
name = QString("%1 %2")
|
||||
.arg(obs_module_text(
|
||||
"AdvSceneSwitcher.action.projector.display"))
|
||||
.arg(QString::number(i + 1));
|
||||
}
|
||||
QString str =
|
||||
QString("%1: %2x%3 @ %4,%5")
|
||||
.arg(name,
|
||||
QString::number(screenGeometry.width() *
|
||||
ratio),
|
||||
QString::number(screenGeometry.height() *
|
||||
ratio),
|
||||
QString::number(screenGeometry.x()),
|
||||
QString::number(screenGeometry.y()));
|
||||
|
||||
monitorNames << str;
|
||||
}
|
||||
return monitorNames;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
8
plugins/base/utils/monitor-helpers.hpp
Normal file
8
plugins/base/utils/monitor-helpers.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <QStringList>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QStringList GetMonitorNames();
|
||||
|
||||
} // namespace advss
|
||||
731
plugins/base/utils/osc-helpers.cpp
Normal file
731
plugins/base/utils/osc-helpers.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
#include "osc-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <QGroupBox>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::unordered_map<size_t, OSCMessageElement::TypeInfo>
|
||||
OSCMessageElement::_typeNames = {
|
||||
{std::variant_npos,
|
||||
{"AdvSceneSwitcher.osc.message.type.none", "-"}},
|
||||
{0, {"AdvSceneSwitcher.osc.message.type.int", "i"}},
|
||||
{1, {"AdvSceneSwitcher.osc.message.type.float", "f"}},
|
||||
{2, {"AdvSceneSwitcher.osc.message.type.string", "s"}},
|
||||
{3, {"AdvSceneSwitcher.osc.message.type.binaryBlob", "b"}},
|
||||
{4, {"AdvSceneSwitcher.osc.message.type.true", "T"}},
|
||||
{5, {"AdvSceneSwitcher.osc.message.type.false", "F"}},
|
||||
{6, {"AdvSceneSwitcher.osc.message.type.infinity", "I"}},
|
||||
{7, {"AdvSceneSwitcher.osc.message.type.null", "N"}},
|
||||
};
|
||||
|
||||
// Based on https://github.com/mhroth/tinyosc
|
||||
struct FillMessageElementBufferVisitor {
|
||||
std::vector<char> &buffer;
|
||||
uint32_t &curOffset;
|
||||
|
||||
bool success = false;
|
||||
|
||||
void operator()(const StringVariable &value)
|
||||
{
|
||||
std::string string = value;
|
||||
int length = (int)strlen(string.c_str());
|
||||
if (curOffset + length >= buffer.size()) {
|
||||
buffer.resize(curOffset + length + 1);
|
||||
}
|
||||
strncpy(buffer.data() + curOffset, string.c_str(),
|
||||
buffer.size() - curOffset - length);
|
||||
curOffset = (curOffset + 4 + length) & ~0x3;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const IntVariable &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
int32_t k = value;
|
||||
*((uint32_t *)(buffer.data() + curOffset)) = htonl(k);
|
||||
curOffset += 4;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const DoubleVariable &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
const float f = value;
|
||||
*((uint32_t *)(buffer.data() + curOffset)) =
|
||||
htonl(*((uint32_t *)&f));
|
||||
curOffset += 4;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const OSCBlob &value)
|
||||
{
|
||||
if (curOffset + 4 > buffer.size()) {
|
||||
buffer.resize(curOffset + 4);
|
||||
}
|
||||
auto blob = value.GetBinary();
|
||||
if (!blob.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (curOffset + 4 + blob->size() > buffer.size()) {
|
||||
buffer.resize(curOffset + 4 + blob->size());
|
||||
}
|
||||
*((uint32_t *)(buffer.data() + curOffset)) =
|
||||
htonl(blob->size());
|
||||
curOffset += 4;
|
||||
memcpy(buffer.data() + curOffset, blob->data(), blob->size());
|
||||
curOffset = (curOffset + 3 + blob->size()) & ~0x3;
|
||||
success = true;
|
||||
}
|
||||
void operator()(const OSCTrue &) { success = true; }
|
||||
void operator()(const OSCFalse &) { success = true; }
|
||||
void operator()(const OSCInfinity &) { success = true; }
|
||||
void operator()(const OSCNull &) { success = true; }
|
||||
};
|
||||
|
||||
OSCBlob::OSCBlob(const std::string &stringRepresentation)
|
||||
: _stringRep(stringRepresentation)
|
||||
{
|
||||
}
|
||||
|
||||
void OSCBlob::SetStringRepresentation(const StringVariable &s)
|
||||
{
|
||||
_stringRep = s;
|
||||
}
|
||||
|
||||
std::string OSCBlob::GetStringRepresentation() const
|
||||
{
|
||||
return _stringRep;
|
||||
}
|
||||
|
||||
std::optional<std::vector<char>> OSCBlob::GetBinary() const
|
||||
{
|
||||
std::vector<char> bytes;
|
||||
std::string hexString = _stringRep;
|
||||
|
||||
for (std::size_t i = 2; i < hexString.size(); i += 4) {
|
||||
try {
|
||||
auto byteString = hexString.substr(i, 2);
|
||||
int value = std::stoi(byteString, nullptr, 16);
|
||||
bytes.push_back(static_cast<char>(value));
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to convert hex \"%s\" to binary: %s",
|
||||
hexString.c_str(), e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void OSCBlob::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
_stringRep.Save(obj, name);
|
||||
}
|
||||
|
||||
void OSCBlob::Load(obs_data_t *obj, const char *name)
|
||||
{
|
||||
_stringRep.Load(obj, name);
|
||||
}
|
||||
|
||||
void OSCTrue::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCFalse::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCInfinity::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
void OSCNull::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_bool(obj, name, true);
|
||||
}
|
||||
|
||||
std::optional<std::vector<char>> OSCMessage::GetBuffer() const
|
||||
{
|
||||
if (std::string(_address).empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<char> buffer(128, 0);
|
||||
uint32_t currentOffset = (uint32_t)strlen(_address.c_str());
|
||||
if (currentOffset > buffer.size()) {
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
strncpy(buffer.data(), _address.c_str(), buffer.size());
|
||||
currentOffset = (currentOffset + 4) & ~0x3;
|
||||
|
||||
std::string typeTags;
|
||||
for (const auto &e : _elements) {
|
||||
typeTags += e.GetTypeTag();
|
||||
}
|
||||
|
||||
buffer.at(currentOffset++) = ',';
|
||||
int length = (int)strlen(typeTags.c_str());
|
||||
if (currentOffset + length >= buffer.size()) {
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
strncpy(buffer.data() + currentOffset, typeTags.c_str(),
|
||||
buffer.size() - currentOffset - length);
|
||||
currentOffset = (currentOffset + 4 + length) & ~0x3;
|
||||
|
||||
for (const auto &e : _elements) {
|
||||
const auto &value = e._value;
|
||||
FillMessageElementBufferVisitor visitor{buffer, currentOffset};
|
||||
std::visit(visitor, value);
|
||||
if (!visitor.success) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(currentOffset);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeTag() const
|
||||
{
|
||||
return GetTypeTag(*this);
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeName() const
|
||||
{
|
||||
return GetTypeName(*this);
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeTag(const OSCMessageElement &element)
|
||||
{
|
||||
return _typeNames.at(element._value.index()).tag;
|
||||
}
|
||||
|
||||
const char *OSCMessageElement::GetTypeName(const OSCMessageElement &element)
|
||||
{
|
||||
return obs_module_text(
|
||||
_typeNames.at(element._value.index()).localizedName);
|
||||
}
|
||||
|
||||
void OSCMessageElement::Save(obs_data_t *obj) const
|
||||
{
|
||||
std::visit(
|
||||
[obj](auto &&arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, StringVariable>) {
|
||||
arg.Save(obj, "strValue");
|
||||
} else if constexpr (std::is_same_v<T, IntVariable>) {
|
||||
arg.Save(obj, "intValue");
|
||||
} else if constexpr (std::is_same_v<T, DoubleVariable>) {
|
||||
arg.Save(obj, "floatValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCBlob>) {
|
||||
arg.Save(obj, "binaryValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCTrue>) {
|
||||
arg.Save(obj, "trueValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCFalse>) {
|
||||
arg.Save(obj, "falseValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCInfinity>) {
|
||||
arg.Save(obj, "infiniteValue");
|
||||
} else if constexpr (std::is_same_v<T, OSCNull>) {
|
||||
arg.Save(obj, "nullValue");
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"cannot save unknown OSCMessageElement");
|
||||
}
|
||||
},
|
||||
_value);
|
||||
}
|
||||
|
||||
void OSCMessageElement::Load(obs_data_t *obj)
|
||||
{
|
||||
if (obs_data_has_user_value(obj, "strValue")) {
|
||||
StringVariable string;
|
||||
string.Load(obj, "strValue");
|
||||
_value = string;
|
||||
} else if (obs_data_has_user_value(obj, "intValue")) {
|
||||
NumberVariable<int> intValue;
|
||||
intValue.Load(obj, "intValue");
|
||||
_value = intValue;
|
||||
} else if (obs_data_has_user_value(obj, "floatValue")) {
|
||||
NumberVariable<double> floatValue;
|
||||
floatValue.Load(obj, "floatValue");
|
||||
_value = floatValue;
|
||||
} else if (obs_data_has_user_value(obj, "binaryValue")) {
|
||||
OSCBlob binValue;
|
||||
binValue.Load(obj, "binaryValue");
|
||||
_value = binValue;
|
||||
} else if (obs_data_has_user_value(obj, "trueValue")) {
|
||||
_value = OSCTrue();
|
||||
} else if (obs_data_has_user_value(obj, "falseValue")) {
|
||||
_value = OSCFalse();
|
||||
} else if (obs_data_has_user_value(obj, "OSCInfinite")) {
|
||||
_value = OSCInfinity();
|
||||
} else if (obs_data_has_user_value(obj, "nullValue")) {
|
||||
_value = OSCNull();
|
||||
} else {
|
||||
blog(LOG_WARNING, "cannot load unknown OSCMessageElement");
|
||||
}
|
||||
}
|
||||
|
||||
std::string OSCMessageElement::ToString() const
|
||||
{
|
||||
return std::visit(
|
||||
[](auto &&arg) -> std::string {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, StringVariable>) {
|
||||
return arg;
|
||||
} else if constexpr (std::is_same_v<T, OSCBlob> ||
|
||||
std::is_same_v<T, OSCTrue> ||
|
||||
std::is_same_v<T, OSCFalse> ||
|
||||
std::is_same_v<T, OSCInfinity> ||
|
||||
std::is_same_v<T, OSCNull>) {
|
||||
return arg.GetStringRepresentation();
|
||||
} else {
|
||||
return std::to_string(arg.GetValue());
|
||||
}
|
||||
},
|
||||
_value);
|
||||
}
|
||||
|
||||
void OSCMessage::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
_address.Save(data, "address");
|
||||
auto elements = obs_data_array_create();
|
||||
for (const auto &e : _elements) {
|
||||
auto array_obj = obs_data_create();
|
||||
e.Save(array_obj);
|
||||
obs_data_array_push_back(elements, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(data, "elements", elements);
|
||||
obs_data_set_obj(obj, "oscMessage", data);
|
||||
obs_data_array_release(elements);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void OSCMessage::Load(obs_data_t *obj)
|
||||
{
|
||||
|
||||
auto data = obs_data_get_obj(obj, "oscMessage");
|
||||
_address.Load(data, "address");
|
||||
_elements.clear();
|
||||
auto elements = obs_data_get_array(data, "elements");
|
||||
size_t count = obs_data_array_count(elements);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto array_obj = obs_data_array_item(elements, i);
|
||||
OSCMessageElement e;
|
||||
e.Load(array_obj);
|
||||
_elements.push_back(e);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(elements);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
std::string OSCMessage::ToString() const
|
||||
{
|
||||
std::string res = "address: " + std::string(_address) + " message: ";
|
||||
for (const auto &e : _elements) {
|
||||
res += "[" + e.ToString() + "]";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
OSCMessageElementEdit::OSCMessageElementEdit(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_type(new QComboBox(this)),
|
||||
_intValue(new VariableSpinBox(this)),
|
||||
_doubleValue(new VariableDoubleSpinBox(this)),
|
||||
_text(new VariableLineEdit(this)),
|
||||
_binaryText(new VariableLineEdit(this))
|
||||
{
|
||||
installEventFilter(this);
|
||||
|
||||
_intValue->setMinimum(INT_MIN);
|
||||
_intValue->setMaximum(INT_MAX);
|
||||
_doubleValue->setMinimum(-9999999999);
|
||||
_doubleValue->setMaximum(9999999999);
|
||||
_doubleValue->setDecimals(10);
|
||||
|
||||
_intValue->hide();
|
||||
_doubleValue->hide();
|
||||
_text->hide();
|
||||
_binaryText->hide();
|
||||
|
||||
for (size_t i = 0; i < OSCMessageElement::_typeNames.size() - 1; i++) {
|
||||
_type->addItem(obs_module_text(
|
||||
OSCMessageElement::_typeNames.at(i).localizedName));
|
||||
}
|
||||
_type->setCurrentIndex(0);
|
||||
|
||||
QWidget::connect(_type, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
QWidget::connect(
|
||||
_doubleValue,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||
this, SLOT(DoubleChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(
|
||||
_intValue,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(IntChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_text, SIGNAL(editingFinished()), this,
|
||||
SLOT(TextChanged()));
|
||||
QWidget::connect(_binaryText, SIGNAL(editingFinished()), this,
|
||||
SLOT(BinaryTextChanged()));
|
||||
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_type, 1);
|
||||
layout->addWidget(_intValue, 4);
|
||||
layout->addWidget(_doubleValue, 4);
|
||||
layout->addWidget(_text, 4);
|
||||
layout->addWidget(_binaryText, 4);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::SetMessageElement(const OSCMessageElement &element)
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
_type->setCurrentText(element.GetTypeName());
|
||||
SetVisibility(element);
|
||||
|
||||
if (std::holds_alternative<StringVariable>(element._value)) {
|
||||
_text->setText(std::get<StringVariable>(element._value));
|
||||
} else if (std::holds_alternative<IntVariable>(element._value)) {
|
||||
_intValue->SetValue(std::get<IntVariable>(element._value));
|
||||
} else if (std::holds_alternative<DoubleVariable>(element._value)) {
|
||||
_doubleValue->SetValue(
|
||||
std::get<DoubleVariable>(element._value));
|
||||
} else if (std::holds_alternative<OSCBlob>(element._value)) {
|
||||
_binaryText->setText(std::get<OSCBlob>(element._value)
|
||||
.GetStringRepresentation());
|
||||
}
|
||||
}
|
||||
|
||||
bool OSCMessageElementEdit::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress ||
|
||||
event->type() == QEvent::MouseButtonDblClick) {
|
||||
emit Focussed();
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
void OSCMessageElementEdit::showEvent(QShowEvent *event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
|
||||
QWidgetList childWidgets = findChildren<QWidget *>();
|
||||
for (QWidget *childWidget : childWidgets) {
|
||||
childWidget->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::DoubleChanged(const DoubleVariable &value)
|
||||
{
|
||||
emit ElementValueChanged(OSCMessageElement(value));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::IntChanged(const IntVariable &value)
|
||||
{
|
||||
emit ElementValueChanged(OSCMessageElement(value));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::TextChanged()
|
||||
{
|
||||
emit ElementValueChanged(StringVariable(_text->text().toStdString()));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::BinaryTextChanged()
|
||||
{
|
||||
emit ElementValueChanged(OSCBlob(_binaryText->text().toStdString()));
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::SetVisibility(const OSCMessageElement &element)
|
||||
{
|
||||
_intValue->hide();
|
||||
_doubleValue->hide();
|
||||
_text->hide();
|
||||
_binaryText->hide();
|
||||
|
||||
if (std::holds_alternative<StringVariable>(element._value)) {
|
||||
_text->show();
|
||||
} else if (std::holds_alternative<IntVariable>(element._value)) {
|
||||
_intValue->show();
|
||||
} else if (std::holds_alternative<DoubleVariable>(element._value)) {
|
||||
_doubleValue->show();
|
||||
} else if (std::holds_alternative<OSCBlob>(element._value)) {
|
||||
_binaryText->show();
|
||||
}
|
||||
}
|
||||
|
||||
void OSCMessageElementEdit::TypeChanged(int idx)
|
||||
{
|
||||
OSCMessageElement element;
|
||||
if (idx == 0) {
|
||||
element = OSCMessageElement(IntVariable(0));
|
||||
} else if (idx == 1) {
|
||||
element = OSCMessageElement(DoubleVariable(0.0));
|
||||
} else if (idx == 2) {
|
||||
element = OSCMessageElement("value");
|
||||
} else if (idx == 3) {
|
||||
element = OSCMessageElement(OSCBlob("\\x00\\x01\\x02\\x03"));
|
||||
} else if (idx == 4) {
|
||||
element = OSCMessageElement(OSCTrue());
|
||||
} else if (idx == 5) {
|
||||
element = OSCMessageElement(OSCFalse());
|
||||
} else if (idx == 6) {
|
||||
element = OSCMessageElement(OSCInfinity());
|
||||
} else if (idx == 7) {
|
||||
element = OSCMessageElement(OSCNull());
|
||||
}
|
||||
SetVisibility(element);
|
||||
SetMessageElement(element);
|
||||
emit ElementValueChanged(element);
|
||||
}
|
||||
|
||||
OSCMessageEdit::OSCMessageEdit(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_address(new VariableLineEdit(this)),
|
||||
_elements(new QListWidget()),
|
||||
_add(new QPushButton()),
|
||||
_remove(new QPushButton()),
|
||||
_up(new QPushButton()),
|
||||
_down(new QPushButton())
|
||||
{
|
||||
_elements->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_elements->setAutoScroll(false);
|
||||
|
||||
_add->setMaximumWidth(22);
|
||||
_add->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_add->setFlat(true);
|
||||
_remove->setMaximumWidth(22);
|
||||
_remove->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_remove->setFlat(true);
|
||||
_up->setMaximumWidth(22);
|
||||
_up->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_up->setFlat(true);
|
||||
_down->setMaximumWidth(22);
|
||||
_down->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_down->setFlat(true);
|
||||
|
||||
QWidget::connect(_address, SIGNAL(editingFinished()), this,
|
||||
SLOT(AddressChanged()));
|
||||
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));
|
||||
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove()));
|
||||
QWidget::connect(_up, SIGNAL(clicked()), this, SLOT(Up()));
|
||||
QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down()));
|
||||
|
||||
auto controlsLayout = new QHBoxLayout();
|
||||
controlsLayout->addWidget(_add);
|
||||
controlsLayout->addWidget(_remove);
|
||||
QFrame *line = new QFrame();
|
||||
line->setFrameShape(QFrame::VLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
controlsLayout->addWidget(line);
|
||||
controlsLayout->addWidget(_up);
|
||||
controlsLayout->addWidget(_down);
|
||||
controlsLayout->addStretch();
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_address);
|
||||
layout->addWidget(_elements);
|
||||
layout->addLayout(controlsLayout);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::InsertElement(const OSCMessageElement &element)
|
||||
{
|
||||
auto item = new QListWidgetItem(_elements);
|
||||
_elements->addItem(item);
|
||||
auto elementEdit = new OSCMessageElementEdit(this);
|
||||
elementEdit->SetMessageElement(element);
|
||||
item->setSizeHint(elementEdit->minimumSizeHint());
|
||||
_elements->setItemWidget(item, elementEdit);
|
||||
QWidget::connect(elementEdit,
|
||||
SIGNAL(ElementValueChanged(const OSCMessageElement &)),
|
||||
this,
|
||||
SLOT(ElementValueChanged(const OSCMessageElement &)));
|
||||
QWidget::connect(elementEdit, SIGNAL(Focussed()), this,
|
||||
SLOT(ElementFocussed()));
|
||||
_currentSelection._elements.push_back(element);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::SetMessage(const OSCMessage &message)
|
||||
{
|
||||
_address->setText(message._address);
|
||||
for (const auto &element : message._elements) {
|
||||
InsertElement(element);
|
||||
}
|
||||
_currentSelection = message;
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::AddressChanged()
|
||||
{
|
||||
_currentSelection._address = _address->text().toStdString();
|
||||
emit MessageChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Add()
|
||||
{
|
||||
OSCMessageElement element;
|
||||
InsertElement(element);
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Remove()
|
||||
{
|
||||
auto item = _elements->currentItem();
|
||||
int idx = _elements->currentRow();
|
||||
if (!item || idx == -1) {
|
||||
return;
|
||||
}
|
||||
delete item;
|
||||
_currentSelection._elements.erase(_currentSelection._elements.begin() +
|
||||
idx);
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
static bool moveUp(QListWidget *list)
|
||||
{
|
||||
int index = list->currentRow();
|
||||
if (index == -1 || index == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QWidget *row = list->itemWidget(list->currentItem());
|
||||
QListWidgetItem *itemN = list->currentItem()->clone();
|
||||
|
||||
list->insertItem(index - 1, itemN);
|
||||
list->setItemWidget(itemN, row);
|
||||
|
||||
list->takeItem(index + 1);
|
||||
list->setCurrentRow(index - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Up()
|
||||
{
|
||||
int idx = _elements->currentRow();
|
||||
if (!moveUp(_elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(_currentSelection._elements.begin() + idx,
|
||||
_currentSelection._elements.begin() + idx - 1);
|
||||
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
static bool moveDown(QListWidget *list)
|
||||
{
|
||||
int index = list->currentRow();
|
||||
if (index == -1 || index == list->count() - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QWidget *row = list->itemWidget(list->currentItem());
|
||||
QListWidgetItem *itemN = list->currentItem()->clone();
|
||||
|
||||
list->insertItem(index + 2, itemN);
|
||||
list->setItemWidget(itemN, row);
|
||||
|
||||
list->takeItem(index);
|
||||
list->setCurrentRow(index + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OSCMessageEdit::Down()
|
||||
{
|
||||
int idx = _elements->currentRow();
|
||||
if (!moveDown(_elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(_currentSelection._elements.begin() + idx,
|
||||
_currentSelection._elements.begin() + idx + 1);
|
||||
|
||||
emit MessageChanged(_currentSelection);
|
||||
SetWidgetSize();
|
||||
}
|
||||
|
||||
static QListWidgetItem *getItemFromWidget(QListWidget *list, QWidget *widget)
|
||||
{
|
||||
for (int i = 0; i < list->count(); i++) {
|
||||
auto item = list->item(i);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
auto itemWidget = list->itemWidget(item);
|
||||
if (itemWidget == widget) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int OSCMessageEdit::GetIndexOfSignal()
|
||||
{
|
||||
auto sender = this->sender();
|
||||
if (!sender) {
|
||||
return -1;
|
||||
}
|
||||
auto widget = qobject_cast<QWidget *>(sender);
|
||||
if (!widget) {
|
||||
return -1;
|
||||
}
|
||||
return _elements->row(getItemFromWidget(_elements, widget));
|
||||
}
|
||||
|
||||
void OSCMessageEdit::ElementFocussed()
|
||||
{
|
||||
int idx = GetIndexOfSignal();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_elements->setCurrentRow(idx);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::ElementValueChanged(const OSCMessageElement &element)
|
||||
{
|
||||
int idx = GetIndexOfSignal();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSelection._elements.at(idx) = element;
|
||||
_elements->setCurrentRow(idx);
|
||||
emit MessageChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void OSCMessageEdit::SetWidgetSize()
|
||||
{
|
||||
SetHeightToContentHeight(_elements);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
168
plugins/base/utils/osc-helpers.hpp
Normal file
168
plugins/base/utils/osc-helpers.hpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include "variable-string.hpp"
|
||||
#include "variable-number.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include <QListWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class OSCBlob {
|
||||
public:
|
||||
OSCBlob() = default;
|
||||
OSCBlob(const std::string &stringRepresentation);
|
||||
void SetStringRepresentation(const StringVariable &);
|
||||
std::string GetStringRepresentation() const;
|
||||
std::optional<std::vector<char>> GetBinary() const;
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
void Load(obs_data_t *obj, const char *name);
|
||||
|
||||
private:
|
||||
StringVariable _stringRep;
|
||||
};
|
||||
|
||||
class OSCTrue {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "true"; }
|
||||
};
|
||||
|
||||
class OSCFalse {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "false"; }
|
||||
};
|
||||
|
||||
class OSCInfinity {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "infinity"; }
|
||||
};
|
||||
|
||||
class OSCNull {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name) const;
|
||||
std::string GetStringRepresentation() const { return "null"; }
|
||||
};
|
||||
|
||||
class OSCMessageElement {
|
||||
public:
|
||||
OSCMessageElement() = default;
|
||||
OSCMessageElement(const StringVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const IntVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const DoubleVariable &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCBlob &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCTrue &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCFalse &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCInfinity &v) : _value(v) {}
|
||||
OSCMessageElement(const OSCNull &v) : _value(v) {}
|
||||
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
std::string ToString() const;
|
||||
const char *GetTypeName() const;
|
||||
const char *GetTypeTag() const;
|
||||
static const char *GetTypeName(const OSCMessageElement &);
|
||||
static const char *GetTypeTag(const OSCMessageElement &);
|
||||
|
||||
private:
|
||||
struct TypeInfo {
|
||||
const char *localizedName, *tag;
|
||||
};
|
||||
static std::unordered_map<size_t, TypeInfo> _typeNames;
|
||||
|
||||
std::variant<IntVariable, DoubleVariable, StringVariable, OSCBlob,
|
||||
OSCTrue, OSCFalse, OSCInfinity, OSCNull>
|
||||
_value;
|
||||
|
||||
friend class OSCMessage;
|
||||
friend class OSCMessageElementEdit;
|
||||
};
|
||||
|
||||
class OSCMessage {
|
||||
public:
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
std::string ToString() const;
|
||||
std::optional<std::vector<char>> GetBuffer() const;
|
||||
|
||||
private:
|
||||
StringVariable _address = "/address";
|
||||
std::vector<OSCMessageElement> _elements = {
|
||||
OSCMessageElement("example"),
|
||||
OSCMessageElement(IntVariable(3))};
|
||||
|
||||
friend class OSCMessageEdit;
|
||||
};
|
||||
|
||||
class OSCMessageElementEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OSCMessageElementEdit(QWidget *);
|
||||
void SetMessageElement(const OSCMessageElement &);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void TypeChanged(int);
|
||||
void DoubleChanged(const NumberVariable<double> &value);
|
||||
void IntChanged(const NumberVariable<int> &value);
|
||||
void TextChanged();
|
||||
void BinaryTextChanged();
|
||||
|
||||
signals:
|
||||
void ElementValueChanged(const OSCMessageElement &);
|
||||
void Focussed();
|
||||
|
||||
private:
|
||||
void SetVisibility(const OSCMessageElement &);
|
||||
|
||||
QComboBox *_type;
|
||||
VariableSpinBox *_intValue;
|
||||
VariableDoubleSpinBox *_doubleValue;
|
||||
VariableLineEdit *_text;
|
||||
VariableLineEdit *_binaryText;
|
||||
};
|
||||
|
||||
class OSCMessageEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OSCMessageEdit(QWidget *);
|
||||
void SetMessage(const OSCMessage &);
|
||||
|
||||
private slots:
|
||||
void ElementValueChanged(const OSCMessageElement &);
|
||||
void ElementFocussed();
|
||||
void AddressChanged();
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
|
||||
signals:
|
||||
void MessageChanged(const OSCMessage &);
|
||||
|
||||
private:
|
||||
void InsertElement(const OSCMessageElement &);
|
||||
void SetWidgetSize();
|
||||
int GetIndexOfSignal();
|
||||
|
||||
VariableLineEdit *_address;
|
||||
QListWidget *_elements;
|
||||
QPushButton *_add;
|
||||
QPushButton *_remove;
|
||||
QPushButton *_up;
|
||||
QPushButton *_down;
|
||||
|
||||
OSCMessage _currentSelection;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
18
plugins/base/utils/osx/osx.mm
Normal file
18
plugins/base/utils/osx/osx.mm
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "hotkey-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool canSimulateKeyPresses = false;
|
||||
|
||||
bool CanSimulateKeyPresses()
|
||||
{
|
||||
return canSimulateKeyPresses;
|
||||
}
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
{
|
||||
// Not supported on MacOS
|
||||
return;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
188
plugins/base/utils/process-config.cpp
Normal file
188
plugins/base/utils/process-config.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "process-config.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QFileDialog>
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool ProcessConfig::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
_path.Save(data, "path");
|
||||
_workingDirectory.Save(data, "workingDirectory");
|
||||
_args.Save(data, "args", "arg");
|
||||
obs_data_set_obj(obj, "processConfig", data);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessConfig::Load(obs_data_t *obj)
|
||||
{
|
||||
// TODO: Remove this fallback in a future version
|
||||
if (!obs_data_has_user_value(obj, "processConfig")) {
|
||||
_path = obs_data_get_string(obj, "path");
|
||||
_workingDirectory =
|
||||
obs_data_get_string(obj, "workingDirectory");
|
||||
_args.Load(obj, "args", "arg");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto data = obs_data_get_obj(obj, "processConfig");
|
||||
_path.Load(data, "path");
|
||||
_workingDirectory.Load(data, "workingDirectory");
|
||||
_args.Load(data, "args", "arg");
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList ProcessConfig::Args() const
|
||||
{
|
||||
QStringList result;
|
||||
for (auto &arg : _args) {
|
||||
result << QString::fromStdString(arg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ProcessConfig::StartProcessDetached() const
|
||||
{
|
||||
return QProcess::startDetached(QString::fromStdString(Path()), Args(),
|
||||
QString::fromStdString(WorkingDir()));
|
||||
}
|
||||
|
||||
std::variant<int, ProcessConfig::ProcStartError>
|
||||
ProcessConfig::StartProcessAndWait(int timeout) const
|
||||
{
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(QString::fromStdString(WorkingDir()));
|
||||
process.start(QString::fromStdString(Path()), Args());
|
||||
vblog(LOG_INFO, "run \"%s\" with a timeout of %d ms", Path().c_str(),
|
||||
timeout);
|
||||
|
||||
if (!process.waitForFinished(timeout)) {
|
||||
if (process.error() == QProcess::FailedToStart) {
|
||||
vblog(LOG_INFO, "failed to start \"%s\"!",
|
||||
Path().c_str());
|
||||
return ProcStartError::FAILED_TO_START;
|
||||
}
|
||||
vblog(LOG_INFO,
|
||||
"timeout while running \"%s\"\nAttempting to kill process!",
|
||||
Path().c_str());
|
||||
process.kill();
|
||||
process.waitForFinished();
|
||||
return ProcStartError::TIMEOUT;
|
||||
}
|
||||
|
||||
if (process.exitStatus() == QProcess::NormalExit) {
|
||||
return process.exitCode();
|
||||
}
|
||||
vblog(LOG_INFO, "process \"%s\" crashed!", Path().c_str());
|
||||
return ProcStartError::CRASH;
|
||||
}
|
||||
|
||||
ProcessConfigEdit::ProcessConfigEdit(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_filePath(new FileSelection()),
|
||||
_showAdvancedSettings(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.process.showAdvanced"))),
|
||||
_advancedSettingsLayout(new QVBoxLayout()),
|
||||
_argList(new StringListEdit(
|
||||
this, obs_module_text("AdvSceneSwitcher.process.addArgument"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.process.addArgumentDescription"),
|
||||
4096, true)),
|
||||
_workingDirectory(new FileSelection(FileSelection::Type::FOLDER))
|
||||
{
|
||||
_advancedSettingsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
QWidget::connect(_showAdvancedSettings, SIGNAL(clicked()), this,
|
||||
SLOT(ShowAdvancedSettingsClicked()));
|
||||
QWidget::connect(_argList,
|
||||
SIGNAL(StringListChanged(const StringList &)), this,
|
||||
SLOT(ArgsChanged(const StringList &)));
|
||||
QWidget::connect(_workingDirectory,
|
||||
SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(WorkingDirectoryChanged(const QString &)));
|
||||
|
||||
auto *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{filePath}}", _filePath},
|
||||
{"{{workingDirectory}}", _workingDirectory},
|
||||
{"{{advancedSettings}}", _showAdvancedSettings},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.process.entry"),
|
||||
entryLayout, widgetPlaceholders, false);
|
||||
|
||||
auto workingDirectoryLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.process.entry.workingDirectory"),
|
||||
workingDirectoryLayout, widgetPlaceholders, false);
|
||||
|
||||
_advancedSettingsLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.process.arguments")));
|
||||
_advancedSettingsLayout->addWidget(_argList);
|
||||
_advancedSettingsLayout->addLayout(workingDirectoryLayout);
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addLayout(_advancedSettingsLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::SetProcessConfig(const ProcessConfig &conf)
|
||||
{
|
||||
_conf = conf;
|
||||
_filePath->SetPath(conf._path);
|
||||
_argList->SetStringList(conf._args);
|
||||
_workingDirectory->SetPath(conf._workingDirectory);
|
||||
ShowAdvancedSettings(
|
||||
!_conf._args.empty() ||
|
||||
!_conf._workingDirectory.UnresolvedValue().empty());
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::PathChanged(const QString &text)
|
||||
{
|
||||
_conf._path = text.toStdString();
|
||||
emit ConfigChanged(_conf);
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::ShowAdvancedSettingsClicked()
|
||||
{
|
||||
ShowAdvancedSettings(true);
|
||||
emit ConfigChanged(_conf); // Just to make sure resizing is handled
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::WorkingDirectoryChanged(const QString &path)
|
||||
{
|
||||
_conf._workingDirectory = path.toStdString();
|
||||
emit ConfigChanged(_conf);
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::ArgsChanged(const StringList &args)
|
||||
{
|
||||
_conf._args = args;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
emit ConfigChanged(_conf);
|
||||
}
|
||||
|
||||
void ProcessConfigEdit::ShowAdvancedSettings(bool showAdvancedSettings)
|
||||
{
|
||||
SetLayoutVisible(_advancedSettingsLayout, showAdvancedSettings);
|
||||
_showAdvancedSettings->setVisible(!showAdvancedSettings);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
if (showAdvancedSettings) {
|
||||
emit AdvancedSettingsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
73
plugins/base/utils/process-config.hpp
Normal file
73
plugins/base/utils/process-config.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include "file-selection.hpp"
|
||||
#include "string-list.hpp"
|
||||
|
||||
#include <obs-data.h>
|
||||
#include <obs-module-helper.hpp>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
#include <variant>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ProcessConfig {
|
||||
public:
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
std::string Path() const { return _path; }
|
||||
std::string UnresolvedPath() const { return _path.UnresolvedValue(); }
|
||||
std::string WorkingDir() const { return _workingDirectory; }
|
||||
QStringList Args() const; // Resolves variables
|
||||
|
||||
enum class ProcStartError {
|
||||
NONE,
|
||||
FAILED_TO_START,
|
||||
TIMEOUT,
|
||||
CRASH,
|
||||
};
|
||||
|
||||
std::variant<int, ProcStartError>
|
||||
StartProcessAndWait(int timeoutInMs) const;
|
||||
bool StartProcessDetached() const;
|
||||
|
||||
private:
|
||||
StringVariable _path = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
StringVariable _workingDirectory = "";
|
||||
StringList _args;
|
||||
|
||||
friend class ProcessConfigEdit;
|
||||
};
|
||||
|
||||
class ProcessConfigEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProcessConfigEdit(QWidget *parent);
|
||||
void SetProcessConfig(const ProcessConfig &);
|
||||
|
||||
private slots:
|
||||
void PathChanged(const QString &);
|
||||
void ShowAdvancedSettingsClicked();
|
||||
void WorkingDirectoryChanged(const QString &);
|
||||
void ArgsChanged(const StringList &);
|
||||
signals:
|
||||
void ConfigChanged(const ProcessConfig &);
|
||||
void AdvancedSettingsEnabled();
|
||||
|
||||
private:
|
||||
void ShowAdvancedSettings(bool);
|
||||
|
||||
ProcessConfig _conf;
|
||||
|
||||
FileSelection *_filePath;
|
||||
QPushButton *_showAdvancedSettings;
|
||||
QVBoxLayout *_advancedSettingsLayout;
|
||||
StringListEdit *_argList;
|
||||
FileSelection *_workingDirectory;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
33
plugins/base/utils/profile-helpers.cpp
Normal file
33
plugins/base/utils/profile-helpers.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "profile-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void PopulateProfileSelection(QComboBox *box)
|
||||
{
|
||||
auto profiles = obs_frontend_get_profiles();
|
||||
char **temp = profiles;
|
||||
while (*temp) {
|
||||
const char *name = *temp;
|
||||
box->addItem(name);
|
||||
temp++;
|
||||
}
|
||||
bfree(profiles);
|
||||
box->model()->sort(0);
|
||||
AddSelectionEntry(
|
||||
box, obs_module_text("AdvSceneSwitcher.selectProfile"), false);
|
||||
box->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
std::string GetPathInProfileDir(const char *filePath)
|
||||
{
|
||||
auto path = obs_frontend_get_current_profile_path();
|
||||
std::string result(path);
|
||||
bfree(path);
|
||||
return result + "/" + filePath;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
10
plugins/base/utils/profile-helpers.hpp
Normal file
10
plugins/base/utils/profile-helpers.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <QComboBox>
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void PopulateProfileSelection(QComboBox *list);
|
||||
std::string GetPathInProfileDir(const char *filePath);
|
||||
|
||||
} // namespace advss
|
||||
1062
plugins/base/utils/scene-item-selection.cpp
Normal file
1062
plugins/base/utils/scene-item-selection.cpp
Normal file
File diff suppressed because it is too large
Load Diff
157
plugins/base/utils/scene-item-selection.hpp
Normal file
157
plugins/base/utils/scene-item-selection.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
#include "scene-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-string.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SceneItemSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj,
|
||||
const char *name = "sceneItemSelection") const;
|
||||
void Load(obs_data_t *obj, const char *name = "sceneItemSelection");
|
||||
// TODO: Remove in future version
|
||||
void Load(obs_data_t *obj, const char *name, const char *targetName,
|
||||
const char *idxName);
|
||||
|
||||
enum class Type {
|
||||
SOURCE_NAME,
|
||||
VARIABLE_NAME,
|
||||
SOURCE_NAME_PATTERN = 10,
|
||||
SOURCE_GROUP = 20,
|
||||
INDEX = 30,
|
||||
INDEX_RANGE = 40,
|
||||
ALL = 50,
|
||||
};
|
||||
|
||||
// Name conflicts can happen if multiple instances of a given source are
|
||||
// present in a given scene.
|
||||
//
|
||||
// If that is the case, the user has the option to specify if all / any
|
||||
// or a given individual instance of the source based on its index shall
|
||||
// be returned.
|
||||
enum class NameConflictSelection {
|
||||
ALL,
|
||||
ANY,
|
||||
INDIVIDUAL,
|
||||
};
|
||||
|
||||
Type GetType() const { return _type; }
|
||||
NameConflictSelection GetIndexType() const;
|
||||
std::vector<OBSSceneItem> GetSceneItems(const SceneSelection &) const;
|
||||
std::string ToString(bool resolve = false) const;
|
||||
|
||||
// TODO: Remove in future version
|
||||
//
|
||||
// Only exists to enable backwards compatabilty with older versions of
|
||||
// scene item visibility action
|
||||
void SetSourceTypeSelection(const char *);
|
||||
|
||||
private:
|
||||
std::vector<OBSSceneItem>
|
||||
GetSceneItemsByName(const SceneSelection &) const;
|
||||
std::vector<OBSSceneItem>
|
||||
GetSceneItemsByPattern(const SceneSelection &) const;
|
||||
std::vector<OBSSceneItem>
|
||||
GetSceneItemsByGroup(const SceneSelection &) const;
|
||||
std::vector<OBSSceneItem>
|
||||
GetSceneItemsByIdx(const SceneSelection &) const;
|
||||
std::vector<OBSSceneItem>
|
||||
GetAllSceneItems(const SceneSelection &) const;
|
||||
|
||||
void ReduceBadedOnIndexSelection(std::vector<OBSSceneItem> &) const;
|
||||
|
||||
Type _type = Type::SOURCE_NAME;
|
||||
|
||||
OBSWeakSource _source;
|
||||
std::weak_ptr<Variable> _variable;
|
||||
IntVariable _index = 1;
|
||||
IntVariable _indexEnd = 1;
|
||||
NameConflictSelection _nameConflictSelectionType =
|
||||
NameConflictSelection::ALL;
|
||||
int _nameConflictSelectionIndex = 0;
|
||||
std::string _sourceGroup;
|
||||
StringVariable _pattern = ".*";
|
||||
RegexConfig _regex = RegexConfig(true);
|
||||
friend class SceneItemSelectionWidget;
|
||||
};
|
||||
|
||||
class SceneItemSelectionWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Placeholder { ALL, ANY };
|
||||
SceneItemSelectionWidget(QWidget *parent, bool addPlaceholder = true,
|
||||
Placeholder placeholderType = Placeholder::ALL);
|
||||
void SetSceneItem(const SceneItemSelection &);
|
||||
void SetScene(const SceneSelection &);
|
||||
void ShowPlaceholder(bool);
|
||||
void SetPlaceholderType(Placeholder t, bool resetSelection = true);
|
||||
signals:
|
||||
void SceneItemChanged(const SceneItemSelection &);
|
||||
|
||||
private slots:
|
||||
// Name based
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void VariableChanged(const QString &);
|
||||
void SourceChanged(int);
|
||||
void NameConflictIndexChanged(int);
|
||||
void PatternChanged();
|
||||
void RegexChanged(const RegexConfig &);
|
||||
|
||||
// Source group based
|
||||
void SourceGroupChanged(const QString &);
|
||||
|
||||
// Index based
|
||||
void IndexChanged(const NumberVariable<int> &);
|
||||
void IndexEndChanged(const NumberVariable<int> &);
|
||||
|
||||
void ChangeType();
|
||||
|
||||
private:
|
||||
void ClearWidgets();
|
||||
void PopulateItemSelection();
|
||||
void SetupNameConflictIdxSelection(int);
|
||||
void SetNameConflictVisibility();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QHBoxLayout *_controlsLayout;
|
||||
FilterComboBox *_sources;
|
||||
VariableSelection *_variables;
|
||||
QComboBox *_nameConflictIndex;
|
||||
VariableSpinBox *_index;
|
||||
VariableSpinBox *_indexEnd;
|
||||
QComboBox *_sourceGroups;
|
||||
VariableLineEdit *_pattern;
|
||||
RegexConfigWidget *_regex;
|
||||
QPushButton *_changeType;
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _currentSelection;
|
||||
bool _hasPlaceholderEntry = false;
|
||||
Placeholder _placeholder = Placeholder::ALL;
|
||||
|
||||
bool _showTypeSelection = false;
|
||||
};
|
||||
|
||||
class SceneItemTypeSelection : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SceneItemTypeSelection(QWidget *parent,
|
||||
const SceneItemSelection::Type &type);
|
||||
static bool AskForSettings(QWidget *parent,
|
||||
SceneItemSelection::Type &type);
|
||||
|
||||
private:
|
||||
QComboBox *_typeSelection;
|
||||
QDialogButtonBox *_buttonbox;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
78
plugins/base/utils/scene-item-transform-helpers.cpp
Normal file
78
plugins/base/utils/scene-item-transform-helpers.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "scene-item-transform-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::pair<double, double> getSceneItemSize(obs_scene_item *item)
|
||||
{
|
||||
std::pair<double, double> size;
|
||||
obs_source_t *source = obs_sceneitem_get_source(item);
|
||||
size.first = double(obs_source_get_width(source));
|
||||
size.second = double(obs_source_get_height(source));
|
||||
return size;
|
||||
}
|
||||
|
||||
std::string GetSceneItemTransform(obs_scene_item *item)
|
||||
{
|
||||
struct obs_transform_info info;
|
||||
struct obs_sceneitem_crop crop;
|
||||
obs_sceneitem_get_info(item, &info);
|
||||
obs_sceneitem_get_crop(item, &crop);
|
||||
auto size = getSceneItemSize(item);
|
||||
|
||||
auto data = obs_data_create();
|
||||
SaveTransformState(data, info, crop);
|
||||
obs_data_t *obj = obs_data_create();
|
||||
obs_data_set_double(obj, "width", size.first * info.scale.x);
|
||||
obs_data_set_double(obj, "height", size.second * info.scale.y);
|
||||
obs_data_set_obj(data, "size", obj);
|
||||
obs_data_release(obj);
|
||||
auto json = std::string(obs_data_get_json(data));
|
||||
obs_data_release(data);
|
||||
return json;
|
||||
}
|
||||
|
||||
void LoadTransformState(obs_data_t *obj, struct obs_transform_info &info,
|
||||
struct obs_sceneitem_crop &crop)
|
||||
{
|
||||
obs_data_get_vec2(obj, "pos", &info.pos);
|
||||
obs_data_get_vec2(obj, "scale", &info.scale);
|
||||
info.rot = (float)obs_data_get_double(obj, "rot");
|
||||
info.alignment = (uint32_t)obs_data_get_int(obj, "alignment");
|
||||
info.bounds_type =
|
||||
(enum obs_bounds_type)obs_data_get_int(obj, "bounds_type");
|
||||
info.bounds_alignment =
|
||||
(uint32_t)obs_data_get_int(obj, "bounds_alignment");
|
||||
obs_data_get_vec2(obj, "bounds", &info.bounds);
|
||||
crop.top = (int)obs_data_get_int(obj, "top");
|
||||
crop.bottom = (int)obs_data_get_int(obj, "bottom");
|
||||
crop.left = (int)obs_data_get_int(obj, "left");
|
||||
crop.right = (int)obs_data_get_int(obj, "right");
|
||||
}
|
||||
|
||||
bool SaveTransformState(obs_data_t *obj, const struct obs_transform_info &info,
|
||||
const struct obs_sceneitem_crop &crop)
|
||||
{
|
||||
struct vec2 pos = info.pos;
|
||||
struct vec2 scale = info.scale;
|
||||
float rot = info.rot;
|
||||
uint32_t alignment = info.alignment;
|
||||
uint32_t bounds_type = info.bounds_type;
|
||||
uint32_t bounds_alignment = info.bounds_alignment;
|
||||
struct vec2 bounds = info.bounds;
|
||||
|
||||
obs_data_set_vec2(obj, "pos", &pos);
|
||||
obs_data_set_vec2(obj, "scale", &scale);
|
||||
obs_data_set_double(obj, "rot", rot);
|
||||
obs_data_set_int(obj, "alignment", alignment);
|
||||
obs_data_set_int(obj, "bounds_type", bounds_type);
|
||||
obs_data_set_vec2(obj, "bounds", &bounds);
|
||||
obs_data_set_int(obj, "bounds_alignment", bounds_alignment);
|
||||
obs_data_set_int(obj, "top", crop.top);
|
||||
obs_data_set_int(obj, "bottom", crop.bottom);
|
||||
obs_data_set_int(obj, "left", crop.left);
|
||||
obs_data_set_int(obj, "right", crop.right);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
13
plugins/base/utils/scene-item-transform-helpers.hpp
Normal file
13
plugins/base/utils/scene-item-transform-helpers.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void LoadTransformState(obs_data_t *obj, struct obs_transform_info &info,
|
||||
struct obs_sceneitem_crop &crop);
|
||||
bool SaveTransformState(obs_data_t *obj, const struct obs_transform_info &info,
|
||||
const struct obs_sceneitem_crop &crop);
|
||||
std::string GetSceneItemTransform(obs_scene_item *item);
|
||||
|
||||
} // namespace advss
|
||||
235
plugins/base/utils/source-setting.cpp
Normal file
235
plugins/base/utils/source-setting.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#include "source-setting.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "math-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
Q_DECLARE_METATYPE(advss::SourceSetting);
|
||||
|
||||
namespace advss {
|
||||
|
||||
SourceSetting::SourceSetting(const std::string &id,
|
||||
const std::string &description,
|
||||
const std::string &longDescription)
|
||||
: _id(id), _description(description), _longDescription(longDescription)
|
||||
{
|
||||
}
|
||||
|
||||
bool SourceSetting::Save(obs_data_t *obj) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "id", _id.c_str());
|
||||
obs_data_set_string(data, "description", _description.c_str());
|
||||
obs_data_set_obj(obj, "sourceSetting", data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceSetting::Load(obs_data_t *obj)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_get_obj(obj, "sourceSetting");
|
||||
_id = obs_data_get_string(data, "id");
|
||||
_description = obs_data_get_string(data, "description");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceSetting::operator==(const SourceSetting &other) const
|
||||
{
|
||||
return _id == other._id;
|
||||
}
|
||||
|
||||
std::vector<SourceSetting> GetSoruceSettings(obs_source_t *source)
|
||||
{
|
||||
auto properties = obs_source_properties(source);
|
||||
if (!properties) {
|
||||
return {};
|
||||
}
|
||||
std::vector<SourceSetting> settings;
|
||||
auto it = obs_properties_first(properties);
|
||||
do {
|
||||
if (!it) {
|
||||
continue;
|
||||
}
|
||||
auto name = obs_property_name(it);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
auto description = obs_property_description(it);
|
||||
if (!description) {
|
||||
continue;
|
||||
}
|
||||
auto longDescription = obs_property_long_description(it);
|
||||
SourceSetting setting(name, description,
|
||||
longDescription ? longDescription : "");
|
||||
settings.emplace_back(setting);
|
||||
} while (obs_property_next(&it));
|
||||
obs_properties_destroy(properties);
|
||||
return settings;
|
||||
}
|
||||
|
||||
std::string GetSourceSettingValue(const OBSWeakSource &ws,
|
||||
const SourceSetting &setting)
|
||||
{
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(ws);
|
||||
OBSDataAutoRelease data = obs_source_get_settings(source);
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
OBSDataAutoRelease dataWithDefaults = obs_data_get_defaults(data);
|
||||
obs_data_apply(dataWithDefaults, data);
|
||||
auto json = obs_data_get_json(dataWithDefaults);
|
||||
if (!json) {
|
||||
return "";
|
||||
}
|
||||
auto value = GetJsonField(json, setting.GetID());
|
||||
return value.value_or("");
|
||||
}
|
||||
|
||||
void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
|
||||
const std::string &value)
|
||||
{
|
||||
auto id = setting.GetID();
|
||||
OBSDataAutoRelease data = obs_source_get_settings(source);
|
||||
auto item = obs_data_item_byname(data, id.c_str());
|
||||
auto type = obs_data_item_gettype(item);
|
||||
switch (type) {
|
||||
case OBS_DATA_NULL:
|
||||
break;
|
||||
case OBS_DATA_STRING:
|
||||
obs_data_set_string(data, id.c_str(), value.c_str());
|
||||
break;
|
||||
case OBS_DATA_NUMBER: {
|
||||
auto type = obs_data_item_numtype(item);
|
||||
switch (type) {
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
case OBS_DATA_NUM_INT: {
|
||||
auto intValue = GetInt(value);
|
||||
if (intValue.has_value()) {
|
||||
obs_data_set_int(data, id.c_str(), *intValue);
|
||||
break;
|
||||
}
|
||||
auto doubleValue = GetDouble(value);
|
||||
if (doubleValue.has_value()) {
|
||||
obs_data_set_int(data, id.c_str(),
|
||||
*doubleValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBS_DATA_NUM_DOUBLE: {
|
||||
auto numValue = GetDouble(value);
|
||||
if (!numValue.has_value()) {
|
||||
break;
|
||||
}
|
||||
obs_data_set_int(data, id.c_str(), *numValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBS_DATA_BOOLEAN:
|
||||
obs_data_set_bool(data, id.c_str(), value == "true");
|
||||
break;
|
||||
case OBS_DATA_OBJECT: {
|
||||
OBSDataAutoRelease json =
|
||||
obs_data_create_from_json(value.c_str());
|
||||
obs_data_set_obj(data, id.c_str(), json);
|
||||
break;
|
||||
}
|
||||
case OBS_DATA_ARRAY: {
|
||||
auto jsonStr = obs_data_get_json(data);
|
||||
if (!jsonStr) {
|
||||
break;
|
||||
}
|
||||
std::string resultJsonStr;
|
||||
try {
|
||||
auto json = nlohmann::json::parse(jsonStr);
|
||||
auto jsonArray = nlohmann::json::parse(value);
|
||||
json[id] = jsonArray;
|
||||
resultJsonStr = json.dump();
|
||||
|
||||
} catch (const nlohmann::json::exception &) {
|
||||
break;
|
||||
}
|
||||
OBSDataAutoRelease newData =
|
||||
obs_data_create_from_json(resultJsonStr.c_str());
|
||||
if (!newData) {
|
||||
break;
|
||||
}
|
||||
obs_data_clear(data);
|
||||
obs_data_apply(data, newData);
|
||||
}
|
||||
}
|
||||
obs_data_item_release(&item);
|
||||
obs_source_update(source, data);
|
||||
}
|
||||
|
||||
SourceSettingSelection::SourceSettingSelection(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_settings(new FilterComboBox(
|
||||
this, obs_module_text("AdvSceneSwitcher.selectSetting"))),
|
||||
_tooltip(new QLabel())
|
||||
{
|
||||
QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
QIcon icon(path);
|
||||
QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
_tooltip->setPixmap(pixmap);
|
||||
_tooltip->hide();
|
||||
|
||||
QWidget::connect(_settings, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SelectionIdxChanged(int)));
|
||||
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_settings);
|
||||
layout->addWidget(_tooltip);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void SourceSettingSelection::SetSource(const OBSWeakSource &source)
|
||||
{
|
||||
_settings->clear();
|
||||
Populate(source);
|
||||
}
|
||||
|
||||
void SourceSettingSelection::SetSetting(const SourceSetting &setting)
|
||||
{
|
||||
QVariant variant;
|
||||
variant.setValue(setting);
|
||||
_settings->setCurrentIndex(_settings->findData(variant));
|
||||
}
|
||||
|
||||
void SourceSettingSelection::SelectionIdxChanged(int idx)
|
||||
{
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
auto setting = _settings->itemData(idx).value<SourceSetting>();
|
||||
if (setting._longDescription.empty()) {
|
||||
_tooltip->setToolTip("");
|
||||
_tooltip->hide();
|
||||
} else {
|
||||
_tooltip->setToolTip(
|
||||
QString::fromStdString(setting._longDescription));
|
||||
_tooltip->show();
|
||||
}
|
||||
emit SelectionChanged(setting);
|
||||
}
|
||||
|
||||
void SourceSettingSelection::Populate(const OBSWeakSource &source)
|
||||
{
|
||||
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
|
||||
auto settings = GetSoruceSettings(s);
|
||||
for (const auto &setting : settings) {
|
||||
QVariant variant;
|
||||
variant.setValue(setting);
|
||||
_settings->addItem(QString::fromStdString(setting._description),
|
||||
variant);
|
||||
}
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
57
plugins/base/utils/source-setting.hpp
Normal file
57
plugins/base/utils/source-setting.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "filter-combo-box.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SourceSetting {
|
||||
public:
|
||||
SourceSetting() = default;
|
||||
SourceSetting(const std::string &id, const std::string &description,
|
||||
const std::string &longDescription = "");
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetID() const { return _id; }
|
||||
EXPORT bool operator==(const SourceSetting &other) const;
|
||||
|
||||
private:
|
||||
std::string _id = "";
|
||||
std::string _description = "";
|
||||
std::string _longDescription = "";
|
||||
|
||||
friend class SourceSettingSelection;
|
||||
};
|
||||
|
||||
std::vector<SourceSetting> GetSoruceSettings(obs_source_t *source);
|
||||
std::string GetSourceSettingValue(const OBSWeakSource &source,
|
||||
const SourceSetting &setting);
|
||||
void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
|
||||
const std::string &value);
|
||||
|
||||
class SourceSettingSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SourceSettingSelection(QWidget *parent = nullptr);
|
||||
void SetSource(const OBSWeakSource &);
|
||||
void SetSetting(const SourceSetting &);
|
||||
|
||||
private slots:
|
||||
void SelectionIdxChanged(int);
|
||||
|
||||
signals:
|
||||
void SelectionChanged(const SourceSetting &);
|
||||
|
||||
private:
|
||||
void Populate(const OBSWeakSource &);
|
||||
|
||||
FilterComboBox *_settings;
|
||||
QLabel *_tooltip;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
50
plugins/base/utils/source-settings-helpers.cpp
Normal file
50
plugins/base/utils/source-settings-helpers.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "source-settings-helpers.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::string GetSourceSettings(OBSWeakSource ws)
|
||||
{
|
||||
if (!ws) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string settings;
|
||||
auto s = obs_weak_source_get_source(ws);
|
||||
obs_data_t *data = obs_source_get_settings(s);
|
||||
auto json = obs_data_get_json(data);
|
||||
if (json) {
|
||||
settings = json;
|
||||
}
|
||||
obs_data_release(data);
|
||||
obs_source_release(s);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
void SetSourceSettings(obs_source_t *s, const std::string &settings)
|
||||
{
|
||||
if (settings.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_data_t *data = obs_data_create_from_json(settings.c_str());
|
||||
if (!data) {
|
||||
blog(LOG_WARNING, "invalid source settings provided: \n%s",
|
||||
settings.c_str());
|
||||
return;
|
||||
}
|
||||
obs_source_update(s, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
bool CompareSourceSettings(const OBSWeakSource &source,
|
||||
const std::string &settings,
|
||||
const RegexConfig ®ex)
|
||||
{
|
||||
std::string currentSettings = GetSourceSettings(source);
|
||||
return MatchJson(currentSettings, settings, regex);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
14
plugins/base/utils/source-settings-helpers.hpp
Normal file
14
plugins/base/utils/source-settings-helpers.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <obs.hpp>
|
||||
#include <string>
|
||||
#include <regex-config.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::string GetSourceSettings(OBSWeakSource ws);
|
||||
void SetSourceSettings(obs_source_t *s, const std::string &settings);
|
||||
bool CompareSourceSettings(const OBSWeakSource &source,
|
||||
const std::string &settings,
|
||||
const RegexConfig ®ex);
|
||||
|
||||
} // namespace advss
|
||||
230
plugins/base/utils/string-list.cpp
Normal file
230
plugins/base/utils/string-list.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "string-list.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QLayout>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool StringList::Save(obs_data_t *obj, const char *name,
|
||||
const char *elementName) const
|
||||
{
|
||||
obs_data_array_t *strings = obs_data_array_create();
|
||||
for (auto &string : *this) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
string.Save(array_obj, elementName);
|
||||
obs_data_array_push_back(strings, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, name, strings);
|
||||
obs_data_array_release(strings);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringList::Load(obs_data_t *obj, const char *name,
|
||||
const char *elementName)
|
||||
{
|
||||
clear();
|
||||
obs_data_array_t *strings = obs_data_get_array(obj, name);
|
||||
size_t count = obs_data_array_count(strings);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(strings, i);
|
||||
StringVariable string;
|
||||
string.Load(array_obj, elementName);
|
||||
*this << string;
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(strings);
|
||||
return true;
|
||||
}
|
||||
|
||||
StringListEdit::StringListEdit(QWidget *parent, const QString &addString,
|
||||
const QString &addStringDescription,
|
||||
int maxStringSize, bool allowEmtpy)
|
||||
: QWidget(parent),
|
||||
_list(new QListWidget()),
|
||||
_add(new QPushButton()),
|
||||
_remove(new QPushButton()),
|
||||
_up(new QPushButton()),
|
||||
_down(new QPushButton()),
|
||||
_addString(addString),
|
||||
_addStringDescription(addStringDescription),
|
||||
_maxStringSize(maxStringSize),
|
||||
_allowEmpty(allowEmtpy)
|
||||
{
|
||||
_add->setMaximumWidth(22);
|
||||
_add->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_add->setFlat(true);
|
||||
_remove->setMaximumWidth(22);
|
||||
_remove->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_remove->setFlat(true);
|
||||
_up->setMaximumWidth(22);
|
||||
_up->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_up->setFlat(true);
|
||||
_down->setMaximumWidth(22);
|
||||
_down->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_down->setFlat(true);
|
||||
|
||||
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));
|
||||
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove()));
|
||||
QWidget::connect(_up, SIGNAL(clicked()), this, SLOT(Up()));
|
||||
QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down()));
|
||||
QWidget::connect(_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
|
||||
this, SLOT(Clicked(QListWidgetItem *)));
|
||||
|
||||
auto controlLayout = new QHBoxLayout;
|
||||
controlLayout->setContentsMargins(0, 0, 0, 0);
|
||||
controlLayout->addWidget(_add);
|
||||
controlLayout->addWidget(_remove);
|
||||
QFrame *line = new QFrame();
|
||||
line->setFrameShape(QFrame::VLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
controlLayout->addWidget(line);
|
||||
controlLayout->addWidget(_up);
|
||||
controlLayout->addWidget(_down);
|
||||
controlLayout->addStretch();
|
||||
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->addWidget(_list);
|
||||
mainLayout->addLayout(controlLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
void StringListEdit::SetStringList(const StringList &list)
|
||||
{
|
||||
_stringList = list;
|
||||
_list->clear();
|
||||
for (const auto &string : list) {
|
||||
QListWidgetItem *item = new QListWidgetItem(
|
||||
QString::fromStdString(string.UnresolvedValue()),
|
||||
_list);
|
||||
item->setData(Qt::UserRole, string);
|
||||
}
|
||||
SetListSize();
|
||||
}
|
||||
|
||||
void StringListEdit::SetMaxStringSize(int size)
|
||||
{
|
||||
_maxStringSize = size;
|
||||
}
|
||||
|
||||
void StringListEdit::showEvent(QShowEvent *e)
|
||||
{
|
||||
QWidget::showEvent(e);
|
||||
// This is necessary as the list viewport might not be updated yet
|
||||
// while the list was hidden.
|
||||
// Thus, previous calls to SetListSize() might not have resized the
|
||||
// widget correctly, for example due to not regarding the horizontal
|
||||
// scrollbar yet.
|
||||
SetListSize();
|
||||
}
|
||||
|
||||
void StringListEdit::Add()
|
||||
{
|
||||
std::string name;
|
||||
bool accepted = AdvSSNameDialog::AskForName(this, _addString,
|
||||
_addStringDescription, name,
|
||||
"", _maxStringSize, false);
|
||||
|
||||
if (!accepted || (!_allowEmpty && name.empty())) {
|
||||
return;
|
||||
}
|
||||
StringVariable string = name;
|
||||
QVariant v = QVariant::fromValue(string);
|
||||
QListWidgetItem *item = new QListWidgetItem(
|
||||
QString::fromStdString(string.UnresolvedValue()), _list);
|
||||
item->setData(Qt::UserRole, string);
|
||||
|
||||
_stringList << string;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { SetListSize(); });
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void StringListEdit::Remove()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_stringList.removeAt(idx);
|
||||
|
||||
QListWidgetItem *item = _list->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
delete item;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { SetListSize(); });
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void StringListEdit::Up()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx != -1 && idx != 0) {
|
||||
_list->insertItem(idx - 1, _list->takeItem(idx));
|
||||
_list->setCurrentRow(idx - 1);
|
||||
|
||||
_stringList.move(idx, idx - 1);
|
||||
}
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void StringListEdit::Down()
|
||||
{
|
||||
int idx = _list->currentRow();
|
||||
if (idx != -1 && idx != _list->count() - 1) {
|
||||
_list->insertItem(idx + 1, _list->takeItem(idx));
|
||||
_list->setCurrentRow(idx + 1);
|
||||
|
||||
_stringList.move(idx, idx + 1);
|
||||
}
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void StringListEdit::Clicked(QListWidgetItem *item)
|
||||
{
|
||||
std::string name;
|
||||
bool accepted = AdvSSNameDialog::AskForName(this, _addString,
|
||||
_addStringDescription, name,
|
||||
item->text(),
|
||||
_maxStringSize, false);
|
||||
|
||||
if (!accepted || (!_allowEmpty && name.empty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringVariable string = name;
|
||||
QVariant v = QVariant::fromValue(string);
|
||||
item->setText(QString::fromStdString(string.UnresolvedValue()));
|
||||
item->setData(Qt::UserRole, string);
|
||||
int idx = _list->currentRow();
|
||||
_stringList[idx] = string;
|
||||
|
||||
// Delay resizing to make sure the list viewport was already updated
|
||||
QTimer::singleShot(0, this, [this]() { SetListSize(); });
|
||||
|
||||
StringListChanged(_stringList);
|
||||
}
|
||||
|
||||
void StringListEdit::SetListSize()
|
||||
{
|
||||
SetHeightToContentHeight(_list);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
62
plugins/base/utils/string-list.hpp
Normal file
62
plugins/base/utils/string-list.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include "variable-string.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QStringList>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class StringList : public QList<StringVariable> {
|
||||
public:
|
||||
bool Save(obs_data_t *obj, const char *name,
|
||||
const char *elementName = "string") const;
|
||||
bool Load(obs_data_t *obj, const char *name,
|
||||
const char *elementName = "string");
|
||||
|
||||
friend class StringListEdit;
|
||||
};
|
||||
|
||||
class StringListEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StringListEdit(QWidget *parent, const QString &addString = "",
|
||||
const QString &addStringDescription = "",
|
||||
int maxStringSize = 170, bool allowEmtpy = false);
|
||||
void SetStringList(const StringList &);
|
||||
void SetMaxStringSize(int);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
|
||||
private slots:
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
void Clicked(QListWidgetItem *);
|
||||
signals:
|
||||
void StringListChanged(const StringList &);
|
||||
|
||||
private:
|
||||
void SetListSize();
|
||||
|
||||
StringList _stringList;
|
||||
|
||||
QListWidget *_list;
|
||||
QPushButton *_add;
|
||||
QPushButton *_remove;
|
||||
QPushButton *_up;
|
||||
QPushButton *_down;
|
||||
|
||||
QString _addString;
|
||||
QString _addStringDescription;
|
||||
int _maxStringSize = 170;
|
||||
bool _allowEmpty = false;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
41
plugins/base/utils/striped-frame.cpp
Normal file
41
plugins/base/utils/striped-frame.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "striped-frame.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace advss {
|
||||
|
||||
constexpr QColor stripeColor = QColor(255, 255, 255, 50);
|
||||
constexpr int stripeWidth = 30;
|
||||
constexpr int stripeSpacing = 30;
|
||||
constexpr qreal sqrtOf2 = 1.41421356237309;
|
||||
constexpr int painterVerticalOffset = stripeWidth / sqrtOf2 + 1;
|
||||
constexpr int rotatedPainterSpacing =
|
||||
(stripeWidth + stripeSpacing) / sqrtOf2 + 1;
|
||||
constexpr qreal horizontalMoveStep = stripeWidth + stripeSpacing;
|
||||
constexpr qreal verticalMoveStep = -horizontalMoveStep;
|
||||
|
||||
void StripedFrame::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QFrame::paintEvent(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(stripeColor);
|
||||
|
||||
const int biggerSideSize = width() > height() ? width() : height();
|
||||
const int numStripes = biggerSideSize / rotatedPainterSpacing + 1;
|
||||
|
||||
painter.translate(0, -painterVerticalOffset);
|
||||
painter.rotate(45);
|
||||
|
||||
const qreal diagonalLength =
|
||||
std::sqrt(width() * width() + height() * height());
|
||||
const qreal stripeLength = diagonalLength * 2;
|
||||
|
||||
for (int i = 0; i < numStripes; ++i) {
|
||||
painter.drawRect(0, 0, stripeWidth, stripeLength);
|
||||
painter.translate(horizontalMoveStep, verticalMoveStep);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
15
plugins/base/utils/striped-frame.hpp
Normal file
15
plugins/base/utils/striped-frame.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <QFrame>
|
||||
#include <QPainter>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class StripedFrame : public QFrame {
|
||||
public:
|
||||
StripedFrame(QWidget *parent = nullptr) : QFrame(parent) {}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
15
plugins/base/utils/text-helpers.cpp
Normal file
15
plugins/base/utils/text-helpers.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "text-helpers.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QString EscapeForRegex(const QString &s)
|
||||
{
|
||||
static std::regex specialChars{R"([-[\]{}()*+?.,\^$|#\s])"};
|
||||
std::string input = s.toStdString();
|
||||
return QString::fromStdString(
|
||||
std::regex_replace(input, specialChars, R"(\$&)"));
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
8
plugins/base/utils/text-helpers.hpp
Normal file
8
plugins/base/utils/text-helpers.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
|
||||
namespace advss {
|
||||
|
||||
QString EscapeForRegex(const QString &s);
|
||||
|
||||
} // namespace advss
|
||||
169
plugins/base/utils/transition-selection.cpp
Normal file
169
plugins/base/utils/transition-selection.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "transition-selection.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
void TransitionSelection::Save(obs_data_t *obj, const char *name,
|
||||
const char *typeName) const
|
||||
{
|
||||
obs_data_set_int(obj, typeName, static_cast<int>(_type));
|
||||
|
||||
switch (_type) {
|
||||
case Type::TRANSITION:
|
||||
obs_data_set_string(obj, name,
|
||||
GetWeakSourceName(_transition).c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionSelection::Load(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
{
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, typeName));
|
||||
auto target = obs_data_get_string(obj, name);
|
||||
switch (_type) {
|
||||
case Type::TRANSITION:
|
||||
_transition = GetWeakTransitionByName(target);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OBSWeakSource TransitionSelection::GetTransition() const
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::TRANSITION:
|
||||
return _transition;
|
||||
case Type::CURRENT: {
|
||||
auto source = obs_frontend_get_current_transition();
|
||||
auto weakSource = obs_source_get_weak_source(source);
|
||||
obs_weak_source_release(weakSource);
|
||||
obs_source_release(source);
|
||||
return weakSource;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string TransitionSelection::ToString() const
|
||||
{
|
||||
switch (_type) {
|
||||
case Type::TRANSITION:
|
||||
return GetWeakSourceName(_transition);
|
||||
case Type::CURRENT:
|
||||
return obs_module_text("AdvSceneSwitcher.currentTransition");
|
||||
case Type::ANY:
|
||||
return obs_module_text("AdvSceneSwitcher.anyTransition");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
|
||||
bool current, bool any)
|
||||
: FilterComboBox(parent,
|
||||
obs_module_text("AdvSceneSwitcher.selectTransition"))
|
||||
{
|
||||
setDuplicatesEnabled(true);
|
||||
PopulateTransitionSelection(this, current, any, false);
|
||||
|
||||
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SelectionChanged(const QString &)));
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
|
||||
{
|
||||
// Order of entries
|
||||
// 1. Any transition
|
||||
// 2. Current transition
|
||||
// 4. Transitions
|
||||
|
||||
switch (t.GetType()) {
|
||||
case TransitionSelection::Type::TRANSITION:
|
||||
setCurrentText(QString::fromStdString(t.ToString()));
|
||||
break;
|
||||
case TransitionSelection::Type::CURRENT:
|
||||
setCurrentIndex(findText(QString::fromStdString(obs_module_text(
|
||||
"AdvSceneSwitcher.currentTransition"))));
|
||||
break;
|
||||
case TransitionSelection::Type::ANY:
|
||||
setCurrentIndex(findText(QString::fromStdString(
|
||||
obs_module_text("AdvSceneSwitcher.anyTransition"))));
|
||||
break;
|
||||
default:
|
||||
setCurrentIndex(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::Repopulate(bool current, bool any)
|
||||
{
|
||||
{
|
||||
const QSignalBlocker blocker(this);
|
||||
clear();
|
||||
PopulateTransitionSelection(this, current, any);
|
||||
setCurrentIndex(-1);
|
||||
}
|
||||
TransitionSelection t;
|
||||
emit TransitionChanged(t);
|
||||
}
|
||||
|
||||
static bool isFirstEntry(QComboBox *l, QString name, int idx)
|
||||
{
|
||||
for (auto i = l->count() - 1; i >= 0; i--) {
|
||||
if (l->itemText(i) == name) {
|
||||
return idx == i;
|
||||
}
|
||||
}
|
||||
|
||||
// If entry cannot be found we dont want the selection to be empty
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TransitionSelectionWidget::IsCurrentTransitionSelected(const QString &name)
|
||||
{
|
||||
if (name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.currentTransition")))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TransitionSelectionWidget::IsAnyTransitionSelected(const QString &name)
|
||||
{
|
||||
if (name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.anyTransition")))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TransitionSelectionWidget::SelectionChanged(const QString &name)
|
||||
{
|
||||
TransitionSelection t;
|
||||
auto transition = GetWeakTransitionByQString(name);
|
||||
if (transition) {
|
||||
t._type = TransitionSelection::Type::TRANSITION;
|
||||
t._transition = transition;
|
||||
}
|
||||
|
||||
if (!transition) {
|
||||
if (IsCurrentTransitionSelected(name)) {
|
||||
t._type = TransitionSelection::Type::CURRENT;
|
||||
}
|
||||
if (IsAnyTransitionSelected(name)) {
|
||||
t._type = TransitionSelection::Type::ANY;
|
||||
}
|
||||
}
|
||||
|
||||
emit TransitionChanged(t);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
50
plugins/base/utils/transition-selection.hpp
Normal file
50
plugins/base/utils/transition-selection.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "filter-combo-box.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class TransitionSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "transition",
|
||||
const char *typeName = "transitionType") const;
|
||||
void Load(obs_data_t *obj, const char *name = "transition",
|
||||
const char *typeName = "transitionType");
|
||||
|
||||
enum class Type {
|
||||
TRANSITION,
|
||||
CURRENT,
|
||||
ANY,
|
||||
};
|
||||
|
||||
Type GetType() const { return _type; }
|
||||
OBSWeakSource GetTransition() const;
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
OBSWeakSource _transition;
|
||||
Type _type = Type::TRANSITION;
|
||||
friend class TransitionSelectionWidget;
|
||||
};
|
||||
|
||||
class TransitionSelectionWidget : public FilterComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TransitionSelectionWidget(QWidget *parent, bool current = true,
|
||||
bool any = false);
|
||||
void SetTransition(TransitionSelection &);
|
||||
void Repopulate(bool current, bool any);
|
||||
signals:
|
||||
void TransitionChanged(const TransitionSelection &);
|
||||
|
||||
private slots:
|
||||
void SelectionChanged(const QString &name);
|
||||
|
||||
private:
|
||||
bool IsCurrentTransitionSelected(const QString &name);
|
||||
bool IsAnyTransitionSelected(const QString &name);
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
427
plugins/base/utils/websocket-helpers.cpp
Normal file
427
plugins/base/utils/websocket-helpers.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
#include "websocket-helpers.hpp"
|
||||
#include "connection-manager.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "sync-helpers.hpp"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <obs-websocket-api.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
using websocketpp::lib::bind;
|
||||
|
||||
#define RPC_VERSION 1
|
||||
|
||||
constexpr char VendorName[] = "AdvancedSceneSwitcher";
|
||||
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
|
||||
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
|
||||
obs_websocket_vendor vendor;
|
||||
|
||||
static void clearWebsocketMessages();
|
||||
static std::vector<std::string> websocketMessages;
|
||||
static void registerWebsocketVendor();
|
||||
|
||||
static bool setup();
|
||||
static bool setupDone = setup();
|
||||
|
||||
bool setup()
|
||||
{
|
||||
AddIntervalResetStep(clearWebsocketMessages);
|
||||
AddPluginPostLoadStep(registerWebsocketVendor);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> &GetWebsocketMessages()
|
||||
{
|
||||
return websocketMessages;
|
||||
}
|
||||
|
||||
static void clearWebsocketMessages()
|
||||
{
|
||||
websocketMessages.clear();
|
||||
for (auto &connection : GetConnections()) {
|
||||
auto c = dynamic_cast<Connection *>(connection.get());
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
c->Events().clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SendWebsocketEvent(const std::string &eventMsg)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_string(data, "message", eventMsg.c_str());
|
||||
obs_websocket_vendor_emit_event(vendor, VendorEvent, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *,
|
||||
void *)
|
||||
{
|
||||
if (!obs_data_has_user_value(request_data, "message")) {
|
||||
vblog(LOG_INFO, "received unexpected m '%s'",
|
||||
obs_data_get_json(request_data));
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = obs_data_get_string(request_data, "message");
|
||||
auto lock = LockContext();
|
||||
websocketMessages.emplace_back(msg);
|
||||
vblog(LOG_INFO, "received message: %s", msg);
|
||||
}
|
||||
|
||||
static void registerWebsocketVendor()
|
||||
{
|
||||
vendor = obs_websocket_register_vendor(VendorName);
|
||||
if (!vendor) {
|
||||
blog(LOG_ERROR,
|
||||
"Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obs_websocket_vendor_register_request(
|
||||
vendor, VendorRequest, receiveWebsocketMessage, NULL))
|
||||
blog(LOG_ERROR,
|
||||
"Failed to register `AdvancedSceneSwitcherMessage` request with obs-websocket.");
|
||||
|
||||
uint api_version = obs_websocket_get_api_version();
|
||||
if (api_version == 0) {
|
||||
blog(LOG_ERROR,
|
||||
"Unable to fetch obs-websocket plugin API version.");
|
||||
return;
|
||||
} else if (api_version == 1) {
|
||||
blog(LOG_WARNING,
|
||||
"Unsupported obs-websocket plugin API version for calling requests.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
WSConnection::WSConnection(bool useOBSProtocol) : QObject(nullptr)
|
||||
{
|
||||
_client.get_alog().clear_channels(
|
||||
websocketpp::log::alevel::frame_header |
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
_client.init_asio();
|
||||
#ifndef _WIN32
|
||||
_client.set_reuse_addr(true);
|
||||
#endif
|
||||
|
||||
UseOBSWebsocketProtocol(useOBSProtocol);
|
||||
_client.set_close_handler(bind(&WSConnection::OnClose, this, _1));
|
||||
}
|
||||
|
||||
WSConnection::~WSConnection()
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
void WSConnection::ConnectThread()
|
||||
{
|
||||
do {
|
||||
std::unique_lock<std::mutex> lck(_waitMtx);
|
||||
_client.reset();
|
||||
_status = Status::CONNECTING;
|
||||
// Create a connection to the given URI and queue it for connection once
|
||||
// the event loop starts
|
||||
websocketpp::lib::error_code ec;
|
||||
client::connection_ptr con = _client.get_connection(_uri, ec);
|
||||
if (ec) {
|
||||
_failMsg = ec.message();
|
||||
blog(LOG_INFO, "connect to '%s' failed: %s",
|
||||
_uri.c_str(), _failMsg.c_str());
|
||||
} else {
|
||||
_failMsg = "";
|
||||
_client.connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
|
||||
// Start the ASIO io_service run loop
|
||||
vblog(LOG_INFO, "connect io thread started for '%s'",
|
||||
_uri.c_str());
|
||||
_client.run();
|
||||
vblog(LOG_INFO, "connect: io thread exited '%s'",
|
||||
_uri.c_str());
|
||||
}
|
||||
|
||||
if (_reconnect) {
|
||||
blog(LOG_INFO,
|
||||
"trying to reconnect to %s in %d seconds.",
|
||||
_uri.c_str(), _reconnectDelay);
|
||||
_cv.wait_for(lck,
|
||||
std::chrono::seconds(_reconnectDelay));
|
||||
}
|
||||
} while (_reconnect && !_disconnect);
|
||||
_status = Status::DISCONNECTED;
|
||||
}
|
||||
|
||||
void WSConnection::Connect(const std::string &uri, const std::string &pass,
|
||||
bool reconnect, int reconnectDelay)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
if (_status != Status::DISCONNECTED) {
|
||||
blog(LOG_INFO, "connect to '%s' already in progress",
|
||||
uri.c_str());
|
||||
return;
|
||||
}
|
||||
_uri = uri;
|
||||
_password = pass;
|
||||
_reconnect = reconnect;
|
||||
_reconnectDelay = reconnectDelay;
|
||||
_disconnect = false;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
_thread = std::thread(&WSConnection::ConnectThread, this);
|
||||
blog(LOG_INFO, "connect to '%s' started", uri.c_str());
|
||||
}
|
||||
|
||||
void WSConnection::Disconnect()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
_disconnect = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Client stopping", ec);
|
||||
{
|
||||
std::unique_lock<std::mutex> waitLck(_waitMtx);
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
while (_status != Status::DISCONNECTED) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Client stopping", ec);
|
||||
}
|
||||
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
_status = Status::DISCONNECTED;
|
||||
}
|
||||
|
||||
std::string ConstructVendorRequestMessage(const std::string &message)
|
||||
{
|
||||
auto request = obs_data_create();
|
||||
obs_data_set_int(request, "op", 6);
|
||||
auto *data = obs_data_create();
|
||||
obs_data_set_string(data, "requestType", "CallVendorRequest");
|
||||
obs_data_set_string(data, "requestId", message.c_str());
|
||||
|
||||
auto vendorData = obs_data_create();
|
||||
obs_data_set_string(vendorData, "vendorName", VendorName);
|
||||
obs_data_set_string(vendorData, "requestType", VendorRequest);
|
||||
|
||||
auto msgObj = obs_data_create();
|
||||
obs_data_set_string(msgObj, "message", message.c_str());
|
||||
obs_data_set_obj(vendorData, "requestData", msgObj);
|
||||
obs_data_set_obj(data, "requestData", vendorData);
|
||||
|
||||
obs_data_set_obj(request, "d", data);
|
||||
|
||||
const std::string result(obs_data_get_json(request));
|
||||
|
||||
obs_data_release(msgObj);
|
||||
obs_data_release(vendorData);
|
||||
obs_data_release(data);
|
||||
obs_data_release(request);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WSConnection::SendRequest(const std::string &msg)
|
||||
{
|
||||
Send(msg);
|
||||
}
|
||||
|
||||
WSConnection::Status WSConnection::GetStatus() const
|
||||
{
|
||||
return _status;
|
||||
}
|
||||
|
||||
void WSConnection::UseOBSWebsocketProtocol(bool useOBSProtocol)
|
||||
{
|
||||
_client.set_open_handler(bind(useOBSProtocol
|
||||
? &WSConnection::OnOBSOpen
|
||||
: &WSConnection::OnGenericOpen,
|
||||
this, _1));
|
||||
_client.set_message_handler(
|
||||
bind(useOBSProtocol ? &WSConnection::OnOBSMessage
|
||||
: &WSConnection::OnGenericMessage,
|
||||
this, _1, _2));
|
||||
}
|
||||
|
||||
void WSConnection::OnGenericOpen(connection_hdl)
|
||||
{
|
||||
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
|
||||
_status = Status::AUTHENTICATED;
|
||||
}
|
||||
|
||||
void WSConnection::OnOBSOpen(connection_hdl)
|
||||
{
|
||||
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
|
||||
_status = Status::CONNECTING;
|
||||
}
|
||||
|
||||
void WSConnection::HandleHello(obs_data_t *helloMsg)
|
||||
{
|
||||
_status = Status::CONNECTED;
|
||||
|
||||
auto identifyMsg = obs_data_create();
|
||||
obs_data_set_int(identifyMsg, "op", 1);
|
||||
auto *data = obs_data_create();
|
||||
obs_data_set_int(data, "rpcVersion", RPC_VERSION);
|
||||
// We are only interested in EventSubscription::Vendors (1 << 9)
|
||||
obs_data_set_int(data, "eventSubscriptions", 1 << 9);
|
||||
obs_data_t *helloData = obs_data_get_obj(helloMsg, "d");
|
||||
if (obs_data_has_user_value(helloData, "authentication")) {
|
||||
auto auth = obs_data_get_obj(helloData, "authentication");
|
||||
QString salt = obs_data_get_string(auth, "salt");
|
||||
QString challenge = obs_data_get_string(auth, "challenge");
|
||||
auto secret = QCryptographicHash::hash(
|
||||
(QString::fromStdString(_password) + salt)
|
||||
.toUtf8(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toBase64();
|
||||
auto authenticationString = QString(
|
||||
QCryptographicHash::hash((secret + challenge).toUtf8(),
|
||||
QCryptographicHash::Sha256)
|
||||
.toBase64());
|
||||
obs_data_set_string(data, "authentication",
|
||||
authenticationString.toStdString().c_str());
|
||||
obs_data_release(auth);
|
||||
}
|
||||
obs_data_release(helloData);
|
||||
obs_data_set_obj(identifyMsg, "d", data);
|
||||
const std::string response(obs_data_get_json(identifyMsg));
|
||||
obs_data_release(data);
|
||||
obs_data_release(identifyMsg);
|
||||
Send(response);
|
||||
}
|
||||
|
||||
void WSConnection::HandleEvent(obs_data_t *msg)
|
||||
{
|
||||
auto d = obs_data_get_obj(msg, "d");
|
||||
auto eventData = obs_data_get_obj(d, "eventData");
|
||||
if (strcmp(obs_data_get_string(eventData, "vendorName"), VendorName) !=
|
||||
0) {
|
||||
vblog(LOG_INFO, "ignoring vendor event from \"%s\"",
|
||||
obs_data_get_string(eventData, "vendorName"));
|
||||
return;
|
||||
}
|
||||
if (strcmp(obs_data_get_string(eventData, "eventType"), VendorEvent) !=
|
||||
0) {
|
||||
vblog(LOG_INFO, "ignoring event type\"%s\"",
|
||||
obs_data_get_string(eventData, "eventType"));
|
||||
return;
|
||||
}
|
||||
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
|
||||
auto lock = LockContext();
|
||||
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
|
||||
vblog(LOG_INFO, "received event msg \"%s\"",
|
||||
obs_data_get_string(eventDataNested, "message"));
|
||||
obs_data_release(eventDataNested);
|
||||
obs_data_release(eventData);
|
||||
obs_data_release(d);
|
||||
}
|
||||
|
||||
void WSConnection::HandleResponse(obs_data_t *response)
|
||||
{
|
||||
auto data = obs_data_get_obj(response, "d");
|
||||
auto id = obs_data_get_string(data, "requestId");
|
||||
auto status = obs_data_get_obj(data, "requestStatus");
|
||||
bool result = obs_data_get_bool(status, "result");
|
||||
int code = obs_data_get_int(status, "code");
|
||||
auto comment = obs_data_get_string(status, "comment");
|
||||
vblog(LOG_INFO, "received result '%d' with code '%d' (%s) for id '%s'",
|
||||
result, code, comment, id);
|
||||
obs_data_release(status);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSConnection::OnGenericMessage(connection_hdl, client::message_ptr message)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
const auto payload = message->get_payload();
|
||||
_messages.emplace_back(payload);
|
||||
vblog(LOG_INFO, "received event msg \"%s\"", payload.c_str());
|
||||
}
|
||||
|
||||
void WSConnection::OnOBSMessage(connection_hdl, client::message_ptr message)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string payload = message->get_payload();
|
||||
const char *msg = payload.c_str();
|
||||
auto json = obs_data_create_from_json(msg);
|
||||
if (!json) {
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||
obs_data_release(json);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obs_data_has_user_value(json, "op")) {
|
||||
blog(LOG_ERROR, "received msg has no opcode, '%s'", msg);
|
||||
obs_data_release(json);
|
||||
return;
|
||||
}
|
||||
|
||||
int opcode = obs_data_get_int(json, "op");
|
||||
switch (opcode) {
|
||||
case 0: // Hello
|
||||
HandleHello(json);
|
||||
break;
|
||||
case 2: // Identified
|
||||
_status = Status::AUTHENTICATED;
|
||||
break;
|
||||
case 5: // Event (Vendor)
|
||||
HandleEvent(json);
|
||||
break;
|
||||
case 7: // RequestResponse
|
||||
HandleResponse(json);
|
||||
break;
|
||||
default:
|
||||
vblog(LOG_INFO, "ignoring unknown opcode %d", opcode);
|
||||
break;
|
||||
}
|
||||
obs_data_release(json);
|
||||
}
|
||||
|
||||
void WSConnection::Send(const std::string &msg)
|
||||
{
|
||||
if (_connection.expired()) {
|
||||
return;
|
||||
}
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_client.send(_connection, msg, websocketpp::frame::opcode::text,
|
||||
errorCode);
|
||||
if (errorCode) {
|
||||
std::string errorCodeMessage = errorCode.message();
|
||||
blog(LOG_INFO, "websocket send failed: %s",
|
||||
errorCodeMessage.c_str());
|
||||
}
|
||||
vblog(LOG_INFO, "sent message to '%s':\n%s", _uri.c_str(), msg.c_str());
|
||||
}
|
||||
|
||||
void WSConnection::OnClose(connection_hdl)
|
||||
{
|
||||
blog(LOG_INFO, "client-connection to %s closed.", _uri.c_str());
|
||||
_status = Status::DISCONNECTED;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
82
plugins/base/utils/websocket-helpers.hpp
Normal file
82
plugins/base/utils/websocket-helpers.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QVariantHash>
|
||||
#include <QtCore/QThreadPool>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <QRunnable>
|
||||
|
||||
#include <websocketpp/config/asio_no_tls_client.hpp>
|
||||
#include <websocketpp/config/asio_no_tls.hpp>
|
||||
#include <websocketpp/server.hpp>
|
||||
#include <websocketpp/client.hpp>
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
using websocketpp::connection_hdl;
|
||||
|
||||
void SendWebsocketEvent(const std::string &);
|
||||
std::string ConstructVendorRequestMessage(const std::string &message);
|
||||
std::vector<std::string> &GetWebsocketMessages();
|
||||
|
||||
class WSConnection : public QObject {
|
||||
using server = websocketpp::server<websocketpp::config::asio>;
|
||||
using client = websocketpp::client<websocketpp::config::asio_client>;
|
||||
|
||||
public:
|
||||
explicit WSConnection(bool useOBSProtocol = true);
|
||||
virtual ~WSConnection();
|
||||
|
||||
void Connect(const std::string &uri, const std::string &pass,
|
||||
bool _reconnect, int reconnectDelay = 10);
|
||||
void Disconnect();
|
||||
void SendRequest(const std::string &msg);
|
||||
std::vector<std::string> &Events() { return _messages; }
|
||||
std::string GetFail() { return _failMsg; }
|
||||
|
||||
enum class Status {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
AUTHENTICATED,
|
||||
};
|
||||
Status GetStatus() const;
|
||||
void UseOBSWebsocketProtocol(bool);
|
||||
|
||||
private:
|
||||
void OnGenericOpen(connection_hdl hdl);
|
||||
void OnOBSOpen(connection_hdl hdl);
|
||||
void OnGenericMessage(connection_hdl hdl, client::message_ptr message);
|
||||
void OnOBSMessage(connection_hdl hdl, client::message_ptr message);
|
||||
void OnClose(connection_hdl hdl);
|
||||
void Send(const std::string &);
|
||||
void ConnectThread();
|
||||
void HandleHello(obs_data_t *helloMsg);
|
||||
void HandleEvent(obs_data_t *event);
|
||||
void HandleResponse(obs_data_t *response);
|
||||
|
||||
client _client;
|
||||
std::string _uri = "";
|
||||
std::string _password = "";
|
||||
connection_hdl _connection;
|
||||
std::thread _thread;
|
||||
bool _reconnect = false;
|
||||
int _reconnectDelay = 10;
|
||||
std::mutex _waitMtx;
|
||||
std::mutex _connectMtx;
|
||||
std::condition_variable _cv;
|
||||
std::string _failMsg = "";
|
||||
std::atomic<Status> _status = {Status::DISCONNECTED};
|
||||
std::atomic_bool _disconnect{false};
|
||||
|
||||
std::vector<std::string> _messages;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
314
plugins/base/utils/windows/windows.cpp
Normal file
314
plugins/base/utils/windows/windows.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#include "hotkey-helpers.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <unordered_map>
|
||||
#include <util/platform.h>
|
||||
#include <windows.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool canSimulateKeyPresses = true;
|
||||
|
||||
bool CanSimulateKeyPresses()
|
||||
{
|
||||
return canSimulateKeyPresses;
|
||||
}
|
||||
|
||||
static const std::unordered_map<HotkeyType, long> keyTable = {
|
||||
// Chars
|
||||
{HotkeyType::Key_A, 0x41},
|
||||
{HotkeyType::Key_B, 0x42},
|
||||
{HotkeyType::Key_C, 0x43},
|
||||
{HotkeyType::Key_D, 0x44},
|
||||
{HotkeyType::Key_E, 0x45},
|
||||
{HotkeyType::Key_F, 0x46},
|
||||
{HotkeyType::Key_G, 0x47},
|
||||
{HotkeyType::Key_H, 0x48},
|
||||
{HotkeyType::Key_I, 0x49},
|
||||
{HotkeyType::Key_J, 0x4A},
|
||||
{HotkeyType::Key_K, 0x4B},
|
||||
{HotkeyType::Key_L, 0x4C},
|
||||
{HotkeyType::Key_M, 0x4D},
|
||||
{HotkeyType::Key_N, 0x4E},
|
||||
{HotkeyType::Key_O, 0x4F},
|
||||
{HotkeyType::Key_P, 0x50},
|
||||
{HotkeyType::Key_Q, 0x51},
|
||||
{HotkeyType::Key_R, 0x52},
|
||||
{HotkeyType::Key_S, 0x53},
|
||||
{HotkeyType::Key_T, 0x54},
|
||||
{HotkeyType::Key_U, 0x55},
|
||||
{HotkeyType::Key_V, 0x56},
|
||||
{HotkeyType::Key_W, 0x57},
|
||||
{HotkeyType::Key_X, 0x58},
|
||||
{HotkeyType::Key_Y, 0x59},
|
||||
{HotkeyType::Key_Z, 0x5A},
|
||||
|
||||
// Numbers
|
||||
{HotkeyType::Key_0, 0x30},
|
||||
{HotkeyType::Key_1, 0x31},
|
||||
{HotkeyType::Key_2, 0x32},
|
||||
{HotkeyType::Key_3, 0x33},
|
||||
{HotkeyType::Key_4, 0x34},
|
||||
{HotkeyType::Key_5, 0x35},
|
||||
{HotkeyType::Key_6, 0x36},
|
||||
{HotkeyType::Key_7, 0x37},
|
||||
{HotkeyType::Key_8, 0x38},
|
||||
{HotkeyType::Key_9, 0x39},
|
||||
|
||||
{HotkeyType::Key_F1, VK_F1},
|
||||
{HotkeyType::Key_F2, VK_F2},
|
||||
{HotkeyType::Key_F3, VK_F3},
|
||||
{HotkeyType::Key_F4, VK_F4},
|
||||
{HotkeyType::Key_F5, VK_F5},
|
||||
{HotkeyType::Key_F6, VK_F6},
|
||||
{HotkeyType::Key_F7, VK_F7},
|
||||
{HotkeyType::Key_F8, VK_F8},
|
||||
{HotkeyType::Key_F9, VK_F9},
|
||||
{HotkeyType::Key_F10, VK_F10},
|
||||
{HotkeyType::Key_F11, VK_F11},
|
||||
{HotkeyType::Key_F12, VK_F12},
|
||||
{HotkeyType::Key_F13, VK_F13},
|
||||
{HotkeyType::Key_F14, VK_F14},
|
||||
{HotkeyType::Key_F15, VK_F15},
|
||||
{HotkeyType::Key_F16, VK_F16},
|
||||
{HotkeyType::Key_F17, VK_F17},
|
||||
{HotkeyType::Key_F18, VK_F18},
|
||||
{HotkeyType::Key_F19, VK_F19},
|
||||
{HotkeyType::Key_F20, VK_F20},
|
||||
{HotkeyType::Key_F21, VK_F21},
|
||||
{HotkeyType::Key_F22, VK_F22},
|
||||
{HotkeyType::Key_F23, VK_F23},
|
||||
{HotkeyType::Key_F24, VK_F24},
|
||||
|
||||
{HotkeyType::Key_Escape, VK_ESCAPE},
|
||||
{HotkeyType::Key_Space, VK_SPACE},
|
||||
{HotkeyType::Key_Return, VK_RETURN},
|
||||
{HotkeyType::Key_Backspace, VK_BACK},
|
||||
{HotkeyType::Key_Tab, VK_TAB},
|
||||
|
||||
{HotkeyType::Key_Shift_L, VK_LSHIFT},
|
||||
{HotkeyType::Key_Shift_R, VK_RSHIFT},
|
||||
{HotkeyType::Key_Control_L, VK_LCONTROL},
|
||||
{HotkeyType::Key_Control_R, VK_RCONTROL},
|
||||
{HotkeyType::Key_Alt_L, VK_LMENU},
|
||||
{HotkeyType::Key_Alt_R, VK_RMENU},
|
||||
{HotkeyType::Key_Win_L, VK_LWIN},
|
||||
{HotkeyType::Key_Win_R, VK_RWIN},
|
||||
{HotkeyType::Key_Apps, VK_APPS},
|
||||
|
||||
{HotkeyType::Key_CapsLock, VK_CAPITAL},
|
||||
{HotkeyType::Key_NumLock, VK_NUMLOCK},
|
||||
{HotkeyType::Key_ScrollLock, VK_SCROLL},
|
||||
|
||||
{HotkeyType::Key_PrintScreen, VK_SNAPSHOT},
|
||||
{HotkeyType::Key_Pause, VK_PAUSE},
|
||||
|
||||
{HotkeyType::Key_Insert, VK_INSERT},
|
||||
{HotkeyType::Key_Delete, VK_DELETE},
|
||||
{HotkeyType::Key_PageUP, VK_PRIOR},
|
||||
{HotkeyType::Key_PageDown, VK_NEXT},
|
||||
{HotkeyType::Key_Home, VK_HOME},
|
||||
{HotkeyType::Key_End, VK_END},
|
||||
|
||||
{HotkeyType::Key_Left, VK_LEFT},
|
||||
{HotkeyType::Key_Up, VK_UP},
|
||||
{HotkeyType::Key_Right, VK_RIGHT},
|
||||
{HotkeyType::Key_Down, VK_DOWN},
|
||||
|
||||
{HotkeyType::Key_Numpad0, VK_NUMPAD0},
|
||||
{HotkeyType::Key_Numpad1, VK_NUMPAD1},
|
||||
{HotkeyType::Key_Numpad2, VK_NUMPAD2},
|
||||
{HotkeyType::Key_Numpad3, VK_NUMPAD3},
|
||||
{HotkeyType::Key_Numpad4, VK_NUMPAD4},
|
||||
{HotkeyType::Key_Numpad5, VK_NUMPAD5},
|
||||
{HotkeyType::Key_Numpad6, VK_NUMPAD6},
|
||||
{HotkeyType::Key_Numpad7, VK_NUMPAD7},
|
||||
{HotkeyType::Key_Numpad8, VK_NUMPAD8},
|
||||
{HotkeyType::Key_Numpad9, VK_NUMPAD9},
|
||||
|
||||
{HotkeyType::Key_NumpadAdd, VK_ADD},
|
||||
{HotkeyType::Key_NumpadSubtract, VK_SUBTRACT},
|
||||
{HotkeyType::Key_NumpadMultiply, VK_MULTIPLY},
|
||||
{HotkeyType::Key_NumpadDivide, VK_DIVIDE},
|
||||
{HotkeyType::Key_NumpadDecimal, VK_DECIMAL},
|
||||
{HotkeyType::Key_NumpadEnter, VK_RETURN},
|
||||
};
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
{
|
||||
const int repeatInterval = 100;
|
||||
|
||||
INPUT ip;
|
||||
ip.type = INPUT_KEYBOARD;
|
||||
ip.ki.wScan = 0;
|
||||
ip.ki.time = 0;
|
||||
ip.ki.dwExtraInfo = 0;
|
||||
|
||||
for (int cur = 0; cur < duration; cur += repeatInterval) {
|
||||
// Press keys
|
||||
ip.ki.dwFlags = 0;
|
||||
for (const auto &key : keys) {
|
||||
auto it = keyTable.find(key);
|
||||
if (it == keyTable.end()) {
|
||||
continue;
|
||||
}
|
||||
ip.ki.wVk = (WORD)it->second;
|
||||
SendInput(1, &ip, sizeof(INPUT));
|
||||
}
|
||||
// When instantly releasing the key presses OBS might miss them
|
||||
Sleep(repeatInterval);
|
||||
}
|
||||
|
||||
// Release keys
|
||||
ip.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
for (const auto &key : keys) {
|
||||
auto it = keyTable.find(key);
|
||||
if (it == keyTable.end()) {
|
||||
continue;
|
||||
}
|
||||
ip.ki.wVk = (WORD)it->second;
|
||||
SendInput(1, &ip, sizeof(INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
static HWND getHWNDfromTitle(const std::string &title)
|
||||
{
|
||||
HWND hwnd = NULL;
|
||||
wchar_t wTitle[512];
|
||||
os_utf8_to_wcs(title.c_str(), 0, wTitle, 512);
|
||||
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle);
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
std::string GetWindowClassByWindowTitle(const std::string &window)
|
||||
{
|
||||
HWND hwnd = NULL;
|
||||
hwnd = getHWNDfromTitle(window);
|
||||
if (!hwnd) {
|
||||
return "";
|
||||
}
|
||||
std::wstring wClass;
|
||||
wClass.resize(1024);
|
||||
if (!GetClassNameW(hwnd, &wClass[0], wClass.capacity())) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t len = os_wcs_to_utf8(wClass.c_str(), 0, nullptr, 0);
|
||||
std::string className;
|
||||
className.resize(len);
|
||||
os_wcs_to_utf8(wClass.c_str(), 0, &className[0], len + 1);
|
||||
return className;
|
||||
}
|
||||
|
||||
class RawMouseInputFilter;
|
||||
static RawMouseInputFilter *mouseInputFilter;
|
||||
static std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};
|
||||
static std::chrono::high_resolution_clock::time_point lastMouseMiddleClickTime{};
|
||||
static std::chrono::high_resolution_clock::time_point lastMouseRightClickTime{};
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseLeftClickTime()
|
||||
{
|
||||
return lastMouseLeftClickTime;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseMiddleClickTime()
|
||||
{
|
||||
return lastMouseMiddleClickTime;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastMouseRightClickTime()
|
||||
{
|
||||
return lastMouseRightClickTime;
|
||||
}
|
||||
|
||||
static void handleRawMouseInput(LPARAM lParam)
|
||||
{
|
||||
UINT dwSize;
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize,
|
||||
sizeof(RAWINPUTHEADER));
|
||||
LPBYTE lpb = new BYTE[dwSize];
|
||||
if (lpb == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,
|
||||
sizeof(RAWINPUTHEADER)) != dwSize)
|
||||
OutputDebugString(TEXT(
|
||||
"GetRawInputData does not return correct size !\n"));
|
||||
|
||||
RAWINPUT *raw = (RAWINPUT *)lpb;
|
||||
if (raw->header.dwType == RIM_TYPEMOUSE) {
|
||||
switch (raw->data.mouse.usButtonFlags) {
|
||||
case RI_MOUSE_BUTTON_1_DOWN:
|
||||
lastMouseLeftClickTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
break;
|
||||
case RI_MOUSE_BUTTON_3_DOWN:
|
||||
lastMouseMiddleClickTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
break;
|
||||
case RI_MOUSE_BUTTON_2_DOWN:
|
||||
lastMouseRightClickTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete[] lpb;
|
||||
}
|
||||
|
||||
class RawMouseInputFilter : public QAbstractNativeEventFilter {
|
||||
public:
|
||||
virtual bool nativeEventFilter(const QByteArray &eventType,
|
||||
void *message, qintptr *) Q_DECL_OVERRIDE
|
||||
{
|
||||
if (eventType != "windows_generic_MSG") {
|
||||
return false;
|
||||
}
|
||||
MSG *msg = reinterpret_cast<MSG *>(message);
|
||||
|
||||
if (msg->message != WM_INPUT) {
|
||||
return false;
|
||||
}
|
||||
handleRawMouseInput(msg->lParam);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static void setupMouseEeventFilter()
|
||||
{
|
||||
mouseInputFilter = new RawMouseInputFilter;
|
||||
QAbstractEventDispatcher::instance()->installNativeEventFilter(
|
||||
mouseInputFilter);
|
||||
|
||||
RAWINPUTDEVICE rid;
|
||||
rid.dwFlags = RIDEV_INPUTSINK;
|
||||
rid.usUsagePage = 1;
|
||||
rid.usUsage = 2;
|
||||
rid.hwndTarget = (HWND)obs_frontend_get_main_window_handle();
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
|
||||
blog(LOG_WARNING, "Registering for raw mouse input failed!\n"
|
||||
"Mouse click detection not functional!");
|
||||
}
|
||||
}
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginInitStep(setupMouseEeventFilter);
|
||||
AddPluginCleanupStep([]() {
|
||||
if (mouseInputFilter) {
|
||||
delete mouseInputFilter;
|
||||
mouseInputFilter = nullptr;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init = setup();
|
||||
|
||||
} // namespace advss
|
||||
Reference in New Issue
Block a user