Add support for websocket vendor events

This commit is contained in:
WarmUpTill 2022-09-14 00:56:50 +02:00 committed by WarmUpTill
parent 6d755ad570
commit 1a8a4584c1
9 changed files with 331 additions and 25 deletions

View File

@ -335,7 +335,11 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="Profile"
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
AdvSceneSwitcher.condition.websocket="Websocket"
AdvSceneSwitcher.condition.websocket.entry="Message was received:"
AdvSceneSwitcher.condition.websocket.type.request="Request"
AdvSceneSwitcher.condition.websocket.type.event="Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
@ -496,7 +500,10 @@ AdvSceneSwitcher.action.sequence.status.none="none"
AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached"
AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item"
AdvSceneSwitcher.action.websocket="Websocket"
AdvSceneSwitcher.action.websocket.entry="Send scene switcher message via {{connection}}"
AdvSceneSwitcher.action.websocket.type.request="Request"
AdvSceneSwitcher.action.websocket.type.event="Event"
AdvSceneSwitcher.action.websocket.entry.request="Send scene switcher {{type}} via {{connection}}"
AdvSceneSwitcher.action.websocket.entry.event="Send scene switcher {{type}} to connected clients"
AdvSceneSwitcher.action.http="Http"
AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST"

View File

@ -227,7 +227,7 @@ void SwitcherData::Thread()
}
}
websocketMessages.clear();
ClearWebsocketMessages();
// After this point we will call frontend functions like
// obs_frontend_set_current_scene() and

View File

@ -9,25 +9,58 @@ bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
"AdvSceneSwitcher.action.websocket"});
bool MacroActionWebsocket::PerformAction()
static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
{MacroActionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.action.websocket.type.request"},
{MacroActionWebsocket::Type::EVENT,
"AdvSceneSwitcher.action.websocket.type.event"},
};
void MacroActionWebsocket::SendRequest()
{
auto connection = GetConnectionByName(_connection);
if (!connection) {
return true;
return;
}
connection->SendMsg(_message);
}
bool MacroActionWebsocket::PerformAction()
{
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
SendRequest();
break;
case MacroActionWebsocket::Type::EVENT:
SendWebsocketEvent(_message);
break;
default:
break;
}
return true;
}
void MacroActionWebsocket::LogAction()
{
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
break;
case MacroActionWebsocket::Type::EVENT:
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
_message.c_str());
break;
default:
break;
}
}
bool MacroActionWebsocket::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_string(obj, "message", _message.c_str());
obs_data_set_string(obj, "connection", _connection.c_str());
return true;
@ -36,6 +69,7 @@ bool MacroActionWebsocket::Save(obs_data_t *obj)
bool MacroActionWebsocket::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message");
_connection = obs_data_get_string(obj, "connection");
return true;
@ -43,30 +77,39 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
std::string MacroActionWebsocket::GetShortDesc()
{
return _connection;
if (_type == Type::REQUEST) {
return _connection;
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionWebsocketEdit::MacroActionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
: QWidget(parent),
_actions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(this)),
_connection(new ConnectionSelection(this))
_connection(new ConnectionSelection(this)),
_editLayout(new QHBoxLayout())
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged()));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
auto *connectionLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.websocket.entry"),
connectionLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(connectionLayout);
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message);
setLayout(mainLayout);
@ -75,19 +118,73 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
_loading = false;
}
void MacroActionWebsocketEdit::SetupRequestEdit()
{
_editLayout->removeWidget(_actions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.request"),
_editLayout, widgetPlaceholders);
_connection->show();
}
void MacroActionWebsocketEdit::SetupEventEdit()
{
_editLayout->removeWidget(_actions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.event"),
_editLayout, widgetPlaceholders);
_connection->hide();
}
void MacroActionWebsocketEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message));
_connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize();
updateGeometry();
}
void MacroActionWebsocketEdit::ActionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroActionWebsocket::Type>(index);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionWebsocketEdit::MessageChanged()
{
if (_loading || !_entryData) {

View File

@ -22,10 +22,18 @@ public:
return std::make_shared<MacroActionWebsocket>(m);
}
enum class Type {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
std::string _connection;
private:
void SendRequest();
static bool _registered;
static const std::string id;
};
@ -47,6 +55,7 @@ public:
}
private slots:
void ActionChanged(int);
void MessageChanged();
void ConnectionSelectionChanged(const QString &);
signals:
@ -56,7 +65,12 @@ protected:
std::shared_ptr<MacroActionWebsocket> _entryData;
private:
void SetupRequestEdit();
void SetupEventEdit();
QComboBox *_actions;
ResizingPlainTextEdit *_message;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true;
};

View File

