Add/Move helpers to register OBS websocket vendor requests

Adds websocket vendor requests to start and stop the plugin.
Adds vendor events to indicate when the plugin is started or stopped.

These are ntended to be used for the StreamDeck plugin support.
This commit is contained in:
WarmUpTill 2024-09-01 22:15:25 +02:00 committed by WarmUpTill
parent 624b841ca1
commit 91912f2aa5
6 changed files with 201 additions and 69 deletions

View File

@ -259,6 +259,8 @@ target_sources(
lib/utils/utility.hpp
lib/utils/volume-control.cpp
lib/utils/volume-control.hpp
lib/utils/websocket-api.cpp
lib/utils/websocket-api.hpp
lib/variables/variable-line-edit.cpp
lib/variables/variable-line-edit.hpp
lib/variables/variable-number.hpp

View File

@ -29,7 +29,8 @@ extern "C" {
#endif
typedef void *obs_websocket_vendor;
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
typedef void (*obs_websocket_request_callback_function)(obs_data_t *,
obs_data_t *, void *);
struct obs_websocket_request_response {
unsigned int status_code;
@ -44,7 +45,7 @@ struct obs_websocket_request_callback {
void *priv_data;
};
inline proc_handler_t *_ph;
static proc_handler_t *_ph;
/* ==================== INTERNAL API FUNCTIONS ==================== */
@ -53,9 +54,10 @@ static inline proc_handler_t *obs_websocket_get_ph(void)
proc_handler_t *global_ph = obs_get_proc_handler();
assert(global_ph != NULL);
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
blog(LOG_DEBUG,
"Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
calldata_free(&cd);
@ -69,7 +71,9 @@ static inline bool obs_websocket_ensure_ph(void)
return _ph != NULL;
}
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
static inline bool
obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor,
const char *proc_name, calldata_t *cd)
{
if (!obs_websocket_ensure_ph())
return false;
@ -91,12 +95,12 @@ static inline unsigned int obs_websocket_get_api_version(void)
if (!obs_websocket_ensure_ph())
return 0;
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
if (!proc_handler_call(_ph, "get_api_version", &cd))
return 1; // API v1 does not include get_api_version
unsigned int ret = calldata_int(&cd, "version");
unsigned int ret = (unsigned int)calldata_int(&cd, "version");
calldata_free(&cd);
@ -104,7 +108,12 @@ static inline unsigned int obs_websocket_get_api_version(void)
}
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
static inline struct obs_websocket_request_response *
obs_websocket_call_request(const char *request_type, obs_data_t *request_data
#ifdef __cplusplus
= NULL
#endif
)
{
if (!obs_websocket_ensure_ph())
return NULL;
@ -113,14 +122,16 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
if (request_data)
request_data_string = obs_data_get_json(request_data);
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "request_type", request_type);
calldata_set_string(&cd, "request_data", request_data_string);
proc_handler_call(_ph, "call_request", &cd);
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
struct obs_websocket_request_response *ret =
(struct obs_websocket_request_response *)calldata_ptr(
&cd, "response");
calldata_free(&cd);
@ -128,7 +139,8 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
}
// Free a request response object returned by `obs_websocket_call_request()`
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
static inline void obs_websocket_request_response_free(
struct obs_websocket_request_response *response)
{
if (!response)
return;
@ -144,12 +156,13 @@ static inline void obs_websocket_request_response_free(struct obs_websocket_requ
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
// Registers a new "vendor" (Example: obs-ndi)
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
static inline obs_websocket_vendor
obs_websocket_register_vendor(const char *vendor_name)
{
if (!obs_websocket_ensure_ph())
return NULL;
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "name", vendor_name);
@ -161,32 +174,37 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven
}
// Registers a new request for a vendor
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback, void *priv_data)
static inline bool obs_websocket_vendor_register_request(
obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback,
void *priv_data)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
struct obs_websocket_request_callback cb = {};
cb.callback = request_callback;
cb.priv_data = priv_data;
struct obs_websocket_request_callback cb = {request_callback,
priv_data};
calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_request_register", &cd);
calldata_free(&cd);
return success;
}
// Unregisters an existing vendor request
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
static inline bool
obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor,
const char *request_type)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", request_type);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_request_unregister", &cd);
calldata_free(&cd);
return success;
@ -194,14 +212,17 @@ static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor
// Does not affect event_data refcount.
// Emits an event under the vendor's name
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor,
const char *event_name,
obs_data_t *event_data)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void *)event_data);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_event_emit", &cd);
calldata_free(&cd);
return success;

View File

