From 91912f2aa56ac97c33916eb683e2b957d5bb5735 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 1 Sep 2024 22:15:25 +0200 Subject: [PATCH] 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. --- CMakeLists.txt | 2 + deps/obs-websocket/lib/obs-websocket-api.h | 73 ++++++++----- lib/advanced-scene-switcher.cpp | 7 ++ lib/utils/websocket-api.cpp | 114 +++++++++++++++++++++ lib/utils/websocket-api.hpp | 17 +++ plugins/base/utils/websocket-helpers.cpp | 57 +++-------- 6 files changed, 201 insertions(+), 69 deletions(-) create mode 100644 lib/utils/websocket-api.cpp create mode 100644 lib/utils/websocket-api.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 18349b90..fd742099 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/deps/obs-websocket/lib/obs-websocket-api.h b/deps/obs-websocket/lib/obs-websocket-api.h index 3ab1a623..c8fa52ef 100644 --- a/deps/obs-websocket/lib/obs-websocket-api.h +++ b/deps/obs-websocket/lib/obs-websocket-api.h @@ -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; diff --git a/lib/advanced-scene-switcher.cpp b/lib/advanced-scene-switcher.cpp index 74278ffb..148f66a8 100644 --- a/lib/advanced-scene-switcher.cpp +++ b/lib/advanced-scene-switcher.cpp @@ -14,6 +14,7 @@ #include "tab-helpers.hpp" #include "utility.hpp" #include "version.h" +#include "websocket-api.hpp" #include #include @@ -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) { diff --git a/lib/utils/websocket-api.cpp b/lib/utils/websocket-api.cpp new file mode 100644 index 00000000..9ef4cace --- /dev/null +++ b/lib/utils/websocket-api.cpp @@ -0,0 +1,114 @@ +#include "websocket-api.hpp" +#include "log-helper.hpp" +#include "obs-websocket-api.h" +#include "plugin-state-helpers.hpp" + +#include + +namespace advss { + +static std::vector> 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 &callback) +{ + static std::mutex m; + std::lock_guard lock(m); + + callbacks.emplace_back(callback); + auto handleRequest = [](obs_data *requestData, obs_data *other, + void *cbPtr) { + auto cb = *(static_cast< + std::function *>( + 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 diff --git a/lib/utils/websocket-api.hpp b/lib/utils/websocket-api.hpp new file mode 100644 index 00000000..56754331 --- /dev/null +++ b/lib/utils/websocket-api.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "export-symbol-helper.hpp" + +#include +#include +#include + +namespace advss { + +EXPORT const char *GetWebsocketVendorName(); +EXPORT void RegisterWebsocketRequest( + const std::string &name, + const std::function &callback); +EXPORT void SendWebsocketVendorEvent(const std::string &eventName, + obs_data_t *data); + +} // namespace advss diff --git a/plugins/base/utils/websocket-helpers.cpp b/plugins/base/utils/websocket-helpers.cpp index 4a1cc530..62870aeb 100644 --- a/plugins/base/utils/websocket-helpers.cpp +++ b/plugins/base/utils/websocket-helpers.cpp @@ -3,6 +3,7 @@ #include "log-helper.hpp" #include "plugin-state-helpers.hpp" #include "sync-helpers.hpp" +#include "websocket-api.hpp" #include #include @@ -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;