@ -12,6 +12,13 @@ bool MacroConditionWebsocket::_registered = MacroConditionFactory::Register(
{MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create,
"AdvSceneSwitcher.condition.websocket"});
static std::map<MacroConditionWebsocket::Type, std::string> conditionTypes = {
{MacroConditionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.condition.websocket.type.request"},
{MacroConditionWebsocket::Type::EVENT,
"AdvSceneSwitcher.condition.websocket.type.event"},
};
static bool matchRegex(const RegexConfig &conf, const std::string &msg,
const std::string &expr)
{
@ -25,7 +32,28 @@ static bool matchRegex(const RegexConfig &conf, const std::string &msg,
bool MacroConditionWebsocket::CheckCondition()
{
for (const auto &msg : switcher->websocketMessages) {
const std::vector<std::string> *messages = nullptr;
switch (_type) {
case MacroConditionWebsocket::Type::REQUEST:
messages = &switcher->websocketMessages;
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = GetConnectionByName(_connection);
if (!connection) {
return false;
}
messages = &connection->Events();
break;
}
default:
break;
}
if (!messages) {
return false;
}
for (const auto &msg : *messages) {
if (_regex.Enabled()) {
if (matchRegex(_regex, msg, _message)) {
return true;
@ -42,14 +70,17 @@ bool MacroConditionWebsocket::CheckCondition()
bool MacroConditionWebsocket::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_string(obj, "message", _message.c_str());
_regex.Save(obj);
obs_data_set_string(obj, "connection", _connection.c_str());
return true;
}
bool MacroConditionWebsocket::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message");
_regex.Load(obj);
// TOOD: remove in future version
@ -57,29 +88,48 @@ bool MacroConditionWebsocket::Load(obs_data_t *obj)
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"), false);
}
_connection = obs_data_get_string(obj, "connection");
return true;
}
std::string MacroConditionWebsocket::GetShortDesc()
{
return "";
if (_type == Type::REQUEST) {
return "";
}
return _connection;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroConditionWebsocket> entryData)
: QWidget(parent),
_conditions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(this)),
_regex(new RegexConfigWidget(parent))
_regex(new RegexConfigWidget(parent)),
_connection(new ConnectionSelection(this)),
_editLayout(new QHBoxLayout())
{
populateConditionSelection(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged()));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.websocket.entry")));
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message);
mainLayout->addWidget(_regex);
setLayout(mainLayout);
@ -89,19 +139,76 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
_loading = false;
}
void MacroConditionWebsocketEdit::SetupRequestEdit()
{
_editLayout->removeWidget(_conditions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _conditions},
{"{{connection}}", _connection},
};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.websocket.entry.request"),
_editLayout, widgetPlaceholders);
_connection->hide();
}
void MacroConditionWebsocketEdit::SetupEventEdit()
{
_editLayout->removeWidget(_conditions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _conditions},
{"{{connection}}", _connection},
};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.websocket.entry.event"),
_editLayout, widgetPlaceholders);
_connection->show();
}
void MacroConditionWebsocketEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message));
_regex->SetRegexConfig(_entryData->_regex);
_connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize();
updateGeometry();
}
void MacroConditionWebsocketEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroConditionWebsocket::Type>(index);
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionWebsocketEdit::MessageChanged()
{
if (_loading || !_entryData) {
@ -115,6 +222,18 @@ void MacroConditionWebsocketEdit::MessageChanged()
updateGeometry();
}
void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
const QString &connection)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_connection = connection.toStdString();
emit(HeaderInfoChanged(connection));
}
void MacroConditionWebsocketEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {

View File

@ -1,5 +1,6 @@
#pragma once
#include "macro.hpp"
#include "connection-manager.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
@ -18,8 +19,15 @@ public:
return std::make_shared<MacroConditionWebsocket>(m);
}
enum class Type {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
std::string _connection;
private:
static bool _registered;
@ -44,16 +52,24 @@ public:
}
private slots:
void ConditionChanged(int);
void MessageChanged();
void RegexChanged(RegexConfig);
void ConnectionSelectionChanged(const QString &);
signals:
void HeaderInfoChanged(const QString &);
protected:
ResizingPlainTextEdit *_message;
RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionWebsocket> _entryData;
private:
void SetupRequestEdit();
void SetupEventEdit();
QComboBox *_conditions;
ResizingPlainTextEdit *_message;
RegexConfigWidget *_regex;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true;
};

View File

@ -33,6 +33,7 @@ public:
void Load(obs_data_t *obj);
void Save(obs_data_t *obj);
std::string GetName() { return _name; }
std::vector<std::string> &Events() { return _client.Events(); }
private:
std::string _name = "";

View File

@ -13,6 +13,22 @@ using websocketpp::lib::bind;
obs_websocket_vendor vendor;
void ClearWebsocketMessages()
{
switcher->websocketMessages.clear();
for (auto &connection : switcher->connections) {
connection.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);
}
void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *)
{
if (!obs_data_has_user_value(request_data, "message")) {
@ -232,6 +248,31 @@ void WSConnection::HandleHello(obs_data_t *helloMsg)
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");
_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");
@ -278,6 +319,9 @@ void WSConnection::OnMessage(connection_hdl, client::message_ptr message)
case 2: // Identified
_status = Status::AUTHENTICATED;
break;
case 5: // Event (Vendor)
HandleEvent(json);
break;
case 7: // RequestResponse
HandleResponse(json);
break;

View File

@ -24,6 +24,10 @@ typedef websocketpp::client<websocketpp::config::asio_client> client;
constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
void ClearWebsocketMessages();
void SendWebsocketEvent(const std::string &);
class WSConnection : public QObject {
public:
@ -34,6 +38,7 @@ public:
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 {
@ -51,6 +56,7 @@ private:
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;
@ -66,4 +72,6 @@ private:
std::string _failMsg = "";
std::atomic<Status> _status = {Status::DISCONNECTED};
std::atomic_bool _disconnect{false};
std::vector<std::string> _messages;
};