@ -14,6 +14,7 @@
#include "tab-helpers.hpp"
#include "utility.hpp"
#include "version.h"
#include "websocket-api.hpp"
#include <obs-frontend-api.h>
#include <QAction>
@ -454,6 +455,8 @@ void SwitcherData::Start()
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
nullptr);
}
if (showSystemTrayNotifications) {
@ -481,6 +484,10 @@ void SwitcherData::Stop()
delete th;
th = nullptr;
writeToStatusFile("Advanced Scene Switcher stopped");
if (!obsIsShuttingDown) {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
nullptr);
}
}
if (showSystemTrayNotifications) {

114
lib/utils/websocket-api.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "websocket-api.hpp"
#include "log-helper.hpp"
#include "obs-websocket-api.h"
#include "plugin-state-helpers.hpp"
#include <mutex>
namespace advss {
static std::vector<std::function<void(obs_data_t *, obs_data_t *)>> callbacks;
constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequestStart[] = "AdvancedSceneSwitcherStart";
constexpr char VendorRequestStop[] = "AdvancedSceneSwitcherStop";
constexpr char VendorRequestStatus[] = "IsAdvancedSceneSwitcherRunning";
obs_websocket_vendor vendor;
static void registerWebsocketVendor();
static bool setup();
static bool setupDone = setup();
bool setup()
{
AddPluginPostLoadStep(registerWebsocketVendor);
return true;
}
static void
registerWebsocketVendorRequest(const char *requestName,
obs_websocket_request_callback_function callback)
{
if (!obs_websocket_vendor_register_request(vendor, requestName,
callback, NULL)) {
blog(LOG_ERROR,
"Failed to register \"%s\" request with obs-websocket.",
requestName);
}
}
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;
}
auto 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;
}
registerWebsocketVendorRequest(VendorRequestStart,
[](obs_data_t *, obs_data_t *, void *) {
StartPlugin();
});
registerWebsocketVendorRequest(VendorRequestStop,
[](obs_data_t *, obs_data_t *, void *) {
StopPlugin();
});
registerWebsocketVendorRequest(
VendorRequestStatus,
[](obs_data_t *, obs_data_t *response, void *) {
obs_data_set_bool(response, "isRunning",
PluginIsRunning());
});
}
const char *GetWebsocketVendorName()
{
return VendorName;
}
void RegisterWebsocketRequest(
const std::string &name,
const std::function<void(obs_data_t *, obs_data_t *)> &callback)
{
static std::mutex m;
std::lock_guard<std::mutex> lock(m);
callbacks.emplace_back(callback);
auto handleRequest = [](obs_data *requestData, obs_data *other,
void *cbPtr) {
auto cb = *(static_cast<
std::function<void(obs_data *, obs_data *)> *>(
cbPtr));
cb(requestData, other);
};
AddPluginPostLoadStep([name, handleRequest]() {
if (!obs_websocket_vendor_register_request(vendor, name.c_str(),
handleRequest,
&callbacks.back())) {
blog(LOG_ERROR,
"Failed to register \"%s\" request with obs-websocket.",
name.c_str());
}
});
}
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
{
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
}
} // namespace advss

View File

@ -0,0 +1,17 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <functional>
#include <obs-data.h>
#include <string>
namespace advss {
EXPORT const char *GetWebsocketVendorName();
EXPORT void RegisterWebsocketRequest(
const std::string &name,
const std::function<void(obs_data_t *, obs_data_t *)> &callback);
EXPORT void SendWebsocketVendorEvent(const std::string &eventName,
obs_data_t *data);
} // namespace advss

View File

@ -3,6 +3,7 @@
#include "log-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include "websocket-api.hpp"
#include <QCryptographicHash>
#include <obs-websocket-api.h>
@ -16,20 +17,18 @@ using websocketpp::lib::bind;
#define RPC_VERSION 1
#undef DispatchMessage
constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
obs_websocket_vendor vendor;
constexpr char VendorRequestMessage[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEventMessage[] = "AdvancedSceneSwitcherEvent";
static WebsocketMessageDispatcher websocketMessageDispatcher;
static void registerWebsocketVendor();
static bool setup();
static bool setupDone = setup();
static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *);
bool setup()
{
AddPluginPostLoadStep(registerWebsocketVendor);
RegisterWebsocketRequest(VendorRequestMessage, receiveWebsocketMessage);
return true;
}
@ -40,14 +39,12 @@ WebsocketMessageBuffer RegisterForWebsocketMessages()
void SendWebsocketEvent(const std::string &eventMsg)
{
auto data = obs_data_create();
OBSDataAutoRelease 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);
SendWebsocketVendorEvent(VendorEventMessage, data);
}
static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *,
void *)
static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *)
{
if (!obs_data_has_user_value(request_data, "message")) {
vblog(LOG_INFO, "received unexpected m '%s'",
@ -60,32 +57,6 @@ static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *,
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;
}
}
WSClientConnection::WSClientConnection(bool useOBSProtocol) : QObject(nullptr)
{
_client.get_alog().clear_channels(
@ -199,8 +170,8 @@ std::string ConstructVendorRequestMessage(const std::string &message)
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);
obs_data_set_string(vendorData, "vendorName", GetWebsocketVendorName());
obs_data_set_string(vendorData, "requestType", VendorRequestMessage);
auto msgObj = obs_data_create();
obs_data_set_string(msgObj, "message", message.c_str());
@ -298,14 +269,14 @@ void WSClientConnection::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) {
if (strcmp(obs_data_get_string(eventData, "vendorName"),
GetWebsocketVendorName()) != 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) {
if (strcmp(obs_data_get_string(eventData, "eventType"),
VendorEventMessage) != 0) {
vblog(LOG_INFO, "ignoring event type\"%s\"",
obs_data_get_string(eventData, "eventType"));
return;